简体   繁体   中英

GoogleMaps SDK for iOS - SWIFT 3: When hiding a marker and then adding the map view back, the cpu gets stuck at 100%

Scenario

  • UIViewController sets self.view as GMSMapView in viewDidLload
  • A method will create markers and store them in self.markers and set marker.map to the self.view as! GMSMapView self.view as! GMSMapView

So far the app behaves well

  • Later, another method after some action (looking to toggle those markers) sets all self.markers.map to nil

Up to here all goes well and the markers are gone from the map

  • Again, another method that wants those markers back, sets all self.markers.map = self.view as! GMSMapView self.markers.map = self.view as! GMSMapView

Here the cpu gets stuck at 100% (on a simulator in an 8 core machine)

If the self.markers.map gets reset to nil again, the cpu goes back to ~0% and all is good.

Question

Is this a limitation on cpu or GoogleMaps SDK? Is there a way to avoid the problem?

Steps to reproduce

After extracting the bits of code related, I added also similar conditions where a Label is being created as an icon for another marker.

After some tests it seems it is related on the amount of markers to process only. See LabelCount and set to different values, in my cpu the problem appeared with 200 but not with 100 markers (ie: 400 markers as there is an extra marker for the label)

Example code

import UIKit
import GoogleMaps

class ViewController: UIViewController {
    // The problem is noticed when markers have a UIImage
    // Set below to false to see normal cpu behaviour
    static let LabelsMakeProblem = true
    static let LabelCountFine = 100
    static let LabelCountProblems = 200
    static let LabelCount = ViewController.LabelCountProblems
    static let labelWidth = 200
    static let labelHeight = 20

    var coords = [CLLocationCoordinate2D]()
    static let initLat = Double(-33)
    static let initLong = Double(-70)
    static let zoomThreshold = Float(13)
    var oldZoom : Float!
    var markers = [Int: [GMSMarker]]()
    var labels = [Int: [GMSMarker]]()

    override func viewDidLoad() {
        super.viewDidLoad()
        // Generate some random points
        let initCoord = CLLocationCoordinate2D(latitude: ViewController.initLat, longitude: ViewController.initLong)
        let deltaCoord = 0.001
        for i in 0...200 {
            let multiplier = Double(i)
            self.coords.append(CLLocationCoordinate2D(
                latitude: initCoord.latitude + multiplier * deltaCoord,
                longitude: initCoord.longitude + multiplier * deltaCoord))
        }

        // Create a map
        let camera = GMSCameraPosition.camera(withLatitude: ViewController.initLat, longitude: ViewController.initLong, zoom: ViewController.zoomThreshold * 1.3)
        let mapView = GMSMapView.map(withFrame: .zero, camera: camera)
        mapView.delegate = self
        self.view = mapView
        self.oldZoom = mapView.camera.zoom

        // Add markers
        let label = self.createLabel()
        for (idx, coord) in self.coords.enumerated() {
            // Init marker arrays
            if self.markers[idx] == nil {
                self.markers[idx] = [GMSMarker]()
            }
            if self.labels[idx] == nil {
                self.labels[idx] = [GMSMarker]()
            }
            let marker = GMSMarker(position: coord)
            marker.map = mapView
            self.markers[idx]?.append(marker)
            if ViewController.LabelsMakeProblem {
                label.text = coord.latitude.description
                let contextSize = CGSize(width: ViewController.labelWidth, height: ViewController.labelHeight)
                let opaque = false
                UIGraphicsBeginImageContextWithOptions(contextSize, opaque, UIScreen.main.scale)
                if let currentContext = UIGraphicsGetCurrentContext(){
                    let labelBox = CGRect(x: 2, y: 2,
                                          width: ViewController.labelWidth, height: ViewController.labelHeight)
                    label.frame = labelBox
                    label.layer.render(in: currentContext)
                    let labelImage = UIGraphicsGetImageFromCurrentImageContext()
                    let labelMarker = GMSMarker(position: coord)
                    labelMarker.icon = labelImage
                    labelMarker.map = mapView
                    self.labels[idx]?.append(labelMarker)
                }
                UIGraphicsEndImageContext()
            }
        }

    }

