故障地图AnnotationView行为

时间:2019-08-28 22:06:50

标签: ios swift uikit mapkit mkmapview

我已经创建了一个类似于Apple创建的this project的自定义MKAnnotationView。在其中放置一个imageView和一个标签。但是,当我在地图上滚动和缩放时,我注意到非常不正常的行为。即使在启动后,也会显示此行为。在跳动之前,可以在左上角短暂查看这些视图。

下面是该问题的gif的链接

The Problem

下面是自定义AnnotationView的代码

更新 我改用了AnnotationView苹果的修改版本,并带有以下建议,但我仍然遇到相同的跳跃行为jumpy behavior

a link to the complete code

import Foundation
import UIKit
import MapKit
import SDWebImage

class AppleCustomAnnotationView: MKAnnotationView {

    private let boxInset = CGFloat(10)
    private let interItemSpacing = CGFloat(10)
    private let maxContentWidth = CGFloat(90)
    private let contentInsets = UIEdgeInsets(top: 10, left: 30, bottom: 20, right: 20)

    private lazy var stackView: UIStackView = {
        let stackView = UIStackView(arrangedSubviews: [label, imageView])
        stackView.translatesAutoresizingMaskIntoConstraints = false
        stackView.axis = .vertical
        stackView.alignment = .top
        stackView.spacing = interItemSpacing

        return stackView
    }()

    private lazy var label: UILabel = {
        let label = UILabel(frame: .zero)
        label.textColor = UIColor.white
        label.lineBreakMode = .byWordWrapping
        label.backgroundColor = UIColor.clear
        label.numberOfLines = 2
        label.font = UIFont.preferredFont(forTextStyle: .caption1)

        return label
    }()

    private lazy var imageView: UIImageView = {
        let imageView = UIImageView(image: nil)
        return imageView
    }()

    private var imageHeightConstraint: NSLayoutConstraint?

