How to clustered markers from Firebase in GoogleMaps for iOS

后端 未结 2 1718
暗喜
暗喜 2020-12-20 10:26

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

相关标签:
2条回答
  • 2020-12-20 11:21

    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)
    }
    }
    
    0 讨论(0)
  • 2020-12-20 11:27

    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.

    0 讨论(0)
提交回复
热议问题