Reputation: 4907
self.view
as GMSMapView
in viewDidLload
self.markers
and set marker.map
to the self.view as! GMSMapView
So far the app behaves well
self.markers.map
to nil
Up to here all goes well and the markers are gone from the map
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.
Is this a limitation on cpu or GoogleMaps SDK? Is there a way to avoid the problem?
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)
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
}
}
Upvotes: 1
Views: 1596
Reputation: 2668
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
Upvotes: 1