swift 使用UIViewRepresentable时的注解聚类中断

t40tm48m  于 2023-06-28  发布在  Swift
关注(0)|答案(2)|浏览(119)

我有一个正在使用SwiftUI UIViewRepresentable处理的MKMapView。问题是我的集群无法按预期工作。我的代码产生了以下一系列问题。

  • Map加载,可群集注解按预期群集。
  • 放大群集时会将它们解群集。
  • 缩小时,它们会重新聚集,但是如果我进一步缩小,附加的注解不会与组聚集。
  • 在遇到这个bug后再放大,将不会导致任何类型的聚类。

集群标注视图

final class ClusterLocationAnnotationView: MKAnnotationView {
        static let identifier = "cluster-location-annotation-identifier"

        // Omitted irrelevant `UILabel` and other views. 
        
        override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
            super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
            displayPriority = .defaultHigh
            collisionMode = .rectangle
            clusteringIdentifier = "location-detail-cluster-id"
            canShowCallout = false
            setupUI()
        }
        
        // Removed Required Init
        
        private func setupUI() {
            addSubview(backgroundView)
            addSubview(label)
            addSubview(offersLabel)
            
            label.translatesAutoresizingMaskIntoConstraints = false
            NSLayoutConstraint.activate([
                label.centerXAnchor.constraint(equalTo: backgroundView.centerXAnchor),
                label.centerYAnchor.constraint(equalTo: backgroundView.centerYAnchor)
            ])
            
            offersLabel.translatesAutoresizingMaskIntoConstraints = false
            NSLayoutConstraint.activate([
                offersLabel.topAnchor.constraint(equalTo: backgroundView.bottomAnchor, constant: 4), // adjust the constant as needed
                offersLabel.centerXAnchor.constraint(equalTo: backgroundView.centerXAnchor)
            ])
        }
        
        // MARK: - Configure Cluster
        func configure(appConfig: AppConfig, clusterLocations: [LocationDetail]) {
            backgroundView.backgroundColor = UIColor(cgColor: appConfig.secondaryColor.cgColor ?? UIColor.black.cgColor)
            backgroundView.layer.cornerRadius = appConfig.globalCornerRadii
            
            label.text = clusterLocations.count.description
            label.textColor = UIColor(cgColor: appConfig.secondaryColor.contrastingColor().cgColor ?? UIColor.white.cgColor)
            
            // Set number of locationDetails that contain an offer, to the text, eg. offersLabel.text = locationsWithOffersCount
            var offerCount = 0
            
            for location in clusterLocations {
                offerCount += appConfig.hasOffer(for: location) ? 1 : 0
            }
            
            offersLabel.text = "\(offerCount) Offer(s) Available"
        }

注解视图

final class LocationAnnotationView: MKAnnotationView {
        static let identifier = "location-annotation-identifier"

        // Removed irrelevant views.
        
        override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
            super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
            displayPriority = .defaultHigh
            collisionMode = .rectangle
            clusteringIdentifier = "location-detail-cluster-id"
            canShowCallout = true
            setupUI()
        }
        
        //Removed Required INIT
        
        private func setupUI() {
            addSubview(backgroundView)
            addSubview(imageView)
            addSubview(label)

            backgroundView.translatesAutoresizingMaskIntoConstraints = false
            NSLayoutConstraint.activate([
                backgroundView.centerXAnchor.constraint(equalTo: self.centerXAnchor),
                backgroundView.centerYAnchor.constraint(equalTo: self.centerYAnchor),
                backgroundView.widthAnchor.constraint(equalToConstant: 40),
                backgroundView.heightAnchor.constraint(equalToConstant: 40)
            ])

            imageView.translatesAutoresizingMaskIntoConstraints = false
            NSLayoutConstraint.activate([
                imageView.centerXAnchor.constraint(equalTo: backgroundView.centerXAnchor),
                imageView.centerYAnchor.constraint(equalTo: backgroundView.centerYAnchor),
                imageView.widthAnchor.constraint(equalToConstant: 30),
                imageView.heightAnchor.constraint(equalToConstant: 30)
            ])

            label.translatesAutoresizingMaskIntoConstraints = false
            NSLayoutConstraint.activate([
                label.topAnchor.constraint(equalTo: backgroundView.bottomAnchor, constant: 4),
                label.centerXAnchor.constraint(equalTo: backgroundView.centerXAnchor)
            ])
        }

        func configure(with location: LocationDetail, and category: Category, appConfig: AppConfig) {
            backgroundView.backgroundColor = UIColor(cgColor: appConfig.secondaryColor.cgColor ?? UIColor.black.cgColor)
            backgroundView.layer.cornerRadius = appConfig.globalCornerRadii
            
            imageView.image = Iconoir.uiImage(from: category.iconName)?.withRenderingMode(.alwaysTemplate)
            imageView.tintColor = UIColor(cgColor: appConfig.secondaryColor.contrastingColor().cgColor ?? UIColor.white.cgColor)
            
            self.label.text = location.locationName
        }
    }

