【问题标题】:Can I assign different images to every pin in the map?我可以为地图中的每个图钉分配不同的图像吗?
【发布时间】:2017-01-04 17:04:54
【问题描述】:

我对 mapkit 有点陌生,但我想知道,我可以为地图上的每个图钉设置不同的图像吗?例如,字典中有用户信息,而不是常规的 pin 图像,必须有他们自己的图像。我应该如何为以下输出设置 viewFor 注释方法。

[{
email = "user1@gmail.com";
id = jqvDgcBoV9Y4sx1BHCmir5k90dr1;
name = User1;
profileImageUrl = "<null>";
}, {
email = "user2@gmail.com";
id = bqvDmcBoV9Y4sx1BqCmirnk90drz;
name = User2;
profileImageUrl = "https://firebasestorage.googleapis.com/v0/";
}, {
email = "user3@gmail.com";
id = axmDgcB5V9m4sx1nHC5ir5kn1dn3;
name = User3;
profileImageUrl = "https://firebasestorage.googleapis.com/v0/";
}]

顺便说一句,我有一个函数可以将 URL 转换为 UIImageView,但不是 UIImage,这是我最大的困难之一。

我的 viewForAnnotation 代表现在。

func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {

    var annotationView: MKAnnotationView?
    var annotationViewx: MKAnnotationView?
    let annotationIdentifier = "AnnotationIdentifier"

    guard !annotation.isKind(of: MKUserLocation.self) else {
        var annotationViewq: MKAnnotationView?
        annotationViewq = MKAnnotationView(annotation: annotation, reuseIdentifier: "userLocation")
        annotationViewq?.image = UIImage(named: "myLocation.png")


        let size = CGSize(width: 17, height: 17)
        UIGraphicsBeginImageContext(size)
        annotationViewq?.image!.draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height))

        let resizedImage = UIGraphicsGetImageFromCurrentImageContext()
        annotationViewq?.image = resizedImage
        annotationViewq?.isEnabled = true
        annotationViewq?.isUserInteractionEnabled = true
        return annotationViewq

    }


    if let dequeuedAnnotationView = mapView.dequeueReusableAnnotationView(withIdentifier: annotationIdentifier) {
        annotationView = dequeuedAnnotationView
        annotationView?.annotation = annotation
        annotationView?.canShowCallout = true
        annotationView?.image = UIImage(named: "emptyPhoto.png")

        let size = CGSize(width: 17, height: 17)
        UIGraphicsBeginImageContext(size)
        annotationView?.image!.draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height))

        let resizedImage = UIGraphicsGetImageFromCurrentImageContext()
        annotationView?.image = resizedImage

        return annotationView
    }
        //This annotation not working. but not problem
        let av = MKAnnotationView(annotation: annotation, reuseIdentifier: annotationIdentifier)
        av.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
        annotationViewx?.canShowCallout = true
        annotationViewx?.image = UIImage(named: "trafficIcon.png")

        let size = CGSize(width: 17, height: 17)
        UIGraphicsBeginImageContext(size)
        annotationViewx?.image!.draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height))

        let resizedImage = UIGraphicsGetImageFromCurrentImageContext()
        annotationViewx?.image = resizedImage
        annotationViewx = av


    return annotationViewx

}

【问题讨论】:

  • 顺便说一句,你现在如何分配 pin 图像?
  • @Rob 先生,我没有看到标准 pin,我的 pin 就像我在代码中设置的一样,唯一不同的是我的位置 pin,但没有标准的 pin。我想为每个 pin 设置 profileImageUrl ..
  • 我为第一条评论写了viewFor Annotation 方法,他想知道我如何在viewFor Annotation 中分配图像。正如你所说,这是一个好的开始 Matt 的代码。当我处理这个问题时,我会编辑帖子,可能在问题解决后会更清楚:) 顺便说一句,英语不是我的第一语言,抱歉答案不清楚。我很抱歉占用您的时间。

标签: ios swift mapkit nsdictionary mkannotation


【解决方案1】:

首先,让我说,我认为 matt 已经抓住了问题的根源,即如果您有带有自己图像的注释,您应该定义自己的注释类型来捕获图像的 URL,例如

class CustomAnnotation: NSObject, MKAnnotation {
    dynamic var coordinate: CLLocationCoordinate2D
    dynamic var title: String?
    dynamic var subtitle: String?

    var imageURL: URL?

    init(coordinate: CLLocationCoordinate2D, title: String? = nil, subtitle: String? = nil, imageURL: URL? = nil) {
        self.coordinate = coordinate
        self.title = title
        self.subtitle = subtitle
        self.imageURL = imageURL

        super.init()
    }
}

添加注释时,请确保提供 URL,然后您就可以参加比赛了。我要指出的唯一额外的事情是,您确实想要跟踪哪个网络请求与哪个注释相关联(以便您可以在需要时取消它)。所以我会在注解视图类中添加一个URLSessionTask 属性:

class CustomAnnotationView: MKAnnotationView {
    weak var task: URLSessionTask?    // keep track of this in case we need to cancel it when the annotation view is re-used
}

坦率地说,我会将你所有复杂的配置代码从mapView(_:viewFor:) 方法中提取出来,并将其放在注释视图类中,以便更好地分工并避免视图控制器膨胀。

因此,例如,MKUserLocation 注释的自定义注释视图:

class CustomUserAnnotationView: MKAnnotationView {
    static let reuseIdentifier = Bundle.main.bundleIdentifier! + ".customUserAnnotationView"
    private let size = CGSize(width: 17, height: 17)

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

        image = UIImage(named: "myLocation.png")?.resized(to: size)
    }

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

另一个带有imageURL 属性的CustomAnnotation 注释,它将异步获取所需的图像:

class CustomAnnotationView: MKAnnotationView {
    static let reuseIdentifier = Bundle.main.bundleIdentifier! + ".customAnnotationView"

    private weak var task: URLSessionTask?
    private let size = CGSize(width: 17, height: 17)

    override var annotation: MKAnnotation? {
        didSet {
            if annotation === oldValue { return }

            task?.cancel()
            image = UIImage(named: "emptyPhoto.png")?.resized(to: size)
            updateImage(for: annotation)
        }
    }

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

        canShowCallout = true
        rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
        image = UIImage(named: "emptyPhoto.png")?.resized(to: size)
    }

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

    private func updateImage(for annotation: MKAnnotation?) {
        guard let annotation = annotation as? CustomAnnotation, let url = annotation.imageURL else { return }

        let task = URLSession.shared.dataTask(with: url) { data, _, error in
            guard let data = data,
                let image = UIImage(data: data)?.resized(to: self.size),
                error == nil else {
                    print(error ?? "Unknown error")
                    return
            }

            DispatchQueue.main.async {
                UIView.transition(with: self, duration: 0.2, options: .transitionCrossDissolve, animations: {
                    self.image = image
                }, completion: nil)
            }
        }
        task.resume()
        self.task = task
    }
}

然后,在 iOS 11 及更高版本中,我的视图控制器可以简单地在 viewDidLoad 中注册这两个类:

mapView.register(CustomUserAnnotationView.self, forAnnotationViewWithReuseIdentifier: CustomUserAnnotationView.reuseIdentifier)
mapView.register(CustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: CustomAnnotationView.reuseIdentifier)

然后,mapView(_:viewFor:) 提炼成一个更简单的方法:

extension ViewController: MKMapViewDelegate {
    func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
        let identifier: String

        switch annotation {
        case is MKUserLocation:   identifier = CustomUserAnnotationView.reuseIdentifier
        case is CustomAnnotation: identifier = CustomAnnotationView.reuseIdentifier
        default:                  return nil
        }

        return mapView.dequeueReusableAnnotationView(withIdentifier: identifier, for: annotation)
    }
}

请注意,我已尝试修复埋在您的 viewForAnnotation 方法中的一大堆其他问题,特别是:

  1. 您的图像大小调整逻辑 (a) 重复了几次; (b) 没有打电话给UIGraphicsEndImageContext。所以,我建议把它拉出来(例如在这个UIImage 扩展中),还不如使用UIGraphicsImageRenderer 来简化它:

    extension UIImage {
        func resized(to size: CGSize) -> UIImage {
            return UIGraphicsImageRenderer(size: size).image { _ in
                draw(in: CGRect(origin: .zero, size: size))
            }
        }
    }
    

    您可能需要考虑是否需要方面填充或类似的东西,但这里有一些其他想法的排列:https://stackoverflow.com/a/28513086/1271826。或者,也许更好的是,看看 AlamofireImageKingfisher 的大小调整例程。

    但带回家的信息是,您应该将血腥的调整大小逻辑从 viewForAnnotation 中提取出来并放入它自己的例程/库中。

  2. 您确实也应该为用户位置使用出队逻辑。

  3. 注意,我只是在做简单的URLSession.shared.dataTask 而不寻找缓存等。你显然可以在这里变得更复杂(例如缓存调整大小的图像视图等)。

  4. 这没什么大不了,但我觉得这个结构有点笨拙:

    guard !annotation.isKind(of: MKUserLocation.self) else { ... }
    

    所以我将其简化为使用is 测试。例如:

    if annotation is MKUserLocation { ... }
    

    基本上是一样的,但更直观一些。

  5. 注意,上面的例程和你的一样,为注释视图的image 属性使用调整大小的占位符图像。为了未来的读者,让我说这很重要,因为标准注释视图不能优雅地处理image 大小的异步变化。您可以修改该类来执行此操作,或者更简单地,就像我们在这里一样,使用标准大小的占位符图像。

注意,请参阅适用于 iOS 10.x 及更早版本的 prior revision of this answer

【讨论】:

    【解决方案2】:

    但我想知道,我可以为地图上的每个图钉设置不同的图像

    当然。您需要创建自定义注释类型。您的自定义注释类型将在您将提供的实例属性中携带图像信息。这样,当你到达mapView(_:viewFor:)时,你就会知道这个注解需要什么图像并可以分配它。

    例子:

    class MyAnnotation : NSObject, MKAnnotation {
        dynamic var coordinate : CLLocationCoordinate2D
        var title: String?
        var subtitle: String?
        var imageName: String?
    
        init(location coord:CLLocationCoordinate2D) {
            self.coordinate = coord
            super.init()
        }
    }
    

    创建注释时,在将其附加到地图之前,还要为其分配imageName。现在地图调用代理请求图像视图,您可以阅读imageName 属性,您将知道该怎么做。

    【讨论】:

    • 我建议titlesubtitle 也是dynamic。他现在可能不需要它,但标注确实对那些...
    • @Rob 我永远不会说你错了,但就我个人而言,我从来不需要声明它们dynamic
    • 但是我如何设置它们以显示用户自己的图像。也许会有数百人。
    • 等等,@GreatCornholio - 我对“用户自己的形象”、“按顺序”和“数百人”一无所知。您问如何为每个注释赋予其自己的图像,我告诉了您。如果您还有其他更大的问题,那就另当别论了。
    • 是的,先生,感谢您的贡献,但我在帖子的第一段中提到了它。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-02-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多