【问题标题】:Glitchy map AnnotationView Behavior故障地图 AnnotationView 行为
【发布时间】:2019-08-28 22:06:50
【问题描述】:

我创建了一个自定义 MKAnnotationView,类似于 Apple 创建的 this project。在其中,我放置了一个 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
}

【问题讨论】:

    标签: ios swift uikit mapkit mkmapview


    【解决方案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

    希望对你有帮助

    【讨论】:

    • 感谢您的详细回答,请参阅上面的更新。我尝试了您关于在 init 中删除添加约束的建议,但同样的故障仍在继续。为了排除我的自定义注释视图,我切换到苹果项目中使用的类的修改版本以及您的代表建议,但跳跃仍然存在,您认为这可能与集群有关吗
    • 编辑:确保在view.annotation == nil的情况下不显示任何内容。
    • 非常感谢您的所有帮助,我想我通过从 annotationView 的 init 函数中删除行 translatesAutoresizingMaskIntoConstraints = false 来修复它
    猜你喜欢
    • 1970-01-01
    • 2014-10-19
    • 2015-10-26
    • 2014-06-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-08
    • 1970-01-01
    相关资源
    最近更新 更多