我将注释的数据存储在Firebase上的数据库中。我发现,只要注释没有自定义视图,我就可以下载10,000个注释的数据,并将这些注释添加到我的地图中,而不会有很大的滞后。
然而,对于我的应用程序,我需要使用自定义视图,每个注释视图都是由多个图像片段组成的图像。如果我使用自定义视图(即使自定义视图只是一个UIImage),应用程序就会结冰,并最终收到“调试器:由于内存问题而终止”的错误消息。我的应用程序的最小缩放级别为15,所以用户大多只能看到他们周围的东西。
我的目标是在离用户10公里的范围内下载所有注释的注释数据(虽然这不是这个问题的重点,但我将使用geohashing来实现这一点)。手机上的地图只能看到一段大约一公里的土地。
我要么只想
a) add annotations that are visible on the phone 或
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
}示例
如果你看这段youtube视频,你会发现注释并不总是可见的,只有当你缩放或移动它们时,它们才会变得可见。
MapViewController
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
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
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)
}
}发布于 2019-11-27 04:02:06
从您的代码中可以看出,您似乎没有正确地使用reuseIdentifier。
resueIdentifier和去队列视图的目标是永远不要创建更多的实际可见的视图(或者至少最小化它)。
您可以使用它获取与您已经创建的视图相同的视图,但这些视图是不可见的或不再需要的。因此,如果您的自定义视图有一个UIImageView、一个标签和一些布局,您将不再创建它,而是重用已经创建的视图。
一旦您获得了一个可用的视图,就可以将从批注更改到注释的属性分配给注释,而无需创建另一个视图。
这意味着,不管您下载了10,000或10,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
}发布于 2019-11-22 00:50:38
Mapbox有确实更改的委托方法,并将更改区域(请选择)。
func mapView(_ mapView: MGLMapView, regionDidChangeAnimated animated: Bool)当区域发生变化时,您需要将地图上的注释设置为该区域内的注释。最简单的方法似乎是将坐标转换为mapView的空间,然后检查它们是否在mapView的边界内。
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)发布于 2019-11-22 12:32:21
在处理大量问题时,您描述的问题是一个常见的问题,我担心您的方法不会对您有所帮助。如果/当用户大量使用您的地图,从min放大到最大缩放时,情况尤其如此。所有的积分都会被下载,你会陷入同样的问题。注意:如果您选择在用户缩小时删除注释,这就是所谓的集群,这就是下面的解决方案所提供的,即开箱即用(即,a不要重新发明轮子)
请参阅该主题的帖子 by Mapbox,它适用于GL,但同样的推理也适用于您的情况。对于iOS,mapbox已经发布了一个聚类api,我还没有试过,但它似乎能做到这一点。还有一个广泛的代码示例,您可以从中获得灵感。我不会在这里复制,因为很明显的原因,只是最后结果的一张照片,这样你就可以知道这是否是你所需要的。

在github上也有大量代码可用于此,请参阅这里
在mapbox代码中
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)
[...]
}换行:
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])出自:
let source = MGLShapeSource(identifier: "my points",
shapes: shapes, options: [.clustered: true, .clusterRadius: icon.size.width])其中shapes是MGLShape,它是从您的路径点创建的。MGLShape是从使用MGLShape : NSObject <MGLAnnotation, NSSecureCoding>的注释派生出来的,参见这里。还请参阅这里中的MGLShapeSource原型。
您必须创建一个方法,从您的路径点实例化这些形状,或者简单地说:
let source = MGLShapeSource(identifier: "my points",
shapes: self.createShapes(from:annotations), options: [.clustered: true, .clusterRadius: icon.size.width])https://stackoverflow.com/questions/58886085
复制相似问题