Ios 我可以为地图中的每个pin分配不同的图像吗?

Ios 我可以为地图中的每个pin分配不同的图像吗?,ios,swift,mapkit,nsdictionary,mkannotation,Ios,Swift,Mapkit,Nsdictionary,Mkannotation,我对mapkit有点陌生,但我想知道,我能为地图上的每个pin设置不同的图像吗。例如,字典中有用户信息,必须有用户自己的图像,而不是常规的pin图像。如何为以下输出设置viewFor注释方法 [{ email = "user1@gmail.com"; id = jqvDgcBoV9Y4sx1BHCmir5k90dr1; name = User1; profileImageUrl = "<null>"; }, { email = "user2@gmail.com"; id = bqvD

我对mapkit有点陌生,但我想知道,我能为地图上的每个pin设置不同的图像吗。例如,字典中有用户信息,必须有用户自己的图像,而不是常规的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/";
}]
但我想知道,我能为地图上的每个大头针设置不同的图像吗

当然可以。您需要创建自定义注释类型。自定义注释类型将在您将提供给它的实例属性中携带图像信息。这样,当您进入
mapView(uuw0: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
属性,您将知道该做什么

但我想知道,我能为地图上的每个大头针设置不同的图像吗

当然可以。您需要创建自定义注释类型。自定义注释类型将在您将提供给它的实例属性中携带图像信息。这样,当您进入
mapView(uuw0: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
指定给注释。现在map调用要求查看图像的代理,您可以阅读
imageName
属性,您将知道该怎么做。

首先,让我说,我认为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,这样您就可以开始比赛了。我要指出的唯一一点是,您确实希望跟踪哪个网络请求与哪个注释关联(以便在需要时可以取消它)。因此,我将向annotation view类添加一个
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(\uu: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")
    }
}
另一个用于
CustomAnnotation
注释和
imageURL
属性,它将异步获取所需图像:

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(u: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
方法中隐藏的一系列其他问题,特别是:

  • 您的图像大小调整逻辑(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))
            }
        }
    }
    

    你可能想考虑是否需要ActhPosil或类似的东西,但是这里有一些其他的想法。或者,也许更好的做法是,看看或的调整大小例程

    但重要的是,您应该将血腥的调整大小逻辑从
    viewForAnnotation
    中拉出来,放到它自己的例程/库中

  • 您也应该为用户位置使用出列逻辑

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

  • 这没什么大不了的,但我发现这个结构有点笨拙:

    guard !annotation.isKind(of: MKUserLocation.self) else { ... }
    
    因此,我简化了使用
    测试。例如:

    if annotation is MKUserLocation { ... }
    
    这基本上是一样的,但更直观一点

  • 注意,与您的例程一样,上面的例程将调整大小的占位符图像用于注释视图的
    image
    属性。为了将来的读者,让我说这很重要,因为标准注释视图不能优雅地处理
    图像大小的异步更改。您可以修改该类来完成此操作,或者更简单地说,就像我们在这里所做的那样,使用标准大小的占位符图像


  • 注意,请参阅iOS 10.x及更早版本的

    首先,让我说,我认为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,这样您就可以开始比赛了。我要指出的唯一一点是,您确实希望跟踪哪个网络请求与哪个注释关联(以便在需要时可以取消它)。因此,我将向annotation view类添加一个
    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(\uu: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")
        }
    }
    
    另一个用于
    CustomAnnotation
    注释和
    imageURL
    属性,它将异步获取所需图像:

    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(u: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)
        }
    }
    
    注意,我已经