Resize MKAnnotationView Image When map zooms in an

2019-01-22 10:09发布

What i have

I have about 150 MKAnnotationViews on a map. Every MKAnnotationView has an image that replaces the default pin.

What's happening now

When the map zooms in, the MKAnnotationViews are getting smaller and the opposite when it zooms out.

What I wish Happened

Well i want it to be the other way around. Since when the map is small I wish that the MKAnnotationViews will be smaller so the user can see all of them, and when he zooms in I wish they will be bigger.

What code I have so far

I know how to get the zoom change, and i know i can get the "pMapView.region.span.latitudeDelta" as a reference for the zoom amount. and i know i can change the annotationView.frame.

-(void)mapView:(MKMapView *)pMapView regionDidChangeAnimated:(BOOL)animated{
    NSLog(@"mapView.region.span.latitudeDelta = %f",pMapView.region.span.latitudeDelta);
    for (id <MKAnnotation> annotation in pMapView.annotations) {
        MKAnnotationView *annotationView  = [pMapView viewForAnnotation: annotation];
    }
}

Can someone help me with that please? Thanks shani

5条回答
Animai°情兽
2楼-- · 2019-01-22 10:23

An alternative method would be to re-size the annotation view image property. An example is shown in this post

查看更多
劫难
3楼-- · 2019-01-22 10:23

@Funktional's answer in Swift 3:

class MapViewController: UIViewController: UIGestureRecognizerDelegate {

    @IBOutlet weak var mapView: MKMapView!

    override func viewDidLoad() {
        super.viewDidLoad()

        // You can also add this gesture recognizer and set the delegate via storyboard
        let pinchGR = UIPinchGestureRecognizer(target: self, action: #selector(handlePinchGesture))
        pinchGR.delegate = self
        self.mapView.addGestureRecognizer(pinchGR)
    }

    // link as @IBAction when added via storyboard
    func handlePinchGesture(_ sender: UIPinchGestureRecognizer) {
        if sender.state == .ended {
            for annotation in mapView.annotations {
                if annotation is MKUserLocation {
                    continue
                }
                guard let annotationView = self.mapView.view(for: annotation) else { continue }
                let scale = -1 * sqrt(1 - pow(mapView.zoomLevel / 20, 2.0)) + 1.4
                annotationView.transform = CGAffineTransform(scaleX: CGFloat(scale), y: CGFloat(scale))
            }
        }
    }

    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }
}

extension MKMapView {
    var zoomLevel: Double {
        return log2(360 * ((Double(self.frame.size.width) / 256) / self.region.span.longitudeDelta)) - 1
    }
}
查看更多
Summer. ? 凉城
4楼-- · 2019-01-22 10:24

Sorry about my bad english... but I just want to help someone.

Thank you so much, Funktional. Your answer is great... its working on iOS5 perfectly! But this is invalidated on iOS6, and it seems there is no solution to solve this issue for now.

But finally, I solved it in a stupid/static way and it works fine on both iOS. I am not going to explain too much. This is what I've done in my project.

  1. The image for the mkannotation is named pin.png (the size is 20x20)

  2. I created five more images with different size for different zoom level (18x18, 16x16, 14x14, 12x12, 10x10) and they are named pin-5.png, pin-4.png, pin-3.png, pin-2.png, pin-1.png.

  3. I removed all the code about calculating scale in formatAnnotationView and added this line

    NSString* imageName = 
        [NSString stringWithFormat:@"%@%@.png", 
        [imageName substringToIndex:[imageName length]-4 ], 
        [self getImageLevel]];
    
  4. Also, change this from

    UIImage *pinImage = [UIImage imageNamed:@"YOUR_IMAGE_NAME_HERE"];

    to

    UIImage *pinImage = [[UIImage alloc] initWithContentsOfFile:imageName];

  5. add this function

    -(NSString *)getImageLevel{
    
    NSString* output;
    
    double zoomLevel = [self getZoomLevel:_mapview];
    
    
    if(zoomLevel >= 19.0)
        output = @"";
    else if(zoomLevel < 19.0 && zoomLevel >= 18.0)
        output = @"-5";
    else if(zoomLevel < 18.0 && zoomLevel >= 17.0)
        output = @"-4";
    else if(zoomLevel < 17.0 && zoomLevel >= 16.0)
        output = @"-3";
    else if(zoomLevel < 16.0 && zoomLevel >= 15.0)
        output = @"-2";
    else 
        output = @"-1";
    
    return output;}
    

sorry about the bad coding and english again.

查看更多
迷人小祖宗
5楼-- · 2019-01-22 10:27

In Swift 3, this worked for me. My image was 100% on level 19, so 1/19 gives me 0.5263158 which is my linear scale:

func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {    
  if annotation is MKUserLocation {
    return nil
  }

  let mkView = MKAnnotationView(annotation: annotation, reuseIdentifier: "mkView")
  mkView.image = UIImage(named: "Foo")
  formatAnnotation(pinView: mkView, forMapView: mapView)
  return mkView;
}

func formatAnnotation(pinView: MKAnnotationView, forMapView: MKMapView) {
  let zoomLevel = forMapView.getZoomLevel()
  let scale = 0.05263158 * zoomLevel //Modify to whatever scale you need.
  pinView.transform = CGAffineTransform(scaleX: CGFloat(scale), y: CGFloat(scale))
}
查看更多
我想做一个坏孩纸
6楼-- · 2019-01-22 10:48

Actually on the default MKMapView- the annotation (e.g. pin or image) and the callout (e.g. bubble) remain the same size as you zoom in or out. They do not scale. But I get your point- in relation to the map they appear to be growing as the map zooms out and shrinking as the map zooms in.

So there are two solutions to your problem and they work slightly differently:

  1. Implement -(void)mapView:(MKMapView *)pMapView regionDidChangeAnimated:(BOOL)animated from the MKMapViewDelegate Protocol Reference - which you've already done.
  2. Attach a UIPinchGestureRecognizer to the MKMapView object and then implement the action.

Option #1 - mapView:regionDidChangeAnimated: will be called for either a scroll or a zoom event - basically any time the map region changed as the name implies. This results in a slightly less smooth resizing of icons, because the map events are fired less frequently.

My preferences is for Option #2 - Attach a UIPinchGestureRecognizer to the MKMapView object and then implement the action. Pinch gesture events are fired rather quickly, so you get a smooth resizing of the icon. And they only fire for a recognized pinch event- so they won't fire during a scroll event.

The action methods invoked must conform to one of the following signatures:

- (void)handleGesture;
- (void)handleGesture:(UIGestureRecognizer *)gestureRecognizer;

You have to be careful to not override the maps default zoom behavior. See this post: "UIMapView: UIPinchGestureRecognizer not called" for more info. Short answer is that you have to implement shouldRecognizeSimultaneouslyWithGestureRecognizer: and return YES.

All told here is some sample code:

// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad
{
    [super viewDidLoad];

    self.mapView.mapType = MKMapTypeStandard;   // also MKMapTypeSatellite or MKMapTypeHybrid

    // Add a pinch gesture recognizer
    UIPinchGestureRecognizer *pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinchGesture:)];
    pinchRecognizer.delegate = self;
    [self.mapView addGestureRecognizer:pinchRecognizer];
    [pinchRecognizer release];
}

#pragma mark -
#pragma mark UIPinchGestureRecognizer

- (void)handlePinchGesture:(UIPinchGestureRecognizer *)pinchRecognizer {
    if (pinchRecognizer.state != UIGestureRecognizerStateChanged) {
        return;
    }

    MKMapView *aMapView = (MKMapView *)pinchRecognizer.view;

    for (id <MKAnnotation>annotation in aMapView.annotations) {
        // if it's the user location, just return nil.
        if ([annotation isKindOfClass:[MKUserLocation class]])
            return;

        // handle our custom annotations
        //
        if ([annotation isKindOfClass:[MKPointAnnotation class]])
        {
            // try to retrieve an existing pin view first
            MKAnnotationView *pinView = [aMapView viewForAnnotation:annotation];
            //Format the pin view
            [self formatAnnotationView:pinView forMapView:aMapView];
        }
    }    
}

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
    return YES;
}

So at this point- you again have several options for how to resize the annotation. Both of the following code samples rely on Troy Brant's code for getting the zoom level of an MKMapView.

  1. Resize BOTH the annotation image and the callout using a transform. Personally I think the transform results in a cleaner looking resize. But in most cases- resizing the callout is not called for.
  2. Resize just the annotation image - I use Trevor Harmon's Resize a UIImage the right way but again my opinion is that it's not as clean looking a resize.

Here is some more sample code:

- (void)formatAnnotationView:(MKAnnotationView *)pinView forMapView:(MKMapView *)aMapView {
    if (pinView)
    {
        double zoomLevel = [aMapView zoomLevel];
        double scale = -1 * sqrt((double)(1 - pow((zoomLevel/20.0), 2.0))) + 1.1; // This is a circular scale function where at zoom level 0 scale is 0.1 and at zoom level 20 scale is 1.1

        // Option #1
        pinView.transform = CGAffineTransformMakeScale(scale, scale);

        // Option #2
        UIImage *pinImage = [UIImage imageNamed:@"YOUR_IMAGE_NAME_HERE"];
        pinView.image = [pinImage resizedImage:CGSizeMake(pinImage.size.width * scale, pinImage.size.height * scale) interpolationQuality:kCGInterpolationHigh];
    }
}

If this works, don't forget to mark it as the answer.

查看更多
登录 后发表回答