我的注释数据存储在Firebase的数据库中。我发现,只要注释没有自定义视图,我就可以下载10,000个注释的数据并将这些注释添加到地图中,而不会出现很大的延迟。
但是对于我的应用程序,我将需要使用自定义视图,每个注释视图都是由多个图像组成的图像。如果我使用自定义视图(即使自定义视图只是一个UIImage),该应用程序也会冻结,并最终收到错误消息“来自调试器的消息:由于内存问题而终止”。我的应用程序的最小缩放级别为15,因此用户几乎只能看到周围的物体。
我的目标是为用户大概10公里之内的所有注释下载注释数据(我将使用geohashing进行此操作,尽管这不是此问题的重点)。手机上的地图只能查看大约一公里左右的土地。
然后我要么只想
<div class="disc">
或
a) add annotations that are visible on the phone
我希望这些注释在屏幕边界之内立即可见,这样,如果用户在地图上滚动时,他们会立即看到这些注释。
我在视图控制器中拥有此委托函数,该函数确定每个注释的视图,当我对其进行注释时,添加注释会稍有延迟,但不是很多。
b) only load the views for the annotations that are visible.
示例
如果您观看此youtube视频,您会发现注解并不总是可见的,只有在您缩放或移动注解时注解才可见。 https://youtu.be/JWUFD48Od4M
MapViewController
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
}
用户注释
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)
}
}
UserAnnotationView
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])
}
}
MapData
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])
}
}
答案 0 :(得分:2)
从我的代码中可以看出,似乎您没有正确使用reuseIdentifier。
resueIdentifier和使视图出队的目的是永远不要创建更多实际可见的视图(或至少将其最小化)
您可以使用它来获取与您已经创建的类型相同的视图,但是不再可见或不再需要。因此,如果您的自定义视图具有UIImageView和标签以及某些布局,则不会再次创建它,而是重用已经创建的视图。
一旦获得可用的视图,就可以分配从注释更改为注释的属性,而无需创建另一个视图。
这意味着您下载了10,000或100,000个注释都没有关系,为地图创建的视图数永远不会大于屏幕上可见的视图数。
这样,您的代码应如下所示:
Future getCurrentUser() async {
FirebaseUser _user = await FirebaseAuth.instance.currentUser();
print("User: ${_user.displayName ?? "None"}");
return _user;}
答案 1 :(得分:0)
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)
答案 2 :(得分:0)
您所描述的问题是处理大量问题时的常见问题,而且恐怕您的方法无法为您提供帮助。如果/当用户大量使用地图(从最小缩放到最大缩放)时,尤其如此。所有要点将被下载,您将陷入同一问题。注意:如果您选择在用户缩小时删除注释,则称为聚类,这是以下解决方案提供的即用型(也就是不要重新发明轮子)
有关该主题的信息,请参见Mapbox的post,它适用于GL JS,但在您的情况下也适用相同的推理。对于iOS,mapbox发布了clustering api,但我还没有尝试过,但它似乎可以胜任。还有一个广泛的代码示例,您可以从中获得启发。出于明显的原因,我不会在这里复制它,只是最终结果的图片,因此您可以弄清楚这是否是您所需要的。
在github上也有很多代码可以做到这一点,请参见here
在地图框代码中
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>
从“注释”派生的,请参阅here。另请参阅here了解MGLShapeSource原型。
您将必须创建一种方法,以从航点或简而言之实例化这些形状:
let source = MGLShapeSource(identifier: "my points",
shapes: self.createShapes(from:annotations), options: [.clustered: true, .clusterRadius: icon.size.width])
答案 3 :(得分:0)
如果我遵守这项权利,
我们可以忽略数据集大小的问题,因为仅显示它们附近的注释会减少数据集的大小。
对于您要显示的内容,当它们只是注释时,它们可以很好地工作,但是当您为它们提供自定义视图时,它们的显示和消失速度很慢。
通过观看视频,这似乎只是一个动画问题。视图注释正在淡入和淡出。如果您不想那种效果,请关闭该动画。
我无法从代码片段中看出可以指定该动画的位置,但是应该很容易找到它。例如,几个MLG API都具有(动画:布尔值)属性,您可以在其中指定false。