协调器MKMapViewDelegate函数

func mapView(_ mapView: MKMapView, clusterAnnotationForMemberAnnotations memberAnnotations: [MKAnnotation]) -> MKClusterAnnotation {
            return MKClusterAnnotation(memberAnnotations: memberAnnotations)
        }
        
        func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
            let appConfig = parent.appConfig
            
            if let cluster = annotation as? MKClusterAnnotation {
                let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: ClusterLocationAnnotationView.identifier, for: cluster) as? ClusterLocationAnnotationView
                annotationView?.frame.size.height = 40
                annotationView?.frame.size.width = 40
                annotationView?.configure(appConfig: appConfig, clusterLocations: cluster.memberAnnotations as! [LocationDetail])
                return annotationView
            } else if let location = annotation as? LocationDetail {
                let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: LocationAnnotationView.identifier, for: annotation) as? LocationAnnotationView
                annotationView?.frame.size.height = 40
                annotationView?.frame.size.width = 40
                let category = appConfig.category(from: location.categoryId)
                annotationView?.configure(with: location, and: category, appConfig: appConfig)
                return annotationView
            }
            
            return nil
        }

更新UIView

func updateUIView(_ mapView: MKMapView, context: Context) {
        let coordinator = context.coordinator
        mapView.mapType = mapType
        mapView.userTrackingMode = trackingMode
        handleLocationChange(mapView, coordinator: coordinator)
    }
    
    func handleLocationChange(_ mapView: MKMapView, coordinator: Coordinator) {
        if coordinator.lastLocations.count != locations.count {
            mapView.removeAnnotations(mapView.annotations)
            mapView.addAnnotations(locations)
            mapView.setRegion(getRegion(for: locations), animated: true)
            
            coordinator.lastLocations = locations
            DispatchQueue.main.async {
                trackingMode = .none
            }
        }
    }
r7s23pms

r7s23pms1#

handleLocationChange需要被移动到协调器中,以便可以在Map区域更改事件中调用它。
也可能删除位置计数检查,它看起来容易出错。

rnmwe5a2

rnmwe5a22#

有些问题需要解决。首先,我在我的Coordinator上子类化了MKMapView,我不认为这是真正需要的,因为我可以访问updateUIView中的mapView示例。
我需要更新我的makeUIView,以根据更新的状态正确更新。以前,我错误地处理了我的@State,并从makeUIViewUIViewRepresentable结构更新了@State。这里的一般规则是,任何从UIViewRepresentable更新的值,也就是从SwiftUI发布的State值,都应该更新Coordinator上的值。Coordinator更新的任何值都应该发布到UIViewRepresentable结构中的State变量。这将反过来导致调用updateUIView(..)函数,用新更新的State值更新Coordinator的属性值。

func updateUIView(_ mapView: MKMapView, context: Context) {
        mapView.mapType = mapType
        mapView.userTrackingMode = trackingMode
        mapView.removeAnnotations(mapView.annotations)
        mapView.addAnnotations(locations)
    }

为了解决MKAnnotation集群问题,我检查了clusteringIdentifier的代码,我在LocationDetail上设置了clusteringIdentifier,并在LocationAnnotationViewClusterLocationAnnotationView上设置了clusteringIdentifier,因此我删除了该值,仅在调用viewForAnnotation(:)时使用它。

func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
            let appConfig = parent.appConfig
            
            if let cluster = annotation as? MKClusterAnnotation {
                let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: ClusterLocationAnnotationView.identifier, for: cluster) as? ClusterLocationAnnotationView
                annotationView?.frame.size.height = 40
                annotationView?.frame.size.width = 40
                annotationView?.configure(appConfig: appConfig, clusterLocations: cluster.memberAnnotations as! [LocationDetail])
                annotationView?.clusteringIdentifier = "clustering-location-detail-identifier"
                return annotationView
            } else if let location = annotation as? LocationDetail {
                let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: LocationAnnotationView.identifier, for: annotation) as? LocationAnnotationView
                annotationView?.frame.size.height = 40
                annotationView?.frame.size.width = 40
                let category = appConfig.category(from: location.categoryId)
                annotationView?.configure(with: location, and: category, appConfig: appConfig)
                annotationView?.clusteringIdentifier = "clustering-location-detail-identifier"
                return annotationView
            }
            
            return nil
        }

相关问题