London Escorts sunderland escorts 1v1.lol unblocked yohoho 76 https://www.symbaloo.com/mix/yohoho?lang=EN yohoho https://www.symbaloo.com/mix/agariounblockedpvp https://yohoho-io.app/ https://www.symbaloo.com/mix/agariounblockedschool1?lang=EN
12.6 C
New York
Wednesday, November 27, 2024

ios – GoogleMaps customized markers clustering efficiency subject


I’ve efficiency points with shifting, zooming map with customized markers clusters.
I attempted utilizing greatest practices: keep away from re-render, batch, debounce clusters, all loading and resizing duties made asynchronous and all ui updates in major thread, however nonetheless have lags and snooze map efficiency.
If anybody has any suggestions, I would respect it.

That is MarkerPostItem.swift:

class MarkerPostItem: NSObject, GMUClusterItem {
    var place: CLLocationCoordinate2D
    var publish: PhotoPost
    var picture: PFFileObject
    var imageUrl: URL?
    var uiImage: BehaviorRelay<UIImage?> = .init(worth: nil)
    
    personal let disposeBag = DisposeBag()
    personal let imageLoader: ImageLoaderProtocol = ImageLoader()
    
    init(place: CLLocationCoordinate2D, publish: PhotoPost, picture: PFFileObject) {
        self.place = place
        self.publish = publish
        self.picture = picture
        self.imageUrl = URL(string: picture.url ?? "")
        tremendous.init()
        self.fetchAndResizeImage()
    }
    
    personal func fetchAndResizeImage() {
        imageLoader.loadImage(from: imageUrl, placeholder: UIImage(named: "google-icon")) { [weak self] picture in
            let resizedImage = picture?.resizeImage(to: 150.0)
            self?.uiImage.settle for(resizedImage)
        }
    }
}

That is CustomPostClusterRenderer.swift

class CustomPostClusterRenderer: GMUDefaultClusterRenderer {
    personal let GMUAnimationDuration: Double = 0.2
    personal weak var mapView: GMSMapView?
    
    override init(mapView: GMSMapView, clusterIconGenerator iconGenerator: GMUClusterIconGenerator) {
        tremendous.init(mapView: mapView, clusterIconGenerator: iconGenerator)
        self.mapView = mapView
        self.animationDuration = GMUAnimationDuration
    }
    
    override func shouldRender(as cluster: GMUCluster, atZoom zoom: Float) -> Bool {
        return cluster.rely >= 1
    }
}

That is MapPostClusterIconGenerator.swift:

class MapPostClusterIconGenerator: GMUDefaultClusterIconGenerator {
    override func icon(forSize dimension: UInt) -> UIImage {
        return generateIcon(forSize: dimension)
    }

    personal func generateIcon(forSize dimension: UInt) -> UIImage {
        let iconImage = #imageLiteral(resourceName: "google-icon")
        let textual content = String(dimension) as NSString
        let font = UIFont.Spontivly.Khula.extraBold(16.0)
        return createIconImage(withText: textual content, baseImage: iconImage, font: font) ?? iconImage
    }
    
    personal func createIconImage(withText textual content: NSString, baseImage: UIImage, font: UIFont) -> UIImage? {
        UIGraphicsBeginImageContextWithOptions(baseImage.dimension, false, 0.0)
        defer { UIGraphicsEndImageContext() }
        baseImage.draw(in: CGRect(origin: .zero, dimension: baseImage.dimension))
        let textStyle = NSMutableParagraphStyle()
        textStyle.alignment = .middle
        let attributes: [NSAttributedString.Key: Any] = [
            .font: font,
            .paragraphStyle: textStyle,
            .foregroundColor: UIColor.white
        ]
        let textHeight = font.lineHeight
        let textY = (baseImage.dimension.top - textHeight) / 2
        let textRect = CGRect(x: 10, y: textY, width: baseImage.dimension.width - 20, top: textHeight)
        textual content.draw(in: textRect.integral, withAttributes: attributes)
        return UIGraphicsGetImageFromCurrentImageContext()
    }
}

That is ClusterMarkerView.swift:

class ClusterMarkerView: LoadableView {
    @IBOutlet weak var iconView: UIImageView!
    @IBOutlet weak var quantity: UILabel!
    @IBOutlet weak var numberContainer: UIView!
    @IBOutlet weak var numberContainerConstraint: NSLayoutConstraint!
    
    personal var url: URL?
    personal let disposeBag: DisposeBag = .init()
    
    func replace(quantity: UInt, ratio: CGFloat, uiImage: BehaviorRelay<UIImage?>?) {
        if let uiImage = uiImage {
            uiImage.asDriver().drive(iconView.rx.picture).disposed(by: disposeBag)
        } else {
            self.iconView.sd_cancelCurrentImageLoad()
            loadImage(ratio)
        }
        
        DispatchQueue.major.async {
            self.numberContainer.isHidden = quantity == 1
            self.numberContainerConstraint.fixed = quantity == 1 ? 0.0 : 11.0
            self.quantity.textual content = String(quantity)
            self.layoutIfNeeded()
        }
    }
    
    personal func loadImage(_ ratio: CGFloat) {
        guard let url = url else { return }
        iconView.sd_setImage(with: url,
                             placeholderImage: nil,
                             choices: [.continueInBackground, .scaleDownLargeImages],
                             context: [.imagePreserveAspectRatio : false,
                                       .imageThumbnailPixelSize : CGSize(width: 100, height: (100.0 * ratio))],
                             progress: nil) { picture, error, cacheType, url in
            
            if let error = error {
                print("Error loading picture: (error.localizedDescription)")
            }
        }
    }
}

That is MapViewController.swift delegates:

class MapViewController: UIViewController, NonReusableViewProtocol {
    personal var mapView: GMSMapView!
    personal var alreadyLoadedPosts: Bool = false
    personal var isRenderingCluster: Bool = false
    personal var postMarkers = [String: MarkerPostItem]()
    personal var renderedPostIds = Set<String>()
    personal var renderWorkItem: DispatchWorkItem?
    personal var isMapMoving = false
    
    let algorithm = GMUNonHierarchicalDistanceBasedAlgorithm()
    var clusterImage: PFFileObject?
    var clusterItemCount: UInt = 0
    var firstLoad: Bool = true
    var filterISOpened: Bool = false
    var searchOpened: Bool = false
    var ratio: CGFloat = 0.0
    var clusterUiImage: BehaviorRelay<UIImage?>?
    var postClusterManager: GMUClusterManager?
    var subscriptionChecker = Injected<SubscriptionCheckServiceProtocol>()
    
    lazy personal var currentLocationMarker: GMSMarker = {
        let currentLocationMarker = GMSMarker()
        currentLocationMarker.map = mapView
        currentLocationMarker.icon = UIImage(named: "user_pin_icon")
        return currentLocationMarker
    }()
    
    lazy var renderer: CustomPostClusterRenderer = {
        let iconGenerator = MapPostClusterIconGenerator()
        let renderer = CustomPostClusterRenderer(mapView: mapView, clusterIconGenerator: iconGenerator)
        renderer.delegate = self
        return renderer
    }()

    personal func configurePostMarkerCluster() {
        DispatchQueue.world(qos: .userInitiated).async { [weak self] in
            guard let self = self else { return }
            self.postClusterManager = GMUClusterManager(map: self.mapView, algorithm: self.algorithm, renderer: self.renderer)
            
            DispatchQueue.major.async {
                self.postClusterManager?.setDelegate(self, mapDelegate: self)
                self.postClusterManager?.cluster()
            }
        }
    }

    personal func handleFilteredPosts(_ posts: [PhotoPost]) {
        guard !posts.isEmpty else { return }

        DispatchQueue.world(qos: .userInitiated).async { [weak self] in
            guard let self = self else { return }
            // Set of publish IDs within the present filter outcomes
            let currentPostIds = Set(posts.compactMap { $0.objectId })
            
            // Determine markers to take away (these not within the present filter outcomes)
            let postsToRemove = self.renderedPostIds.subtracting(currentPostIds)
            postsToRemove.forEach { postId in
                if let marker = self.postMarkers[postId] {
                    self.postClusterManager?.take away(marker)
                    self.postMarkers.removeValue(forKey: postId)
                }
            }

            // Replace `renderedPostIds` to match the present filter
            self.renderedPostIds = currentPostIds
            
            // Determine new posts so as to add (these in `currentPostIds` however not but rendered)
            let newPosts = posts.filter { publish in
                guard let postId = publish.objectId else { return false }
                return !self.postMarkers.keys.incorporates(postId)
            }
            
            // Type and add new markers for these posts
            let sortedPosts = newPosts.sorted { ($0.createdAt ?? Date()) > ($1.createdAt ?? Date()) }
            sortedPosts.forEach { publish in
                guard let postId = publish.objectId else { return }
                let lat = publish.location?.latitude ?? 0
                let lng = publish.location?.longitude ?? 0
                let marker = MarkerPostItem(place: CLLocationCoordinate2D(latitude: lat, longitude: lng), publish: publish, picture: publish.photoLow)
                
                self.postMarkers[postId] = marker
                self.postClusterManager?.add(marker)
            }

            // Cluster on the principle thread after updating markers
            DispatchQueue.major.async {
                self.batchCluster()
            }
        }
    }

    personal func updateLocation(_ location: CLLocation) {
        mapView.animate(toLocation: location.coordinate)
    }
    
    personal func updateMapPosition(coordinate: CLLocationCoordinate2D) {
        mapView.animate(toLocation: coordinate)
    }
}

// MARK: - Delegates
extension MapViewController: GMUClusterRendererDelegate, GMUClusterManagerDelegate, GMSMapViewDelegate {
    
    func clusterManager(_ clusterManager: GMUClusterManager, didTap cluster: GMUCluster) -> Bool {
        closeOpenPanelsIfNeeded()
        
        if let gadgets = cluster.gadgets as? [MarkerPostItem] {
            viewModel?.selectedCluster.publish(gadgets)
        }
        return true
    }
    
    func renderer(_ renderer: GMUClusterRenderer, markerFor object: Any) -> GMSMarker? {
        guard let cluster = object as? GMUStaticCluster,
              let postMarker = cluster.gadgets.first as? MarkerPostItem else {
            return nil
        }
        
        return createMarker(for: cluster, with: postMarker)
    }
    
    func renderer(_ renderer: GMUClusterRenderer, willRenderMarker marker: GMSMarker) {
        scheduleClusterRenderReset(marker)
    }
    
    func renderer(_ renderer: GMUClusterRenderer, didRenderMarker marker: GMSMarker) {
        finalizeClusterRendering()
    }
    
    func mapViewDidFinishTileRendering(_ mapView: GMSMapView) {
        guard !alreadyLoadedPosts else { return }
        alreadyLoadedPosts = true
        viewModel?.loadPostsMap()
    }
    
    func mapView(_ mapView: GMSMapView, didChange place: GMSCameraPosition) {
        isMapMoving = true
        debounceMapClusterUpdate(for: place)
    }
    
    func mapView(_ mapView: GMSMapView, idleAt place: GMSCameraPosition) {
        isMapMoving = false
        batchCluster() // Set off clustering when motion stops
    }
}

// MARK: - Personal Helper Strategies
personal extension MapViewController {
    
    /// Shut open UI panels reminiscent of search or filter
    func closeOpenPanelsIfNeeded() {
        if searchOpened { closeSearchButton.sendActions(for: .touchUpInside) }
        if filterISOpened { filterButton.sendActions(for: .touchUpInside) }
    }
    
    /// Create and configure a marker for the given cluster and publish merchandise
    func createMarker(for cluster: GMUStaticCluster, with postMarker: MarkerPostItem) -> GMSMarker? {
        let marker = GMSMarker()
        let view = ClusterMarkerView(body: CGRect(x: 0, y: 0, width: 80, top: 103))
        view.replace(quantity: cluster.rely, ratio: postMarker.publish.imageRatio, uiImage: postMarker.uiImage)
        marker.iconView = view
        marker.groundAnchor = CGPoint(x: 0.5, y: 1)
        return marker
    }
    
    /// Schedule a reset for cluster rendering standing
    func scheduleClusterRenderReset(_ marker: GMSMarker) {
        isRenderingCluster = true
        renderWorkItem?.cancel()
        
        renderWorkItem = DispatchWorkItem { [weak self] in
            guard let self = self else { return }
            self.isRenderingCluster = false
        }
        DispatchQueue.major.asyncAfter(deadline: .now() + 0.5, execute: renderWorkItem!)
    }
    
    /// Finalize cluster rendering by hiding loading indicators
    func finalizeClusterRendering() {
        isRenderingCluster = false
        DispatchQueue.major.asyncAfter(deadline: .now() + 2.0) {
            if !self.isRenderingCluster, self.alreadyLoadedPosts {
                self.loadingView.isHidden = true
                self.activityIndicator.stopAnimating()
                self.loadingOverlayView.isHidden = true
            }
        }
    }
    
    /// Debounce map clustering updates to keep away from frequent recalculations
    func debounceMapClusterUpdate(for place: GMSCameraPosition) {
        let significantZoomChange = abs(mapView.digicam.zoom - place.zoom) >= 1.0
        if significantZoomChange {
            batchCluster()
        }
    }

    personal func batchCluster() {
        // Clusters markers provided that map just isn't at the moment shifting
        guard !isMapMoving else { return }
        postClusterManager?.cluster()
    }
}

Related Articles

Social Media Auto Publish Powered By : XYZScripts.com