首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Mapbox:只有当注释在屏幕上可见时才添加注释

Mapbox:只有当注释在屏幕上可见时才添加注释
EN

Stack Overflow用户
提问于 2019-11-15 23:41:44
回答 4查看 1.6K关注 0票数 3

我将注释的数据存储在Firebase上的数据库中。我发现,只要注释没有自定义视图,我就可以下载10,000个注释的数据,并将这些注释添加到我的地图中,而不会有很大的滞后。

然而,对于我的应用程序,我需要使用自定义视图,每个注释视图都是由多个图像片段组成的图像。如果我使用自定义视图(即使自定义视图只是一个UIImage),应用程序就会结冰,并最终收到“调试器:由于内存问题而终止”的错误消息。我的应用程序的最小缩放级别为15,所以用户大多只能看到他们周围的东西。

我的目标是在离用户10公里的范围内下载所有注释的注释数据(虽然这不是这个问题的重点,但我将使用geohashing来实现这一点)。手机上的地图只能看到一段大约一公里的土地。

我要么只想

代码语言:javascript
复制
 a) add annotations that are visible on the phone 

代码语言:javascript
复制
b) only load the views for the annotations that are visible.

不过,我希望只要批注在屏幕的边界内就可以看到,这样,如果用户正在滚动地图,他们就会立即看到这些注释。

我的视图控制器中有这个委托函数,它决定了每个注释的视图,当我将其注释掉时,添加注释时会出现轻微的延迟,但并不是很多。

代码语言:javascript
复制
func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {
    if annotation is MGLUserLocation && mapView.userLocation != nil {
        let view = CurrentUserAnnoView(reuseIdentifier: currentUser.uid!)
        self.currentUserAnno = view
        return view
    }
    else if annotation is UserAnnotation{
        let anno = annotation as! UserAnnotation
        let auid = anno.reuseIdentifier //The anno uid
        if let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: auid) {
            return annotationView
        } else {
            let annotationView = UserAnnotationView(reuseIdentifier: auid, size: CGSize(width: 45, height: 45), annotation: annotation)
            annotationView.isUserInteractionEnabled = true
            anno.view = annotationView
            return annotationView
        }
    }
    return MGLAnnotationView(annotation: annotation, reuseIdentifier: "ShouldntBeAssigned")  //Should never happen
}

示例

如果你看这段youtube视频,你会发现注释并不总是可见的,只有当你缩放或移动它们时,它们才会变得可见。

https://youtu.be/JWUFD48Od4M

MapViewController

代码语言:javascript
复制
class MapViewController: UIViewController {

    @IBOutlet weak var newPostView: NewPostView!
    @IBOutlet var mapView: MGLMapView!
    var data: MapData?
    var currentUserAnno: CurrentUserAnnoView?
    var testCounter = 0

    let geoFire = GeoFire(firebaseRef: Database.database().reference().child("/users/core"))

    @IBAction func tap(_ sender: UITapGestureRecognizer) {
        self.view.endEditing(true)

    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        geoFire.setLocation(CLLocation(latitude: 37.7853889, longitude: -122.4056973), forKey: "7")
        self.startup()
    }

    func startup(){
        if CLLocationManager.isOff(){
            let popup = UIAlertController(title: "Location Services are Disabled", message: "Please enable location services in your 'Settings -> Privacy' if you want to use this app", preferredStyle: UIAlertController.Style.alert)
            popup.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: {(alert: UIAlertAction) in
                self.startup()
            }))
            popup.view.layoutIfNeeded()
            self.present(popup, animated: true, completion: nil)
        }else{
            self.mapView.userTrackingMode = .follow
            self.data = MapData(delegate: self)
        }
    }

    @IBAction func newHidea(_ sender: Any) {
        newPostView.isHidden = false
    }


}

extension MapViewController: MGLMapViewDelegate{

