如何在GoogleMaps for iOS中使用Firebase聚集标记

时间:2017-09-21 16:10:46

标签: ios swift google-maps firebase markerclusterer

我正在开发一个应用程序,我想在地图上显示很多事件。用户可以单击某个事件并查看有关它的大量信息。 在另一个视图中,用户可以创建新事件,然后位置和标题存储在Firebase数据库中。 然后当其他用户在我的应用上观看GoogleMaps时,他们就能够看到地图中标记的所有事件。 我想在用户缩小地图时对Firebase中的标记进行聚类,但由于我在Firebase上加载了数据标记,因此无法工作。 有3个问题: - 我无法将自定义标记聚为橙色。 - 地图加载时,标记和群集图标不会显示,我需要先放大或缩小 - 我希望标记的数据显示在infoWindow中,但我必须使用GoogleMap和Firebase上相应标记的正确数据。 - 当我点击一个群集图标时,它也会显示alertController,但是当用户点击一个不在群集图标上的标记时,我只想看到alertController。

这是我目前的代码:

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
}

对不起,如果您不了解某些内容让我知道,我会尝试发表评论

此处是googlemap的屏幕截图。 open map photo after zoom in

第一个截图是当我打开地图时,你可以看到地图上没有任何内容,没有群集图标或隔离标记,这很奇怪。

第二个屏幕截图是当我放大一次时,所以在出现群集图标后也会出现一个标记。 我的代码出了什么问题,我想在用户打开地图视图时显示所有群集图标或标记。

2 个答案:

答案 0 :(得分:3)

首先应在clusterManager初始化后调用loadMarker(),即

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

然后

clusterManager.cluster()
clusterManager.setDelegate(self, mapDelegate: self)
在循环结束后,

应放在loadMarker()

您的viewcontroller应符合此协议GMUClusterManagerDelegate,然后在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
}

试试这个并让我知道,如果这有效,我们会尝试别的。

答案 1 :(得分:0)

我解决了我的问题所以我在这里发布解决方案,感谢Prateek的帮助。

我也使用这个主题来解决它:How to implement GMUClusterRenderer in Swift

首先,我在GoogleMaps文件SDK中更改了一行代码: 我在这个路径的项目中找到了它:Pods / Pods / Google-Maps-iOS-Utils / Clustering / GMUDefaultClusterRenderer.m 此文件名是GMUDefaultClusterRenderer.m。 此更改适用于群集中的群集自定义标记图标

- (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];
...
}

其次,我在我的地图的Swift文件中添加了这个函数:

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

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

}

第三,我在POIItem类中添加了一个变量,以便从Firebase获取信息,并在标记的infoWindows中显示它:

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
}
}

由于以下函数中的POIItem类,我从Firebase获取了标记的信息(在我调用函数loadMarker()之前,为了从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
}

这是解决方案的整个代码,它对我来说很好。

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
}
}

希望它可以帮助别人。

我最后添加了在另一个Swift文件中加载Firebase中标记数据的方法(如果它可以帮助某人也这样做):

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)
}
}