    private func createLabel() -> UILabel{
        let label = UILabel()
        label.backgroundColor = UIColor.clear
        label.shadowColor = UIColor.white
        label.shadowOffset = CGSize(width: 5, height: 2)
        label.textColor = UIColor.black
        label.adjustsFontSizeToFitWidth = true
        label.textAlignment = .center
        return label
    }

    func hideMarkers() {
        for markers in self.markers.values.makeIterator() {
            for marker in markers {
                marker.map = nil
            }
        }
        print("Markers hidden")
    }

    func showMarkers() {
        let mapView = self.view as! GMSMapView
        var bounds = GMSCoordinateBounds()
        for markers in self.markers.values.makeIterator() {
            for marker in markers {
                marker.map = mapView
                bounds = bounds.includingCoordinate(marker.position)
            }
        }
        print("Show markers at zoom:\(mapView.camera.zoom)")
        // Ensure we see the markers
        let cameraUpdate = GMSCameraUpdate.fit(bounds)
        mapView.animate(with: cameraUpdate)
    }

    func hideLabels() {
        for markers in self.labels.values.makeIterator() {
            for marker in markers {
                marker.map = nil
            }
        }
        print("Labels hidden")
    }

    func showLabels() {
        let mapView = self.view as! GMSMapView
        for markers in self.labels.values.makeIterator() {
            for marker in markers {
                marker.map = mapView
            }
        }
        print("Show labels at zoom:\(mapView.camera.zoom)")
    }

}

extension ViewController : GMSMapViewDelegate {
    /// Hide labels when zooming out and show them when zooming in
    func mapView(_ mapView: GMSMapView, idleAt position: GMSCameraPosition) {
        print("Zoom update: \(position.zoom)")
        if position.zoom < self.oldZoom && position.zoom < ViewController.zoomThreshold {
            self.hideLabels()
        } else if position.zoom > self.oldZoom && position.zoom > ViewController.zoomThreshold {
            self.showLabels()
        }
        // Track changes
        self.oldZoom = position.zoom
    }
}

This is clustering approach I am using

//method to detect when user scrolls map
@objc(mapView:didChangeCameraPosition:) func mapView(_: GMSMapView, didChange _: GMSCameraPosition) {
    self.counter = self.counter + 1
    self.requestForMap(counter: self.counter)
}

//if user did nothing for 0.2 seconds request data from server
fileprivate func requestForMap(counter: Int) {
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in

        guard let `self` = self else {
            return
        }

        if counter == self.counter {
            self.sessionManager.session.invalidateAndCancel()
            self.requestData()
        }
    }
}

to get pins in area I do this on client

// get coordinates of visible area
extension GMSMapView {

    func boundings() -> [String: Any] {
        let screenBounds = UIScreen.main.bounds
        let topPoint = CGPoint(x: 15, y: 60)
        let bottomPoint = CGPoint(x: screenBounds.width - 15, y: screenBounds.height)
        let shoudBeFull = self.camera.zoom > 15 //if user is zoomed in a lot request all data in area
        let bouding = [
            "top": [
                "lat": self.projection.coordinate(for: topPoint).latitude,
                "lon": self.projection.coordinate(for: topPoint).longitude,
            ],
            "bottom": [
                "lat": self.projection.coordinate(for: bottomPoint).latitude,
                "lon": self.projection.coordinate(for: bottomPoint).longitude,
            ],
            "full": shoudBeFull,
        ] as [String: Any]
        return bouding
    }
}

then this data as JSON is passed to the server and the server gets pins' data for objects, coordinates of which are inside this bounding. We are using node.js, not sure how it works there.

Then I have an array of currently displayed pins like var pins = [GMSMarker] , after I get an array of objects from server I go through this array, remove those, which are not in new data and add those, which are new

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM