【问题标题】:Mapbox image markersMapbox 图像标记
【发布时间】:2019-11-27 17:38:36
【问题描述】:

我正在构建一个 iOS 应用程序,其中涉及使用不同的 MapBox 图像标记来说明不同餐厅的位置。

这样(接受我想要多张图片):https://docs.mapbox.com/ios/assets/maps-sdk-image-annotation-example-480-8ce20af3475bd6b27381fda012a5b10b.webp

我目前在做什么:

我目前正在输入我当地城镇周围 10 家不同餐厅的坐标,并在屏幕上显示一个徽标(来自 MapBox 的比萨徽标)。

func mapView(_ mapView: MGLMapView, imageFor annotation: MGLAnnotation) -> MGLAnnotationImage? {
        // Try to reuse the existing ‘pisa’ annotation image, if it exists.
        var annotationImage = mapView.dequeueReusableAnnotationImage(withIdentifier: "pisa")

        if annotationImage == nil {
            // Leaning Tower of Pisa by Stefan Spieler from the Noun Project.
            var image = UIImage(named: "pisavector")!

            image = image.withAlignmentRectInsets(UIEdgeInsets(top: 0, left: 0, bottom: image.size.height/2, right: 0))

            // Initialize the ‘pisa’ annotation image with the UIImage we just loaded.
            annotationImage = MGLAnnotationImage(image: image, reuseIdentifier: "pisa")
        }

        return annotationImage
    }

我想要达到的目标:

我想将单独的坐标和图像存储在 firebase 的数据库中,这样当我获得一家新餐厅时,我可以在 firebase 上添加坐标和图像,这样就可以在不更新地图的情况下更新地图。

我需要什么帮助:

我需要帮助创建一个函数,该函数允许我输入每个餐厅的坐标数组和图像 URL,并像上面的链接一样显示。

欢迎任何帮助:)

********编辑******** 使用 Mapbox GEO JSON 是否可以实现这一点

【问题讨论】:

    标签: ios swift mapbox mapbox-gl-js mapbox-gl


    【解决方案1】:

    我用func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? 完成了类似的事情。作为参考,这是我的 Mapbox 实现的外观:

    我创建了一个名为 EventImageView 的自定义 UIView,它有 2 个 UIImageViews,一个显示一个 pin,而另一个在 pin 上放置一个图标图像来传达事件是什么(对你来说,这可能是一个快餐的薯条图标,或亚洲餐厅的面条。这里还有一些功能可以使图钉动画到位,从CGSize.zero 的大小扩展到适当的图钉大小。

    import Foundation
    import Mapbox
    import MaterialComponents
    
    // MGLAnnotationView subclass
    class CustomEventAnnotationView: MGLAnnotationView {
    
        //The parent view controller (in this case always a mapVC)
        var parentVC: MapViewController!
        //The imageview that has the colored pin
        var pinImageView = UIImageView()
        //The image view above the pin image view that shows us the category icon
        var categoryImageView = UIImageView()
        //The activity indicator for when the categoryImageView is loading
        var activityIndicator = MDCActivityIndicator()
        var categoryImage: UIImage!
        var hasLoaded = false
    
        override func layoutSubviews() {
            //Set the offset
            centerOffset = CGVector(dx: 0, dy: -20)
    
            //Set the frames of the subviews
            pinImageView.frame = CGRect(x: self.bounds.midX, y: self.bounds.maxY, width: 0, height: 0)
            categoryImageView.frame = pinImageView.frame
            activityIndicator.frame = CGRect(x: categoryImageView.frame.minX + 8, y: categoryImageView.frame.minY - 16, width: categoryImageView.frame.width - 15, height: categoryImageView.frame.height - 15)
            activityIndicator.isHidden = true
            activityIndicator.cycleColors = [UIColor.hootLightBlue]
            addSubview(activityIndicator)
    
            //Run growpin, which animates the pin growing from invisible to full size
            growPin()
        }
    
        override func prepareForReuse() {
            hasLoaded = false
        }
    
        func loadPinImageView(with imageNamed: String) {
            pinImageView.image = UIImage(named: imageNamed)
            self.addSubview(pinImageView)
        }
    
        func loadCategoryImageViewFromPreload(with image: UIImage) {
            categoryImageView.image = image
            self.addSubview(categoryImageView)
        }
    
        //Loads the category image view from a request to the url (this is called if the image hasn't already been preloaded)
        func loadCategoryImageViewFromRequest(with url: String) {
            if categoryImage == nil {
                //Social, job, commerce, and misc have their icons loaded onto the device, so we will pass their names in the url parameter
                switch url {
                case "Social":
                    self.categoryImageView.image = UIImage(named: "social")
                case "Job":
                    self.categoryImageView.image = UIImage(named: "job")
                case "Commerce":
                    self.categoryImageView.image = UIImage(named: "commerce")
                case "Misc":
                    self.categoryImageView.image = UIImage(named: "misc")
                default:
                    //If the category is neither social, job, commerce, nor misc, then we show and animate the activity indicator...
                    activityIndicator.isHidden = false
                    activityIndicator.startAnimating()
                    let task = URLSession.shared.dataTask(with: URL(string: url)!) {(data, response, error) in
                        //...request the image from the respective url
                        //Load the image from the data
                        let image: UIImage = UIImage(data: data!)!
                        DispatchQueue.main.async {
                            //Then set the image we received then hide the activityIndicator on the main thread
                            self.categoryImageView.image = image
                            self.activityIndicator.stopAnimating()
                            self.activityIndicator.isHidden = true
                        }
                    }
                    task.resume()
                }
            }
        }
    
        func growPin() {
            //This is the animation that grows the views from a size of CGPoint(height: 0, width: 0) to their respective heights
            self.addSubview(self.categoryImageView)
            UIView.animate(withDuration: 0.25) {
                self.pinImageView.frame = self.bounds
                self.categoryImageView.frame = CGRect(x: self.bounds.minX + 11, y: self.bounds.minY + 6, width: self.bounds.width - 22, height: self.bounds.width - 22)
            }
        }
    
        func shrinkPin(completion:@escaping (() -> Void)) {
            //Opposite of growPin, not called in code as of yet
            UIView.animate(withDuration: 0.25, animations: {
                self.pinImageView.frame = CGRect(x: self.bounds.midX, y: self.bounds.maxY, width: 0, height: 0)
                self.categoryImageView.frame = CGRect(x: self.bounds.midX, y: self.bounds.maxY, width: 0, height: 0)
            }) { (bool) in
                completion()
            }
        }
    }
    

    我还对MGLAnnotation 进行了子类化,以制作具有存储Event 属性的自定义注释。您可以在自定义注释中使用自定义餐厅对象。

    import Mapbox
    class EventAnnotation: NSObject, MGLAnnotation {
    
        // As a reimplementation of the MGLAnnotation protocol, we have to add mutable coordinate and (sub)title properties ourselves.
        var coordinate: CLLocationCoordinate2D
        var title: String?
        var subtitle: String?
        var bundlePoint: CLLocationCoordinate2D?
    
        var event: Event!
    
        init(coordinate: CLLocationCoordinate2D, event: Event, title: String, subtitle: String) {
            self.coordinate = coordinate
            self.event = event
            self.title = title
            self.subtitle = subtitle
        }
    }
    

    然后我在MapViewController 中使用func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? 来创建每个注释视图:

    import UIKit
    import Mapbox
    class MapViewController: UIViewController {
        ...
    }
    
    extension MapViewController: MGLMapViewDelegate {
    
        func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {
            let eventAnnotation = annotation as? EventAnnotation
    
            //All annotation views with the same event category look the same, so we reuse them. 
            //If you have some sort of identifier for the type of restaurant each annotation represents, you'll want to use this restaurant identifier as your reuse identifier (assuming all pins for, say, Asian restaurants look the same)
            var reuseIdentifier: reuseIdentifier = eventAnnotation?.event.categories.first ?? ""
    
            // For better performance, always try to reuse existing annotations.
            var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseIdentifier) as? CustomEventAnnotationView
    
            // If there’s no reusable annotation view available, initialize a new one.
            if annotationView == nil {
                annotationView = CustomEventAnnotationView(reuseIdentifier: reuseIdentifier)
                annotationView!.bounds = CGRect(x: 0, y: 0, width: 40, height: 40)
    
                if let eventAnnotation = annotation as? EventAnnotation {
                    for category in allCategories {
                        if category.id == eventAnnotation.event.categories.first {
    
                            //Return the correct image depending on what kind of event we're dealing with (social, job, commerce, misc)
                            switch category.type! {
                            case .social:
                                annotationView!.loadPinImageView(with:"redPin")
                            case .job:
                                annotationView!.loadPinImageView(with:"bluePin")
                            case .commerce:
                                annotationView!.loadPinImageView(with:"greenPin")
                            case .misc:
                                annotationView!.loadPinImageView(with:"grayPin")
                            }
    
                            //Actually load the image from the main thread
                            DispatchQueue.main.async {
                                if category.preloadedImage != nil {
                                        annotationView!.loadCategoryImageViewFromPreload(with: category.preloadedImage)
                                } else {
                                        annotationView!.loadCategoryImageViewFromRequest(with: category.imageURL ?? category.name)
                                }
                            }
                        }
                    }
                }
    
                //This checks the rotation of the mapView.
                //The z-position of our annotation views need to be adjusted if, say, the top of the map points south and the bottom points north
                if mapView.direction > 90 && mapView.direction < 270 {
                    annotationView?.layer.zPosition = CGFloat(90-(-(eventAnnotation?.coordinate.latitude)!))
                } else {
                    if annotationView != nil && eventAnnotation != nil {
                        annotationView?.layer.zPosition = CGFloat(90-(eventAnnotation?.coordinate.latitude)!)
                    }
                }
                return annotationView
            }
        }
    }
    

    请务必注意,我将自定义 Event 注释添加到我的 MGLMapView。这是通过请求适当的数据来完成的,一旦对事件(或餐厅)数据的异步请求完成,通过以下方式初始化自定义注释:

    someAsyncRequestForEvents { (newEvents) in
        //newEvents is of type [Event]
        var newEventAnnotations: [EventAnnotation] = []
        for newEvent in newEvents {
            let newAnnotation = EventAnnotation(coordinate: CLLocationCoordinate2D(latitude: Double(newEvent.lat), longitude: Double(newEvent.long)), event: newEvent, title: newEvent.id!, subtitle: newEvent.id!)
            newEventAnnotations.append(newEvent)
        }
        mapView.addAnnotations(newEventAnnotations)
    }
    

    很抱歉,如果这是一个冗长的回复。我只知道对于像 Mapbox for iOS 这样的特定框架很难获得关于 SO 的帮助。想我会分享一些代码,希望能让你知道你能做什么。

    【讨论】:

    • 我看看我能不能试试这个,谢谢你的冗长回复:)
    • 嘿大卫,能在 GitHub 上看到你的代码吗?
    • 我很乐意,但源代码中有一些敏感信息,例如身份验证令牌。你遇到了什么困难?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-10-29
    • 2021-04-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-27
    • 1970-01-01
    相关资源
    最近更新 更多