How to clustered markers from Firebase in GoogleMa

2019-02-21 04:28发布

I'm developing an app on which I want to show a lot of events on the map. The user can click on an event and see a lot of informations about it. In another view, a user can create a new event then the location and title are stored in Firebase database. Then when others users watch the GoogleMaps on my app, they are able to see all the events who are a marker in the map. I want to cluster the markers from Firebase when the user zoom out on the map but it can't work maybe because of my loaded way of the data markers on Firebase. There's 3 issues: - I'm not able to cluster my custom marker with orange color. - The markers and the clusters icon don't appear when the map load, I need to zoom in or zoom out first - I want the data of marker are show in the infoWindow but I got to use the right data for the marker corresponding on the GoogleMap and Firebase. - When I click on a cluster icon, it shows the alertController too, but I want only see the alertController when user tap a marker not on the cluster icon.

Here's my current code :

class POIItem: NSObject, GMUClusterItem {
var position: CLLocationCoordinate2D
var name: String!

init(position: CLLocationCoordinate2D, name: String) {
    self.position = position
    self.name = name
}
}

class NewCarteViewController: UIViewController, GMSMapViewDelegate, CLLocationManagerDelegate, GMUClusterManagerDelegate {

var locationManager = CLLocationManager()
var positionActuelle = CLLocation() // Another current position
var currentPosition = CLLocationCoordinate2D()
var latiti: CLLocationDegrees!
var longiti: CLLocationDegrees!

private var clusterManager: GMUClusterManager! // Cluster
private var maMap: GMSMapView!
var marker = GMSMarker()
let geoCoder = CLGeocoder()

var ref = DatabaseReference()
var estTouche: Bool!

override func viewDidLoad() {
    super.viewDidLoad()

    locationManager.delegate = self
    locationManager.requestWhenInUseAuthorization()

    positionActuelle = locationManager.location!
    latiti = positionActuelle.coordinate.latitude
    longiti = positionActuelle.coordinate.longitude
    currentPosition = CLLocationCoordinate2D(latitude: latiti, longitude: longiti)

    let camera = GMSCameraPosition.camera(withTarget: currentPosition, zoom: 10)
    maMap = GMSMapView.map(withFrame: CGRect.zero, camera: camera)
    maMap.mapType = .normal
    maMap.settings.compassButton = true
    maMap.isMyLocationEnabled = true
    maMap.settings.myLocationButton = true
    maMap.delegate = self
    self.view = maMap

    let iconGenerator = GMUDefaultClusterIconGenerator()
    let algorithm = GMUNonHierarchicalDistanceBasedAlgorithm()
    let renderer = GMUDefaultClusterRenderer(mapView: maMap, clusterIconGenerator: iconGenerator)
    clusterManager = GMUClusterManager(map: maMap, algorithm: algorithm, renderer: renderer)

    loadMarker()
}

// Download datas of markers from Firebase Database
func loadMarker() {
    ref = Database.database().reference()
    let usersRef = ref.child("markers")

    usersRef.observeSingleEvent(of: .value, with: { (snapshot) in
        if (snapshot.value is NSNull) {
            print("not found")
        } else {
            for child in snapshot.children {
                let userSnap = child as! DataSnapshot
                let uid = userSnap.key // the uid of each user
                let userDict = userSnap.value as! [String: AnyObject]
                let latitudes = userDict["latitudeEvent"] as! Double
                let longitudes = userDict["longitudeEvent"] as! Double
                let bellname = userDict["nom"] as! String
                let belltitre = userDict["titreEvent"] as! String
                let total = snapshot.childrenCount // Number of markers in Firebase

                let positionMarker = CLLocationCoordinate2DMake(latitudes, longitudes)
                var diff = Double(round(100*self.getDistanceMetresBetweenLocationCoordinates(positionMarker, coord2: self.currentPosition))/100)
                var dif = Double(round(100*diff)/100)

                var positionEvenement = CLLocation(latitude: latitudes, longitude: longitudes) // Event location

                // Function in order to convert GPS Coordinate in an address
CLGeocoder().reverseGeocodeLocation(positionEvenement, completionHandler: {(placemarks, error) -> Void in

                    if error != nil {
                        print("Reverse geocoder a rencontré une erreur " + (error?.localizedDescription)!)
                        return
                    }

                    if (placemarks?.count)! > 0 {
                        print("PlaceMarks \(placemarks?.count)!")
                        let pm = placemarks?[0] as! CLPlacemark
                        var adres = "\(pm.name!), \(pm.postalCode!) \(pm.locality!)"
                        let item = POIItem(position: CLLocationCoordinate2DMake(latitudes, longitudes), name: "")
                        // self.marker.userData = item // I delete this line
                        self.clusterManager.add(item)
                        self.marker = GMSMarker(position: positionMarker)
                        self.marker.icon = UIImage(named: "marker-45")
                        self.marker.title = "\(belltitre)"
                        self.marker.snippet = "Live de \(bellname)\nLieu: \(adres)\nDistance: \(dif) km"
                        self.marker.map = self.maMap
                    } else {
                        print("Problème avec les données reçu par le géocoder")
                    }
                })
            }
            self.clusterManager.cluster()
            self.clusterManager.setDelegate(self, mapDelegate: self)
        }
    })
}

func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool {
    if let poiItem = marker.userData as? POIItem {
        NSLog("Did tap marker for cluster item \(poiItem.name)")
    } else {
        NSLog("Did tap a normal marker")
    }
    return false
}

func clusterManager(_ clusterManager: GMUClusterManager, didTap cluster: GMUCluster) -> Bool {
    let newCamera = GMSCameraPosition.camera(withTarget: cluster.position, zoom: maMap.camera.zoom + 1)
    let update = GMSCameraUpdate.setCamera(newCamera)
    maMap.moveCamera(update)
    return false
}

func renderer(_ renderer: GMUClusterRenderer, markerFor object: Any) -> GMSMarker? {
    let marker = GMSMarker()
    if let model = object as? POIItem { // POIItem class is your MarkerModel class
        marker.icon = UIImage(named: "marker-45") // Like this ?
        // set image view for gmsmarker
    }
    return marker
}

// Distance between 2 locations
func getDistanceMetresBetweenLocationCoordinates(_ coord1: CLLocationCoordinate2D, coord2: CLLocationCoordinate2D) -> Double {
    let location1 = CLLocation(latitude: coord1.latitude, longitude: coord1.longitude)
    let location2 = CLLocation(latitude: coord2.latitude, longitude: coord2.longitude)
    var distance = ((location1.distance(from: location2)) / 1000)
    return distance
}

// Affiche les boutons du live
func alert(_ sender: AnyObject) {
    let alertController = UIAlertController(title: "", message: "", preferredStyle: .actionSheet)
    alertController.title = nil
    alertController.message = nil
    alertController.addAction(UIAlertAction(title: "Accéder au live", style: .default, handler: self.accederLive))
    alertController.addAction(UIAlertAction(title: "Infos event", style: .default, handler: self.infosEvent))
    alertController.addAction(UIAlertAction(title: "Annuler", style: .cancel, handler: nil))
    self.present(alertController, animated: true, completion: nil)
}

// Display infoWindow and alertController
func mapView(_ mapView: GMSMapView!, markerInfoWindow marker: GMSMarker!) -> UIView! {
    let infoWindow = Bundle.main.loadNibNamed("InfoWindow", owner: self, options: nil)?.first! as! CustomInfoWindow
    self.estTouche = true
    if self.estTouche == true {
        self.alert(self.estTouche as AnyObject)
    } else {
        print("estTouche est false")
    }
    print(self.estTouche)
    return nil // infoWindow
}

Sorry it a lil bit long code if you don't understand something let me know, I tried to comment

Here's the screenshot of the googlemap now. open map photo after zoom in

The first screenshot is when I open the map, you can see nothing appears on the map, no cluster icon or isolate marker, it strange.

The second screenshot is when I zoom in one time, so after cluster icon appears and one marker appears too. What's wrong with my code, I want all cluster icon or marker show when user open the map view.

2条回答
你好瞎i
2楼-- · 2019-02-21 04:37

Firstly loadMarker() should be called after the clusterManager is initialized i.e

clusterManager = GMUClusterManager(map: maMap, algorithm: algorithm, renderer: renderer)

then

clusterManager.cluster()
clusterManager.setDelegate(self, mapDelegate: self)

should be placed in loadMarker() after for loop ends.

Your viewcontroller should conform to this protocol GMUClusterManagerDelegate then add these 2 methods in viewcontroller.

func renderer(_ renderer: GMUClusterRenderer, markerFor object: Any) -> GMSMarker? {

    let marker = GMSMarker()
    if let model = object as? MarkerModel {
         // set image view for gmsmarker
    }

    return marker
}

func clusterManager(_ clusterManager: GMUClusterManager, didTap cluster: GMUCluster) -> Bool {
    let newCamera = GMSCameraPosition.camera(withTarget: cluster.position, zoom: mapView.camera.zoom + 1)
    let update = GMSCameraUpdate.setCamera(newCamera)
    mapView.moveCamera(update)
    return false
}

Try this and let me know if this works else we will try something else.

查看更多
何必那么认真
3楼-- · 2019-02-21 04:53

I resolve my question so I post here the solution, thank to Prateek for his help.

I use this topic for solve it too : How to implement GMUClusterRenderer in Swift

First I changed one line of code in a GoogleMaps files SDK: I found it in my project at this path : Pods/Pods/Google-Maps-iOS-Utils/Clustering/GMUDefaultClusterRenderer.m This file name is GMUDefaultClusterRenderer.m. This changement it's for cluster custom marker icon in the cluster

- (void)renderCluster:(id<GMUCluster>)cluster animated:(BOOL)animated {
...

  GMSMarker *marker = [self markerWithPosition:item.position
                                          from:fromPosition
                                      userData:item
                                   clusterIcon:[UIImage imageNamed:@"YOUR_CUSTOM_ICON"] // Here you change "nil" by the name of your image icon
                                      animated:shouldAnimate];
  [_markers addObject:marker];
  [_renderedClusterItems addObject:item];
...
}

Second, I added this function in my Swift file of my map :

private func setupClusterManager() {
    let iconGenerator = GMUDefaultClusterIconGenerator()
    let algorithm = GMUNonHierarchicalDistanceBasedAlgorithm()
    let renderer = GMUDefaultClusterRenderer(mapView: maMap,
                                             clusterIconGenerator: iconGenerator)

    clusterManager = GMUClusterManager(map: maMap, algorithm: algorithm,
                                       renderer: renderer)

}

Third, I added a variable in the POIItem class in order to get information from Firebase, to show it in infoWindows of markers:

class POIItem: NSObject, GMUClusterItem {
var position: CLLocationCoordinate2D
var name: String!
var snippet: String! // I add it here

init(position: CLLocationCoordinate2D, name: String, snippet: String) {
    self.position = position
    self.name = name
    self.snippet = snippet // I add it also here
}
}

I get the informations of markers from Firebase thanks to POIItem class in the following function (before I call the function loadMarker() in order to load the data of each markers from Firebase):

func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool {
    if let poiItem = marker.userData as? POIItem {
        NSLog("Did tap marker for cluster item \(poiItem.name!)")
        marker.title = "\(poiItem.name!)"
        marker.snippet = "\(poiItem.snippet!)"
        self.estTouche = true
        if self.estTouche == true {
           self.alert(self.estTouche as AnyObject) // If true it shows an alertController
        } else {
           print("estTouche est false")
        }
           print(self.estTouche)
    } else {
        NSLog("Did tap a normal marker")
    }
    return false
}

Here's the whole code of the solution, it works fine for me.

import UIKit
import GoogleMaps
import CoreLocation
import Firebase
import FirebaseAuth
import FirebaseDatabase

/// Point of Interest Item which implements the GMUClusterItem protocol.
class POIItem: NSObject, GMUClusterItem {
var position: CLLocationCoordinate2D
var name: String!
var snippet: String!

init(position: CLLocationCoordinate2D, name: String, snippet: String) {
    self.position = position
    self.name = name
    self.snippet = snippet
}
}

class NewCarteViewController: UIViewController, GMSMapViewDelegate, CLLocationManagerDelegate, GMUClusterManagerDelegate {

var locationManager = CLLocationManager()
var positionActuelle = CLLocation()
var positionEvent = CLLocationCoordinate2D()
var currentPosition = CLLocationCoordinate2D()
var latiti: CLLocationDegrees!
var longiti: CLLocationDegrees!

private var clusterManager: GMUClusterManager! // Cluster

private var maMap: GMSMapView!
var marker = GMSMarker()

var ref = DatabaseReference() // Firebase reference
var estTouche: Bool!
let geoCoder = CLGeocoder()

// For load the map
override func loadView() {
    let camera = GMSCameraPosition.camera(withLatitude: 48.898902,
                                      longitude: 2.282664, zoom: 12)
    maMap = GMSMapView.map(withFrame: CGRect.zero, camera: camera)
    self.view = maMap
}

override func viewDidLoad() {
    super.viewDidLoad()
    let iconGenerator = GMUDefaultClusterIconGenerator()
    let algorithm = GMUNonHierarchicalDistanceBasedAlgorithm() // crée un gestionnaire de groupes utilisant l'algorithme
    let renderer = GMUDefaultClusterRenderer(mapView: maMap, clusterIconGenerator: iconGenerator) // Le renderer est le moteur de rendu des groupes
    clusterManager = GMUClusterManager(map: maMap, algorithm: algorithm, renderer: renderer)

    loadMarker()

    locationManager.delegate = self
    locationManager.requestWhenInUseAuthorization()

    positionActuelle = locationManager.location!
    latiti = positionActuelle.coordinate.latitude
    longiti = positionActuelle.coordinate.longitude

    currentPosition = CLLocationCoordinate2D(latitude: latiti, longitude: longiti)

    maMap.mapType = .normal
    maMap.settings.compassButton = true // Boussole
    maMap.isMyLocationEnabled = true // User current position icon
    maMap.settings.myLocationButton = true // Button for center the camera on the user current position
    maMap.delegate = self
}

// Download datas of markers from Firebase Database
  func loadMarker() {
    ref = Database.database().reference()
    let usersRef = ref.child("markers")

    usersRef.observeSingleEvent(of: .value, with: { (snapshot) in
        if (snapshot.value is NSNull) {
            print("not found")
        } else {
            for child in snapshot.children {
                let userSnap = child as! DataSnapshot
                let uid = userSnap.key // The uid of each user
                let userDict = userSnap.value as! [String: AnyObject] // Child data
                let latitudes = userDict["latitudeEvent"] as! Double
                let longitudes = userDict["longitudeEvent"] as! Double
                let bellname = userDict["nom"] as! String
                let belltitre = userDict["titreEvent"] as! String
                let total = snapshot.childrenCount // Count of markers save in my Firebase database
                print("Total de marqueurs : \(total)")

                let positionMarker = CLLocationCoordinate2DMake(bellatitude, bellongitude)
                var diff = Double(round(100*self.getDistanceMetresBetweenLocationCoordinates(positionMarker, coord2: self.currentPosition))/100)
                var dif = Double(round(100*diff)/100)

                var positionEvenement = CLLocation(latitude: latitudes, longitude: longitudes)

                // Function in order to convert GPS Coordinate in an address
                CLGeocoder().reverseGeocodeLocation(positionEvenement, completionHandler: {(placemarks, error) -> Void in

                    if error != nil {
                        print("Reverse geocoder meets error " + (error?.localizedDescription)!)
                        return
                    }

                    if (placemarks?.count)! > 0 {
                        print("PlaceMarks \((placemarks?.count)!)")
                        let pm = placemarks?[0] as! CLPlacemark
                        var adres = "\(pm.name!), \(pm.postalCode!) \(pm.locality!)"
                        let item = POIItem(position: CLLocationCoordinate2DMake(latitudes, longitudes), name: "\(belltitre)", snippet: "Live de \(bellname)\nLieu: \(adres)\nDistance: \(dif) km") // This line is very important in order to import data from Firebase and show in infoWindow for the right datas for each markers
                        self.clusterManager.add(item)
                        self.clusterManager.cluster()
                        self.clusterManager.setDelegate(self, mapDelegate: self)
                    } else {
                        print("Problème avec les données reçues par le géocoder")
                    }
                })
            }
        }
    })
}

func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool {
    if let poiItem = marker.userData as? POIItem {
        NSLog("Did tap marker for cluster item \(poiItem.name!)")
        marker.title = "\(poiItem.name!)" // Title of the marker infoWindow (title from Firebase)
        marker.snippet = "\(poiItem.snippet!)" // Same for snippet
        self.estTouche = true
        if self.estTouche == true {
           self.alert(self.estTouche as AnyObject) // Show the alertController because infoWindows can't use button
        } else {
           print("estTouche est false")
        }
           print(self.estTouche)
    } else {
        NSLog("Did tap a normal marker")
    }
    return false
}

// If I tap a cluster icon, it zoom in +1
func clusterManager(_ clusterManager: GMUClusterManager, didTap cluster: GMUCluster) -> Bool {
    let newCamera = GMSCameraPosition.camera(withTarget: cluster.position, zoom: maMap.camera.zoom + 1)
    let update = GMSCameraUpdate.setCamera(newCamera)
    maMap.moveCamera(update)
    return false
}

private func setupClusterManager() {
    let iconGenerator = GMUDefaultClusterIconGenerator()
    let algorithm = GMUNonHierarchicalDistanceBasedAlgorithm()
    let renderer = GMUDefaultClusterRenderer(mapView: maMap,
                                             clusterIconGenerator: iconGenerator)

    clusterManager = GMUClusterManager(map: maMap, algorithm: algorithm,
                                       renderer: renderer)

}

func renderer(_ renderer: GMUClusterRenderer, markerFor object: Any) -> GMSMarker? {
    if let model = object as? POIItem {
      self.clusterManager.cluster()
      self.clusterManager.setDelegate(self, mapDelegate: self)
    }
    return nil
}

// Distance between 2 locations
func getDistanceMetresBetweenLocationCoordinates(_ coord1: CLLocationCoordinate2D, coord2: CLLocationCoordinate2D) -> Double {
    let location1 = CLLocation(latitude: coord1.latitude, longitude: coord1.longitude)
    let location2 = CLLocation(latitude: coord2.latitude, longitude: coord2.longitude)
    var distance = ((location1.distance(from: location2)) / 1000)
    return distance
}

// Show alertController with 2 buttons and a Cancel button
func alert(_ sender: AnyObject) {
    let alertController = UIAlertController(title: "", message: "", preferredStyle: .actionSheet) // Ne me donne pas le bon nom
    alertController.title = nil
    alertController.message = nil // Supprime la ligne message sous le titre afin de pouvoir centrer le titre
    alertController.addAction(UIAlertAction(title: "Accéder au live", style: .default, handler: self.accederLive))
    alertController.addAction(UIAlertAction(title: "Infos event", style: .default, handler: self.infosEvent)) // Ou Affichage du profil utilisateur
    alertController.addAction(UIAlertAction(title: "Annuler", style: .cancel, handler: nil))
    self.present(alertController, animated: true, completion: nil)
}

// The two following functions are used in alertController
func accederLive(_ sender: AnyObject) {
...
}

func infosEvent(_ sender: AnyObject) {
    let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Event")
    present(vc, animated: true, completion: nil)
}

func mapView(_ mapView: GMSMapView!, markerInfoWindow marker: GMSMarker!) -> UIView! {
    let infoWindow = Bundle.main.loadNibNamed("InfoWindow", owner: self, options: nil)?.first! as! CustomInfoWindow
    return nil
}
}

Hope it can help someone else.

I add finally my way to load the data of the marker in Firebase in another Swift file (if it can help someone to do it too) :

var locationManage = CLLocationManager()
var positionActuel = CLLocation() // Current position of user
var latitudinale: CLLocationDegrees! // Latitude
var longitudinale: CLLocationDegrees! // Longitude
var m = GMSMarker()

class AddEventViewController: UIViewController, UISearchBarDelegate, GMSMapViewDelegate, CLLocationManagerDelegate {

var categorie: String! // Type of event
var titre: String! // Title of event
var name: String! // Username

var userId = Auth.auth().currentUser?.uid // Get the uid of the connected user in Firebase

@IBOutlet weak var titreTextField: UITextField! // TextField in where user can set a title

override func viewDidLoad() {
    super.viewDidLoad()
...

    locationManage.delegate = self
    locationManage.requestWhenInUseAuthorization()

    positionActuel = locationManage.location!
    latitudinale = positionActuel.coordinate.latitude
    longitudinale = positionActuel.coordinate.longitude

    name = Auth.auth().currentUser?.displayName // In order to get the username of the connected user in Firebase
}

@IBAction func goAction(_ sender: Any) {
    let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Live")
    self.present(vc, animated: true, completion: nil)

    if titreTextField.text == "" {
        titre = "\(name!) Live \(categorie!)"
    } else {
        titre = titreTextField.text!
    }

    setMarker(marker: m) // Add a marker on the map
}

// Save data of this event in Firebase (and this marker on Firebase)
func setMarker(marker: GMSMarker) {
    var lati = latitudinale
    var longi = longitudinale
    var nom = self.name
    var title = self.titre

    var userMarker = ["nom": nom!, // User name
        "latitudeEvent": lati!, // Latitude of event
        "longitudeEvent": longi!, // Longitude of event
        "titreEvent": title!] as [String : AnyObject] // Title of event

    KeychainWrapper.standard.set(userId!, forKey: "uid")

    let emplacement = Database.database().reference().child("markers").child(userId!) // Reference of my Firebase Database
    emplacement.setValue(userMarker)
}
}
查看更多
登录 后发表回答