    override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
        super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)

        backgroundColor = UIColor.clear
        translatesAutoresizingMaskIntoConstraints = false

        addSubview(stackView)

        // Anchor the top and leading edge of the stack view to let it grow to the content size.
        stackView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: contentInsets.left).isActive = true
        stackView.topAnchor.constraint(equalTo: self.topAnchor, constant: contentInsets.top).isActive = true

        // Limit how much the content is allowed to grow.
        imageView.widthAnchor.constraint(lessThanOrEqualToConstant: maxContentWidth).isActive = true
        label.widthAnchor.constraint(equalTo: imageView.widthAnchor).isActive = true
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func prepareForReuse() {
        super.prepareForReuse()
        imageView.image = nil
        label.text = nil
    }

    override func prepareForDisplay() {
        super.prepareForDisplay()

        /*
         If using the same annotation view and reuse identifier for multiple annotations, iOS will reuse this view by calling `prepareForReuse()`
         so the view can be put into a known default state, and `prepareForDisplay()` right before the annotation view is displayed. This method is the view's opportunity to update itself to display content for the new annotation.
         */
        if let annotation = annotation as? ImageAnnotation {
            label.text = annotation.title
            let placeHolder = #imageLiteral(resourceName: "DSC00042")
            self.imageView.sd_setImage(with: annotation.photoURL, placeholderImage: placeHolder, options: SDWebImageOptions.refreshCached, completed: {(image,error,imageCacheType,storageReference) in
                    if let error = error{
                        print("Uh-Oh an error has occured: \(error.localizedDescription)" )
                    }
                guard let image = image else{
                    return
                }
                if let heightConstraint = self.imageHeightConstraint {
                    self.imageView.removeConstraint(heightConstraint)
                }

                let ratio = image.size.height / image.size.width
                self.imageHeightConstraint = self.imageView.heightAnchor.constraint(equalTo: self.imageView.widthAnchor, multiplier: ratio, constant: 0)
                self.imageHeightConstraint?.isActive = true
                })
        }

        // Since the image and text sizes may have changed, require the system do a layout pass to update the size of the subviews.
        setNeedsLayout()
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        // The stack view will not have a size until a `layoutSubviews()` pass is completed. As this view's overall size is the size
        // of the stack view plus a border area, the layout system needs to know that this layout pass has invalidated this view's
        // `intrinsicContentSize`.
        invalidateIntrinsicContentSize()

        // The annotation view's center is at the annotation's coordinate. For this annotation view, offset the center so that the
        // drawn arrow point is the annotation's coordinate.
        let contentSize = intrinsicContentSize
        centerOffset = CGPoint(x: 0, y: -contentSize.height/2)

        // Now that the view has a new size, the border needs to be redrawn at the new size.
        setNeedsDisplay()
    }

    override var intrinsicContentSize: CGSize {
        var size = stackView.bounds.size
        size.width += contentInsets.left + contentInsets.right
        size.height += contentInsets.top + contentInsets.bottom + 30
        return size
    }

    override func draw(_ rect: CGRect) {
        super.draw(rect)

        // Used to draw the rounded background box and pointer.
        UIColor.darkGray.setFill()

//        let path2 = UIBezierPath(ovalIn: CGRect(x: rect.width/2, y: rect.height - 10, width: 10, height: 10))
//
//        let shapeLayer2 = CAShapeLayer()
//        shapeLayer2.path = path2.cgPath
//        shapeLayer2.fillColor = UIColor.purple.cgColor
//        shapeLayer2.strokeColor = UIColor.white.cgColor
//        shapeLayer2.lineWidth = 1
//
//        layer.addSublayer(shapeLayer2)
//        shapeLayer2.position = CGPoint(x: -10, y: 15)

        // Draw the pointed shape.
//        let pointShape = UIBezierPath(ovalIn: CGRect(x: rect.width/2, y: rect.height - 10, width: 10, height: 10))
//        pointShape.move(to: CGPoint(x: 14, y: 0))
//        pointShape.addLine(to: CGPoint.zero)
//        pointShape.addLine(to: CGPoint(x: rect.size.width, y: rect.size.height))
//        pointShape.fill()

        // Draw the rounded box.
        let box = CGRect(x: boxInset, y: 0, width: rect.size.width - boxInset, height: rect.size.height - 30)
        let roundedRect = UIBezierPath(roundedRect: box, cornerRadius: 5)
        roundedRect.lineWidth = 2
        roundedRect.fill()


        UIColor.purple.setFill()
        let circleDot = UIBezierPath(ovalIn: CGRect(x: box.midX - 7.5, y: rect.size.height - 15, width: 15, height: 15))
        circleDot.lineWidth = 2
        circleDot.fill()

    }
}

代理

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


        guard let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "Apple", for: annotation) as? AppleCustomAnnotationView else{
            fatalError("Unexpected annotation view type")
        }
        annotationView.annotation = annotation
        annotationView.clusteringIdentifier = MKMapViewDefaultClusterAnnotationViewReuseIdentifier
        annotationView.collisionMode = .rectangle
        return annotationView
    }else if let cluster = annotation as? MKClusterAnnotation{

        guard let view = mapView.dequeueReusableAnnotationView(withIdentifier: "ClusterAnnotationView", for: annotation) as? AppleClusterAnnotationView else{
            fatalError("Wrong type for cluster annotationview")
        }

        return view
    }
    return nil
}

1 个答案:

答案 0 :(得分:1)

您要做的一件非常不寻常的事:override init,您正在那里进行主要布局工作。

这没有任何意义,因为MKAnnotationView可以重复使用以显示不同的注释。

您可以在委托中使用此重用功能。

您引用的Appl项目中也没有显示这种实现方式。

MKAnnotationView中应该做的是:

override var annotation: MKAnnotation? {
    willSet {
        // do what you did in override init(...) {...} before.
        // be aware that Apple sets annotation to nil if it is not used any more.
    }
}

在您的代表中,仅在出队后这样做

annotationView.annotation = annotation

确保在view.annotation == nil下不显示任何内容。

这样可以确保出队情况和创建新的MKAnnotationView时使用相同的布局代码

希望这会有所帮助