Mapbox:仅当注释在屏幕上可见时才添加注释

2024-02-09

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

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

我的目标是下载用户周围 10 公里范围内的所有注释的注释数据(我将使用 geohashing 来完成此操作,尽管这不是这个问题的重点)。手机上的地图只能查看大约一公里左右的土地。

那么我要么只想

 a) add annotations that are visible on the phone 

or

b) only load the views for the annotations that are visible.

我希望注释在屏幕边界内后立即可见,这样,如果用户滚动地图,他们会立即看到这些注释。


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

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
}

Example

如果您观看此 YouTube 视频,您会发现注释并不总是可见,只有当您缩放或在注释上移动时它们才会变得可见。
https://youtu.be/JWUFD48Od4M https://youtu.be/JWUFD48Od4M


地图视图控制器

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)
    }
}

用户注释

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])
    }
}

用户注释视图

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

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)
    }
}

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

reuseIdentifier 和出队视图的目标是永远不会创建比实际可见的视图更多的视图(或至少将其最小化)

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

一旦您获得可用的视图,您就可以分配注释之间更改的属性,而无需创建另一个视图。

这意味着,无论您下载 10,000 个还是 100,000 个注释,为地图创建的视图数量都不会大于屏幕上可见的视图数量。

也就是说,您的代码应该如下所示:

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
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Mapbox:仅当注释在屏幕上可见时才添加注释 的相关文章

随机推荐

  • 在 Ubuntu 20.04 上设置 ONNX 运行时(C++ API)

    我目前正在尝试让我的图像处理程序在 Ubuntu 来自 Windows 上运行 我已经成功构建并链接了 OpenCV 和 Boost 库以与我的 cpp 程序一起使用 但是我还没有找到任何关于在 Ubuntu 20 04 上设置 Onnx
  • r动画,参数曲线

    我正在使用动画包来绘制参数曲线 x sin t 和 y sin t 2 以及使用以下代码追踪曲线的圆 require animation x lt seq 1 1 length 20 y lt x 2 plot x y type l lib
  • execve("/bin/sh", 0, 0);在管道中

    我有以下示例程序 include
  • 从私有 Docker 注册表中删除镜像的方法

    我设置了一个私有 Docker 注册表 并且我已将其他计算机上的一些映像推送到此注册表 它是一个 V2 注册表 我不知道从存储库中删除图像的新方法 因为这些推送的图像没有在 CLI 中列出 docker 图像 谁能建议我从磁盘中删除这些图像
  • 为 302 重定向到的另一个域设置 cookie

    这个问题比其他任何问题都更像是一个现实检验 我很确定这是可能的 但想确定一下 我正在编写一个代理服务器 它接收 HTTP 请求 将它们传递到远程服务器 然后返回远程服务器的响应 我遇到了一个问题 远程服务器响应之一是设置 cookie 的
  • _popen: 不显示 shell 窗口 (SW_HIDE)

    当我在 c mfc 中执行 popen 命令时 它会打开一个我不喜欢的 shell 窗口 是否可以将其隐藏 例如 当您尝试使用 ShellExecute 函数执行命令时 它可以选择使用 SW HIDE 隐藏 shell 窗口 注释来自文档
  • 如何从 django 的查询集中排除非活动用户

    我想从我的项目中排除非活跃用户 example 1 url users 1 friends will show all friends of that user 我想只显示朋友列表中的活跃用户 example 2 url users 1 f
  • 如何在 iOS 上开始使用 ARM?

    只是好奇如何开始了解 iOS 下的 ARM 任何帮助都会非常好 在我看来 最好的开始方式是 编写 C 代码小片段 后来的 Objective C 查看对应的汇编代码 找出足以理解汇编代码的内容 Repeat 为此 您可以使用 Xcode 创
  • 为什么即使在使用异步等待多个调用后仍然得到空响应?

    这是简单的节点路由 其中 调用异步api 需要做的是return data循环之后 但它返回空白对象 try const array brunch lunch crunch const data array map async d gt c
  • 如果广度优先搜索 (BFS) 可以更快地完成同样的事情,为什么还要使用 Dijkstra 算法呢?

    两者都可用于从单一源查找最短路径 BFS运行在O E V 而 Dijkstra 运行O V E log V 另外 我见过 Dijkstra 在路由协议中的使用很像 因此 如果 BFS 可以更快地完成同样的事情 为什么还要使用 Dijkstr
  • 比较同一目录中的文件

    我们有 20 个文件 名为file txt全部在一个目录中 file1 txt file2 txt file20 txt 在同一目录中 我们还有其他文件 我们需要忽略它们 someotherfile csv somemore txt etc
  • 无法连接到任何指定的 MySQL 主机

    问题就在这里 我正在尝试执行查询及其抛出和异常connection Open 奇怪的是 在同一个应用程序上我正在执行 选择 查询并且它工作正常 但是当我执行 更新 查询时 它会抛出 无法连接到任何指定的 MySQL 主机 错误 一直卡在这上
  • 如何在 nginx 中配置重定向到带有斜杠的 url?

    我想将不带斜杠的 URL 重定向到带尾部斜杠的路径 所以 一些 url to 一些网址 其余的 URL 例如 some url xml 某个网址 some url q v 一些网址 应该保持不变 我找到了这篇文章https www atea
  • Grails 自动重新加载新的控制器操作

    I ve 创建了新的 Grails 2 4 3 项目 created TestController set grails reload enabled true in BuildConfig groovy 运行应用程序grails relo
  • char 数组的问题 = char 数组

    I have char message1 100 char message2 100 当我尝试做的时候message1 message2 我收到错误 分配给类型时不兼容的类型 char 100 从类型 char 我有类似的功能 if sen
  • 用CSS按比例调整图像大小,可能吗?

    有没有办法用 CSS 调整图像大小并保持其比例 容器有固定的宽度和高度 div class container img class theimage src something div 我问的原因是因为布局可以改变 通过类从列表到图标 并且
  • 枚举对继承的支持

    我经常遇到这样的情况 我们创建一个作用于某些枚举的类 但后来我们派生并希望在不更改基类的情况下向枚举添加更多值 我在2009年就看到过这个问题 基枚举类继承 https stackoverflow com questions 644629
  • 使 XStream 忽略一个特定的私有变量

    我目前正在为其编写保存函数的类有一个小问题 我正在使用 XStream com thoughtworks xstream 通过 DOMDriver 将类序列化为 XML 该类看起来像这样 public class World private
  • 如何制作 ToolStripComboBox 来填充 ToolStrip 上的所有可用空间?

    ToolStripComboBox 放置在 ToolStripButton 后面 后面是另一个右对齐的工具条组合框 如何最好地将 ToolStripComboBox 设置为始终调整其长度以填充前后 ToolStripButton 之间的所有
  • Mapbox:仅当注释在屏幕上可见时才添加注释

    我将注释数据存储在 Firebase 的数据库中 我发现只要注释没有自定义视图 我就可以下载 10 000 个注释的数据并将这些注释添加到我的地图中 而不会出现太大的延迟 然而 对于我的应用程序 我需要使用自定义视图 每个注释视图都是由多个