    func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {
        print(testCounter)
        testCounter = testCounter + 1
        if annotation is MGLUserLocation && mapView.userLocation != nil {
            let view = CurrentUserAnnoView(reuseIdentifier: currentUser.uid!)
            self.currentUserAnno = view
            return view
        }
        else if annotation is UserAnnotation{
            let anno = annotation as! UserAnnotation
//            let auid = anno.reuseIdentifier //The anno uid
            if let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "UserAnnotationView") {
                return annotationView
            } else {
                let annotationView = UserAnnotationView(reuseIdentifier: "UserAnnotationView", size: CGSize(width: 45, height: 45), annotation: annotation)
                annotationView.isUserInteractionEnabled = true
                //anno.view = annotationView
                return annotationView
            }
        }
        return MGLAnnotationView(annotation: annotation, reuseIdentifier: "ShouldntBeAssigned")  //Should never happen
    }


    func mapView(_ mapView: MGLMapView, calloutViewFor annotation: MGLAnnotation) -> MGLCalloutView? {
    /*The regular anno status box is replaced by one with buttons*/
        let annotationPoint = mapView.convert(annotation.coordinate, toPointTo: nil)
        let viewFrame = CGRect(origin: CGPoint(x: 0, y: -10), size: CGSize(width: 180, height: 400))
        var cView: AnnoCalloutView
        if (annotation as! UserAnnotation).status != nil{
            cView =  StatusCallout(representedObject: annotation, frame: viewFrame, annotationPoint: annotationPoint)
        }else{
            cView = ProfileCallout(representedObject: annotation, frame: viewFrame, annotationPoint: annotationPoint)
        }
        return cView
    }

    func mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {
        if (annotation is UserAnnotation) {
            return true
        }else{
            return false
        }

    }

    func mapView(_ mapView: MGLMapView, tapOnCalloutFor annotation: MGLAnnotation) {
        mapView.deselectAnnotation(annotation, animated: true)  // Hide the callout.
    }



}

//TODO: Check if there's a better method than a delegate to do this, since it's Model -> Controller
extension MapViewController: MapDataDelegate{
    func addAnnotation(_ anno: UserAnnotation) {
        self.mapView?.addAnnotation(anno)
    }
}

UserAnnotation

代码语言:javascript
复制
class UserAnnotation: NSObject, MGLAnnotation {

    //////////Ignore these, required for MGLAnnotation//////
    var title: String?
    var subtitle: String?
    ////////////////////////////////////////////////////////

    var coordinate: CLLocationCoordinate2D
    var status: Status?{
        didSet{
            //TODO: update annotation
        }
    }
    var reuseIdentifier: String
    var avatar: Avatar
    var uid: String

    //MARK: You could assign these when the profile is viewed once, so if they view it again you have it saved.
    var uName: String?
    var bio: String?

    init(coordinate: CLLocationCoordinate2D, avatar: Avatar, reuseIdentifier: String?, uid: String) {
//    init(coordinate: CLLocationCoordinate2D, reuseIdentifier uid: String?) {
        self.coordinate = coordinate
        self.title = "None"
        self.subtitle = "None"
        self.reuseIdentifier = reuseIdentifier!
        self.uid = uid
        self.avatar = avatar
        super.init()
//        self.setAvatar(avatar: avatar)
    }

    init(coordinate: CLLocationCoordinate2D, title: String?, subtitle: String?){
        print("This shouldn't be printing")
        self.coordinate = coordinate
        self.uName = "ShouldntBeSet"
        self.title = "ShouldntBeSet"
        self.subtitle = "ShouldntBeSet"
        self.reuseIdentifier = "ShouldntBeAssigned"
        self.uid = "ShouldntBeAssigned"
        self.avatar = Avatar(withValues: [0])
    }
}

UserAnnotationView

代码语言:javascript
复制
class UserAnnotationView: MGLAnnotationView {

    var anno: UserAnnotation?
    var statusView: UITextView?
    var imageView: UIImageView?
    var avatarImage: UIImage{
        let ai = AvatarImage()
        ai.update(with: (anno?.avatar.values)!)
        return ai.image!
    }


    init(reuseIdentifier: String, size: CGSize, annotation: MGLAnnotation) {
        super.init(reuseIdentifier: reuseIdentifier)
        // Prevents view from changing size when view tilted
        scalesWithViewingDistance = false
        frame = CGRect(x: 0, y: 0, width: size.width, height: size.height)
        self.anno = annotation as? UserAnnotation
        self.setUpImageView(frame: frame, size: size, annotation: annotation)
        if anno?.status != nil{
            self.createStatus(status: (anno?.status?.status)!)
        }
    }

    func reuseWithDifferentAnno(annotation: UserAnnotation){
        self.anno = annotation
        self.imageView!.image = UIImage(named: "Will")
        //        let av = AvatarImage.newAvatar(values: (anno?.avatar.values)!)
//        self.imageView!.image = av.image
//        if anno?.status != nil{
//            self.createStatus(status: (anno?.status?.status)!)
//        }else{
//            if statusView != nil{
//                deleteStatus()
//            }
//        }
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    private func setUpImageView(frame: CGRect, size: CGSize, annotation: MGLAnnotation){
        self.imageView = UIImageView(frame: frame)
        self.imageView!.translatesAutoresizingMaskIntoConstraints = false
        if annotation is UserAnnotation {
//            let av = AvatarImage.newAvatar(values: (anno?.avatar.values)!)
//            self.imageView!.image = av.image
            self.imageView!.image = UIImage(named: "Will")

        }else{
            let image = UIImage()
            self.imageView!.image = image
        }
        addSubview(self.imageView!)
        imageViewConstraints(imageView: self.imageView!, size: size)
    }

    func setImage(to image: UIImage){
        self.imageView!.image = image
    }

    func createStatus(status: String){
        if (status == self.statusView?.text) && (self.subviews.contains(self.statusView!)){
            return
        }else if self.statusView != nil && self.subviews.contains(self.statusView!){
            deleteStatus()
        }
        self.statusView = UITextView()
        self.statusView!.text = status
        self.statusView!.isHidden = false
        self.adjustUITextViewHeight()
        self.statusView!.translatesAutoresizingMaskIntoConstraints = false
        self.statusView!.layer.cornerRadius = 5
        self.statusView!.textAlignment = .center
        addSubview(self.statusView!)
        textViewConstraints(textView: self.statusView!, isAbove: self.imageView!)
    }

    func deleteStatus(){
        self.statusView?.removeFromSuperview()
        self.statusView = nil
    }

    private func adjustUITextViewHeight(){

        self.statusView!.translatesAutoresizingMaskIntoConstraints = true
        self.statusView!.sizeToFit()
        self.statusView!.isScrollEnabled = false
    }

    private func imageViewConstraints(imageView: UIImageView, size: CGSize){
        let widCon = NSLayoutConstraint(item: imageView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: size.width)
        let heightCon = NSLayoutConstraint(item: imageView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: size.height)
        let cenCon = NSLayoutConstraint(item: imageView, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0)
        NSLayoutConstraint.activate([cenCon, widCon, heightCon])
    }

    private func textViewConstraints(textView status: UITextView, isAbove imageView: UIImageView){
        let cenCon = NSLayoutConstraint(item: status, attribute: .centerX, relatedBy: .equal, toItem: imageView, attribute: .centerX, multiplier: 1, constant: 0)
        let botCon = NSLayoutConstraint(item: status, attribute: .bottom, relatedBy: .equal, toItem: imageView, attribute: .top, multiplier: 1, constant: -10)
        let widCon = NSLayoutConstraint(item: status, attribute: .width, relatedBy: .lessThanOrEqual, toItem: nil, attribute: .width, multiplier: 1, constant: 200)
        NSLayoutConstraint.activate([cenCon, botCon, widCon])
    }

}

MapData

代码语言:javascript
复制
class MapData {
    var annotations = [String:UserAnnotation]()
    var updateTimer: Timer?
    var delegate: MapDataDelegate

    init(delegate: MapDataDelegate){
        self.delegate = delegate
        self.startTimer()
    }

    @objc func getUsers(){
        FBCore.getAllUsers(completion:{(users) in
            for child in users {
                let value = child.value as! NSDictionary
                self.getDataFor(user: value, whoseUidIs: child.key)
            }
        })
    }

    func getDataFor(user: NSDictionary, whoseUidIs annoid: String){
        if annoid != currentUser.uid! && (currentUser.blockedBy?[annoid] ?? false) != true && (currentUser.blocks?[annoid] ?? false) != true{
            guard let (coord, status, avatar) = FBCoreUser.get(forQryVal: user)
                else {return}
            if let anno = self.annotations[annoid]{
                anno.coordinate = coord
                if status != nil{// && anno.view!.isSelected == false {
                    if ((status?.isExpired)!){
                        anno.status = nil
                    }else{
                        anno.status = status
                    }
                }
                if avatar.values != anno.avatar.values{
                    anno.avatar = avatar
                }
            }else{
                let anno = UserAnnotation(coordinate: coord, avatar: avatar, reuseIdentifier: "UserAnnotation", uid: annoid)
                if status != nil{
                    if ((status?.isExpired)!){
                        anno.status = nil
                    }else{
                        anno.status = status
                    }
                }
                self.annotations[annoid] = anno
                //print(anno.reuseIdentifier)
                delegate.addAnnotation(anno)
            }
        }
    }

    func startTimer(){
        // Scheduling timer to Call the function "updateCounting" with the interval of 5 seconds
        if updateTimer != nil{
            updateTimer!.invalidate()
        }
        updateTimer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(getUsers), userInfo: nil, repeats: true)
    }
}
EN

回答 4

Stack Overflow用户

发布于 2019-11-27 04:02:06

从您的代码中可以看出,您似乎没有正确地使用reuseIdentifier。

resueIdentifier和去队列视图的目标是永远不要创建更多的实际可见的视图(或者至少最小化它)。

您可以使用它获取与您已经创建的视图相同的视图,但这些视图是不可见的或不再需要的。因此,如果您的自定义视图有一个UIImageView、一个标签和一些布局,您将不再创建它,而是重用已经创建的视图。

一旦您获得了一个可用的视图,就可以将从批注更改到注释的属性分配给注释,而无需创建另一个视图。

这意味着,不管您下载了10,000或10,000个注释,为地图创建的视图数永远不会超过屏幕上可见的视图数。

这么说,您的代码应该如下所示:

代码语言:javascript
复制
func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {
    if annotation is MGLUserLocation && mapView.userLocation != nil {
        let view = CurrentUserAnnoView(reuseIdentifier: "userLocation")
        self.currentUserAnno = view
        return view
    }
    else if annotation is UserAnnotation{
        let anno = annotation as! UserAnnotation
        let reuseIdentifier = "myCustomAnnotationView"
        if let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseIdentifier) {
        // set view properties for current annotation before returing
            annotationView.image = anno.image
            annotationView.name = anno.name // etc.
            return annotationView
        } else {
            let annotationView = UserAnnotationView(reuseIdentifier: reuseIdentifier, size: CGSize(width: 45, height: 45), annotation: annotation)
            annotationView.isUserInteractionEnabled = true
//          anno.view = annotationView // this is strange, not sure if it makes sense
            annotationView.image = anno.image // set annotation view properties
            annotationView.name = anno.name // etc.
            return annotationView
        }
    }
    return MGLAnnotationView(annotation: annotation, reuseIdentifier: "ShouldntBeAssigned")  //Should never happen
}
票数 2
EN

Stack Overflow用户

发布于 2019-11-22 00:50:38

Mapbox有确实更改的委托方法,并将更改区域(请选择)。

代码语言:javascript
复制
func mapView(_ mapView: MGLMapView, regionDidChangeAnimated animated: Bool)

当区域发生变化时,您需要将地图上的注释设置为该区域内的注释。最简单的方法似乎是将坐标转换为mapView的空间,然后检查它们是否在mapView的边界内。

代码语言:javascript
复制
let newAnnotations = allAnnotations.filter { annotation in
  let point = mapView.convert(annotation.coordinate, toPointTo: mapView)
  return mapView.bounds.contains(point)
}
if let existingAnnotations = mapView.annotations {
  mapView.removeAnnotations(existingAnnotations)
}
mapView.addAnnotations(newAnnotations)
票数 0
EN

Stack Overflow用户

发布于 2019-11-22 12:32:21

在处理大量问题时,您描述的问题是一个常见的问题,我担心您的方法不会对您有所帮助。如果/当用户大量使用您的地图,从min放大到最大缩放时,情况尤其如此。所有的积分都会被下载,你会陷入同样的问题。注意:如果您选择在用户缩小时删除注释,这就是所谓的集群,这就是下面的解决方案所提供的,即开箱即用(即,a不要重新发明轮子)

请参阅该主题的帖子 by Mapbox,它适用于GL,但同样的推理也适用于您的情况。对于iOS,mapbox已经发布了一个聚类api,我还没有试过,但它似乎能做到这一点。还有一个广泛的代码示例,您可以从中获得灵感。我不会在这里复制,因为很明显的原因,只是最后结果的一张照片,这样你就可以知道这是否是你所需要的。

在github上也有大量代码可用于此,请参阅这里

在mapbox代码中

代码语言:javascript
复制
func mapView(_ mapView: MGLMapView, didFinishLoading style: MGLStyle) {
let url = URL(fileURLWithPath: Bundle.main.path(forResource: "ports", ofType: "geojson")!)
let source = MGLShapeSource(identifier: "clusteredPorts",
url: url,
options: [.clustered: true, .clusterRadius: icon.size.width])
style.addSource(source)
[...]
}

换行:

代码语言:javascript
复制
let url = URL(fileURLWithPath: Bundle.main.path(forResource: "ports", ofType: "geojson")!)
let source = MGLShapeSource(identifier: "clusteredPorts",
url: url, options: [.clustered: true, .clusterRadius: icon.size.width])

出自:

代码语言:javascript
复制
let source = MGLShapeSource(identifier: "my points",
shapes: shapes, options: [.clustered: true, .clusterRadius: icon.size.width])

其中shapes是MGLShape,它是从您的路径点创建的。MGLShape是从使用MGLShape : NSObject <MGLAnnotation, NSSecureCoding>的注释派生出来的,参见这里。还请参阅这里中的MGLShapeSource原型。

您必须创建一个方法,从您的路径点实例化这些形状,或者简单地说:

代码语言:javascript
复制
let source = MGLShapeSource(identifier: "my points",
shapes: self.createShapes(from:annotations), options: [.clustered: true, .clusterRadius: icon.size.width])
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/58886085

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档