【问题标题】:How do I animate MKAnnotationView drop?如何为 MKAnnotationView 放置动画?
【发布时间】:2024-04-26 13:35:01
【问题描述】:

我有一个自定义的 MKAnnotationView,我自己在 viewForAnnotation 中设置了我的图像。如何像使用 MKPinAnnotationView 一样为它的下降设置动画?

我的代码是

- (MKAnnotationView *)mapView:(MKMapView *)map viewForAnnotation:(id <MKAnnotation>)annotation
{
    static NSString *AnnotationViewID = @"annotationViewID";

    MKAnnotationView *annotationView = (MKAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:AnnotationViewID];

    if (annotationView == nil)
    {
        annotationView = [[[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:AnnotationViewID] autorelease];
    }

    annotationView.image = [UIImage imageNamed:@"blah.png"];
    annotationView.annotation = annotation;

    return annotationView;
}

【问题讨论】:

标签: cocoa-touch ios-4.2 mkannotationview mkpinannotationview mapkit


【解决方案1】:

Anna Karenina 上面代码的一个问题是,当您在用户正在查看的下方添加注释时,它无法处理。这些注释会在掉落之前漂浮在半空中,因为它们已移动到用户可见的地图矩形中。

另一个是它还会丢弃用户位置蓝点。使用下面的代码,您可以在屏幕外处理用户位置和大量地图注释。我还添加了一个不错的反弹;)

- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views {
    MKAnnotationView *aV; 

    for (aV in views) {

        // Don't pin drop if annotation is user location
        if ([aV.annotation isKindOfClass:[MKUserLocation class]]) {
            continue;
        }

        // Check if current annotation is inside visible map rect, else go to next one
        MKMapPoint point =  MKMapPointForCoordinate(aV.annotation.coordinate);
        if (!MKMapRectContainsPoint(self.mapView.visibleMapRect, point)) {
            continue;
        }

        CGRect endFrame = aV.frame;

        // Move annotation out of view
        aV.frame = CGRectMake(aV.frame.origin.x, aV.frame.origin.y - self.view.frame.size.height, aV.frame.size.width, aV.frame.size.height);

        // Animate drop
        [UIView animateWithDuration:0.5 delay:0.04*[views indexOfObject:aV] options: UIViewAnimationOptionCurveLinear animations:^{

            aV.frame = endFrame;

        // Animate squash
        }completion:^(BOOL finished){
            if (finished) {
                [UIView animateWithDuration:0.05 animations:^{
                    aV.transform = CGAffineTransformMakeScale(1.0, 0.8);

                }completion:^(BOOL finished){
                    if (finished) {
                        [UIView animateWithDuration:0.1 animations:^{
                            aV.transform = CGAffineTransformIdentity;
                        }];
                    }
                }];
            }
        }];
    }
}

【讨论】:

  • 感谢@MrAlek,动画效果很好。
【解决方案2】:

实现didAddAnnotationViews委托方法并自己做动画:

- (void)mapView:(MKMapView *)mapView 
          didAddAnnotationViews:(NSArray *)annotationViews
{
    for (MKAnnotationView *annView in annotationViews)
    {
        CGRect endFrame = annView.frame;
        annView.frame = CGRectOffset(endFrame, 0, -500);
        [UIView animateWithDuration:0.5 
                animations:^{ annView.frame = endFrame; }];
    }
}

【讨论】:

  • 谢谢,成功了!我怎样才能让它反弹而不是从顶部掉下来?
  • 您可以将所有 UIView 动画技术与注释视图(它是 UIView 的子类)一起使用。上面的例子只是改变了 y 坐标,但你可以做其他的变换。搜索诸如“uiview 弹跳动画”之类的示例。这是一个:*.com/questions/2160150/mimic-uialertview-bounce/…。您需要添加 QuartzCore 框架,将其导入,然后将“view”更改为“annView”。
  • @AnnaKarenina 但是我们如何添加 CustomView 作为我们的 pinView 和 customCallOut 任何引用。
【解决方案3】:

@MrAlek 对 swift3

的回答
optional func mapView(_ mapView: MKMapView, didAdd views: [MKAnnotationView]) {
    print(#function)

    var i = -1;
    for view in views {
        i += 1;
        if view.annotation is MKUserLocation {
            continue;
        }

        // Check if current annotation is inside visible map rect, else go to next one
        let point:MKMapPoint  =  MKMapPointForCoordinate(view.annotation!.coordinate);
        if (!MKMapRectContainsPoint(self.mapView.visibleMapRect, point)) {
            continue;
        }

        let endFrame:CGRect = view.frame;

        // Move annotation out of view
        view.frame = CGRect(origin: CGPoint(x: view.frame.origin.x,y :view.frame.origin.y-self.view.frame.size.height), size: CGSize(width: view.frame.size.width, height: view.frame.size.height))

        // Animate drop
        let delay = 0.03 * Double(i)
        UIView.animate(withDuration: 0.5, delay: delay, options: UIViewAnimationOptions.curveEaseIn, animations:{() in
            view.frame = endFrame
            // Animate squash
            }, completion:{(Bool) in
                UIView.animate(withDuration: 0.05, delay: 0.0, options: UIViewAnimationOptions.curveEaseInOut, animations:{() in
                    view.transform = CGAffineTransform(scaleX: 1.0, y: 0.6)

                    }, completion: {(Bool) in
                        UIView.animate(withDuration: 0.3, delay: 0.0, options: UIViewAnimationOptions.curveEaseInOut, animations:{() in
                            view.transform = CGAffineTransform.identity
                            }, completion: nil)
                })

        })
    }
}

【讨论】:

    【解决方案4】:

    @mrAlek 用 Swift 回答:

    func mapView(mapView: MKMapView!, didAddAnnotationViews views: [AnyObject]!) {
        println("didAddAnnotationViews()")
    
        var i = -1;
        for view in views {
            i++;
            let mkView = view as! MKAnnotationView
            if view.annotation is MKUserLocation {
                continue;
            }
    
            // Check if current annotation is inside visible map rect, else go to next one
            let point:MKMapPoint  =  MKMapPointForCoordinate(mkView.annotation.coordinate);
            if (!MKMapRectContainsPoint(self.mapView.visibleMapRect, point)) {
                continue;
            }
    
            let endFrame:CGRect = mkView.frame;
    
            // Move annotation out of view
            mkView.frame = CGRectMake(mkView.frame.origin.x, mkView.frame.origin.y - self.view.frame.size.height, mkView.frame.size.width, mkView.frame.size.height);
    
            // Animate drop
            let delay = 0.03 * Double(i)
            UIView.animateWithDuration(0.5, delay: delay, options: UIViewAnimationOptions.CurveEaseIn, animations:{() in
                mkView.frame = endFrame
                // Animate squash
                }, completion:{(Bool) in
                            UIView.animateWithDuration(0.05, delay: 0.0, options: UIViewAnimationOptions.CurveEaseInOut, animations:{() in
                                mkView.transform = CGAffineTransformMakeScale(1.0, 0.6)
    
                            }, completion: {(Bool) in
                                    UIView.animateWithDuration(0.3, delay: 0.0, options: UIViewAnimationOptions.CurveEaseInOut, animations:{() in
                                        mkView.transform = CGAffineTransformIdentity
                                        }, completion: nil)
                            })
    
                        })
        }
    }
    

    【讨论】:

      【解决方案5】:

      @MrAlek 在 Swift 2 中的回答:

      func mapView(mapView: MKMapView, didAddAnnotationViews views: [MKAnnotationView]) {
          print(__FUNCTION__)
      
          var i = -1;
          for view in views {
              i++;
              if view.annotation is MKUserLocation {
                  continue;
              }
      
              // Check if current annotation is inside visible map rect, else go to next one
              let point:MKMapPoint  =  MKMapPointForCoordinate(view.annotation!.coordinate);
              if (!MKMapRectContainsPoint(self.mapView.visibleMapRect, point)) {
                  continue;
              }
      
              let endFrame:CGRect = view.frame;
      
              // Move annotation out of view
              view.frame = CGRectMake(view.frame.origin.x, view.frame.origin.y - self.view.frame.size.height, view.frame.size.width, view.frame.size.height);
      
              // Animate drop
              let delay = 0.03 * Double(i)
              UIView.animateWithDuration(0.5, delay: delay, options: UIViewAnimationOptions.CurveEaseIn, animations:{() in
                  view.frame = endFrame
                  // Animate squash
                  }, completion:{(Bool) in
                      UIView.animateWithDuration(0.05, delay: 0.0, options: UIViewAnimationOptions.CurveEaseInOut, animations:{() in
                          view.transform = CGAffineTransformMakeScale(1.0, 0.6)
      
                          }, completion: {(Bool) in
                              UIView.animateWithDuration(0.3, delay: 0.0, options: UIViewAnimationOptions.CurveEaseInOut, animations:{() in
                                  view.transform = CGAffineTransformIdentity
                                  }, completion: nil)
                      })
      
              })
          }
      }
      

      【讨论】:

      • 这太棒了!非常感谢;)
      【解决方案6】:

      为 Swift 4.2 更新

         func mapView(_ mapView: MKMapView, didAdd views: [MKAnnotationView]) {
          var i = -1;
          for view in views {
              i += 1;
              if view.annotation is MKUserLocation {
                  continue;
              }
              let point:MKMapPoint  =  MKMapPoint(view.annotation!.coordinate);
              if (!self.mapView.visibleMapRect.contains(point)) {
                  continue;
              }
              let endFrame:CGRect = view.frame;
              view.frame = CGRect(origin: CGPoint(x: view.frame.origin.x,y :view.frame.origin.y-self.view.frame.size.height), size: CGSize(width: view.frame.size.width, height: view.frame.size.height))
              let delay = 0.03 * Double(i)
              UIView.animate(withDuration: 0.5, delay: delay, options: UIView.AnimationOptions.curveEaseIn, animations:{() in
                  view.frame = endFrame
              }, completion:{(Bool) in
                  UIView.animate(withDuration: 0.05, delay: 0.0, options: UIView.AnimationOptions.curveEaseInOut, animations:{() in
                      view.transform = CGAffineTransform(scaleX: 1.0, y: 0.6)
                  }, completion: {(Bool) in
                      UIView.animate(withDuration: 0.3, delay: 0.0, options: UIView.AnimationOptions.curveEaseInOut, animations:{() in
                          view.transform = CGAffineTransform.identity
                      }, completion: nil)
                  })
              })
          }
      }
      

      【讨论】:

        【解决方案7】:

        除了在MKMapViewDelegate 中实现mapView(_:didAdd:),您还可以让注释视图自己做动画。

        class CustomAnnotationView: MKAnnotationView {
            override var annotation: MKAnnotation? { didSet { update(for: annotation) } }
        
            override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
                super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
                update(for: annotation)
            }
        
            override func prepareForReuse() {
                super.prepareForReuse()
                removeFromSuperview()
            }
        
            required init?(coder aDecoder: NSCoder) {
                fatalError("init(coder:) has not been implemented")
            }
        
            override func didMoveToSuperview() {
                super.didMoveToSuperview()
        
                transform = CGAffineTransform(translationX: 0, y: -100)
                alpha = 0
                UIViewPropertyAnimator(duration: 0.5, dampingRatio: 0.4) {
                    self.transform = .identity
                    self.alpha = 1
                }.startAnimation()
            }
        
            private func update(for annotation: MKAnnotation?) {
                // do whatever update to the annotation view you want, if any
            }
        }
        

        这对于避免视图控制器与注释视图动画的混乱很有用。例如。在 iOS 11 及更高版本中,您可能会这样做:

        mapView.register(CustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier)
        

        然后你可以在你的地图视图中添加一个注解,你可以得到这个注解视图的动画,而无需在视图控制器中添加任何代码。

        这个特殊的动画是一个最后有一点反弹的下降,但显然你可以做任何你想做的动画。

        上面是用 Swift 编写的,但这个概念在 Objective-C 中也同样有效。

        【讨论】: