Как реализовать GMUClusterRenderer в Swift

Я использую Google Maps API для iOS и хочу использовать утилиту кластеризации маркеров. Я понял, как показывать сгруппированные маркеры, но я хотел бы настроить маркеры. Может кто-нибудь объяснить, как установить/изменить значок и заголовок каждого маркера или сгруппированных маркеров? Пример кода был бы очень полезен.

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

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

class MyRenderer: NSObject, GMUClusterRenderer {
    var mapView: GMSMapView
    var clusterIconGenerator: GMUClusterIconGenerator
    var clusterManager: GMUClusterManager

    init(mapView: GMSMapView, clusterIconGenerator: GMUClusterIconGenerator, clusterManager: GMUClusterManager) {
        self.mapView = mapView
        self.clusterIconGenerator = clusterIconGenerator
        self.clusterManager = clusterManager
    }

    func renderClusters(clusters: [GMUCluster]) {

    }

    func update() {

    }
}

Это то, что у меня есть до сих пор. Я не знаю, что делать с функциями renderClusters и update.


person S. Park    schedule 24.07.2016    source источник
comment
Вы должны показать, что вы пробовали.   -  person Feldur    schedule 24.07.2016


Ответы (6)


Я нашел чистое решение для кластерных маркеров на Swift 4, чтобы использовать собственное изображение для кластера с номером кластера внутри:

class MapClusterIconGenerator: GMUDefaultClusterIconGenerator {

    override func icon(forSize size: UInt) -> UIImage {
        let image = textToImage(drawText: String(size) as NSString,
                                inImage: UIImage(named: "cluster")!,
                                font: UIFont.systemFont(ofSize: 12))
        return image
    }

    private func textToImage(drawText text: NSString, inImage image: UIImage, font: UIFont) -> UIImage {

        UIGraphicsBeginImageContext(image.size)
        image.draw(in: CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height))

        let textStyle = NSMutableParagraphStyle()
        textStyle.alignment = NSTextAlignment.center
        let textColor = UIColor.black
        let attributes=[
            NSAttributedStringKey.font: font,
            NSAttributedStringKey.paragraphStyle: textStyle,
            NSAttributedStringKey.foregroundColor: textColor]

        // vertically center (depending on font)
        let textH = font.lineHeight
        let textY = (image.size.height-textH)/2
        let textRect = CGRect(x: 0, y: textY, width: image.size.width, height: textH)
        text.draw(in: textRect.integral, withAttributes: attributes)
        let result = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return result!
    }

}

Чем настройка для менеджера кластера:

private func setupClustering() {
    guard let mapView = self.mapView else { return }

    let iconGenerator = MapClusterIconGenerator()
    let renderer = MapClusterRenderer(mapView: mapView, clusterIconGenerator: iconGenerator)
    let algorithm = GMUNonHierarchicalDistanceBasedAlgorithm()
    clusterManager = GMUClusterManager(map: mapView, algorithm: algorithm, renderer: renderer)
}

Я также использовал пользовательский кластерный рендерер MapClusterRenderer.

person Andr3a88    schedule 26.09.2017

Мне удается найти «чистое» решение, хотя оно все еще сбивает с толку. Но это работает!

1) Создайте файл .h "MarkerManager"

    #import <Foundation/Foundation.h>
@import CoreLocation;
#import "GMUClusterItem.h"
#import <GoogleMaps/GoogleMaps.h>


@interface MarkerManager: NSObject

@property (nonatomic) CLLocationCoordinate2D location;
@property (nonatomic, strong) GMSMarker *marker;

@end

2) Перейдите к классу GMUDefaultClusterRenderer в папке Google-Maps-iOS-Utils, импортируйте класс MarkerManager.h, найдите и измените этот метод:

// Returns a marker at final position of |position| with attached |userData|.
// If animated is YES, animates from the closest point from |points|.
- (GMSMarker *)markerWithPosition:(CLLocationCoordinate2D)position
                             from:(CLLocationCoordinate2D)from
                         userData:(id)userData
                      clusterIcon:(UIImage *)clusterIcon
                         animated:(BOOL)animated {
  GMSMarker *marker = [self markerForObject:userData];
  CLLocationCoordinate2D initialPosition = animated ? from : position;
  marker.position = initialPosition;
  marker.userData = userData;
  if (clusterIcon != nil) {
    marker.icon = clusterIcon;
    marker.groundAnchor = CGPointMake(0.5, 0.5);
  }
  //added
  else {
      MarkerManager *data = userData;
      if(data != nil) {
          marker.icon = data.marker.icon;
      }
  }
  //ends here

  marker.zIndex = _zIndex;

  if ([_delegate respondsToSelector:@selector(renderer:willRenderMarker:)]) {
    [_delegate renderer:self willRenderMarker:marker];
  }
  marker.map = _mapView;

  if (animated) {
    [CATransaction begin];
    [CATransaction setAnimationDuration:kGMUAnimationDuration];
    marker.layer.latitude = position.latitude;
    marker.layer.longitude = position.longitude;
    [CATransaction commit];
  }

  if ([_delegate respondsToSelector:@selector(renderer:didRenderMarker:)]) {
    [_delegate renderer:self didRenderMarker:marker];
  }
  return marker;
}

3) Создайте новый класс swift, POIItem:

class POIItem: NSObject, GMUClusterItem {
var position: CLLocationCoordinate2D
@objc var marker: GMSMarker!


init(position: CLLocationCoordinate2D, marker: GMSMarker) {
    self.position = position
    self.marker = marker
}
}

4) Расширить класс GMUDefaultClusterRenderer и переопределить метод markerWithPosition:

import Foundation
import UIKit

class CustomMarkers: GMUDefaultClusterRenderer {
    var mapView:GMSMapView?
    let kGMUAnimationDuration: Double = 0.5

    override init(mapView: GMSMapView, clusterIconGenerator iconGenerator: GMUClusterIconGenerator) {

        super.init(mapView: mapView, clusterIconGenerator: iconGenerator)
    }

    func markerWithPosition(position: CLLocationCoordinate2D, from: CLLocationCoordinate2D, userData: AnyObject, clusterIcon: UIImage, animated: Bool) -> GMSMarker {
        let initialPosition = animated ? from : position
        let marker = GMSMarker(position: initialPosition)
        marker.userData! = userData
        if clusterIcon.cgImage != nil {
            marker.icon = clusterIcon
        }
        else {
            marker.icon = self.getCustomTitleItem(userData: userData)

        }
        marker.map = mapView
        if animated
        {
            CATransaction.begin()
            CAAnimation.init().duration = kGMUAnimationDuration
            marker.layer.latitude = position.latitude
            marker.layer.longitude = position.longitude
            CATransaction.commit()
        }
        return marker
    }

    func getCustomTitleItem(userData: AnyObject) -> UIImage {
        let item = userData as! POIItem
        return item.marker.icon!
    }
}

5) В MapViewController инициируйте POIItem в методе generateClusterItems:

private func generateClusterItems() {

        for object in DataManager.sharedInstance.mapItemsArray {

            let doubleLat = Double(object.latitude)
            let doubleLong = Double(object.longitude)
            let latitude = CLLocationDegrees(doubleLat!)
            let longitude = CLLocationDegrees(doubleLong!)
            let position = CLLocationCoordinate2DMake(latitude, longitude)
            let marker = GMSMarker(position: position)
            let item = POIItem(position: position, marker: marker)
            self.clusterManager.add(item)                
            item.mapItem = object

        }
    }

Внутри цикла for вы можете вызвать:

marker.icon = UIImage(named:"YOUR_IMAGE_NAME")

Теперь вы можете установить логику, чтобы иметь более одного пользовательского маркера.

person nja    schedule 22.05.2017
comment
Мне нравится ваше решение, и оно работает для Swift 3, но теперь в Swift 4 вы получаете неявную точку входа Objective-C, добавьте явное «@objc» в объявление, чтобы создать точку входа Objective-C в Swift 4. Вы нашли другой обходной путь ? - person bradford gray; 07.11.2017
comment
В классе POIItem просто добавьте @objc перед маркером var, я отредактировал решение. Простите за поздний ответ. - person nja; 10.01.2018
comment
Спасибо за ответ. - person bradford gray; 10.01.2018
comment
Спасибо, это работает!!! Единственное, что по какой-то причине не работает, это то, что когда я нажимаю на кластер, он вылетает вместо увеличения. - person Aleksandrs Muravjovs; 14.08.2019

В Swift 4.2:

вы можете использовать GMUClusterRendererDelegate:

Добавьте это расширение в свой контроллер и убедитесь, что ваш контроллер является делегатом GMUClusterRendererDelegate :

willRenderMarker будет вызываться каждый раз, когда маркер будет отображаться (как маркер кластера, так и clusterItemMarker, поэтому вы можете проверить его простым if). Таким образом, вы можете изменить его значок и т. д., прежде чем показывать его пользователю

extension YourController: GMUClusterRendererDelegate {
    func renderer(_ renderer: GMUClusterRenderer, willRenderMarker marker: GMSMarker) {
        // if your marker is pointy you can change groundAnchor
        marker.groundAnchor = CGPoint(x: 0.5, y: 1)
        if  let markerData = (marker.userData as? PersonMarker) {
           let icon = markerData.imageURL
           marker.iconView = CustomMarkerView(forUrl: url)
        }
    }
}

И PersonMarker — это ваш класс маркеров, который является подклассом NSObject и GMUClusterItem: (вы можете использовать класс по умолчанию GMUClusterItem, но если вам нужны какие-то другие свойства, вы можете создать его подкласс)

class PersonMarker: NSObject, GMUClusterItem {

  var position: CLLocationCoordinate2D
  var imageURL : String?
  var name: String?
  var userdId: String?
  var lastSeen: String?

  init(position: CLLocationCoordinate2D, url: String?, name: String?, userId: String?, lastSeen: String?) {
      self.position = position
      self.imageURL = url
      self.name = name
      self.userdId = userId
      self.lastSeen = lastSeen
  }

}

Вы можете добавить PersonMarker к GMUClusterManager следующим образом:

 let position = CLLocationCoordinate2D(latitude: item.latitude!, longitude: item.longitute!)
 let person = PersonMarker(position: position, url: item.user?.avaterUrl, name: item.user?.name, userId: item.user?.userId, lastSeen: item.lastUpdate)
 clusterManager.add(person)
person andesta.erfan    schedule 29.12.2018

Здесь показаны пошаговые инструкции о том, как сгруппировать несколько маркеров карты с помощью кластеризации маркеров

1:

class HFDashBordVC: UIViewController,GMUClusterManagerDelegate,GMSMapViewDelegate{

  private var clusterManager: GMUClusterManager!

 var cameraPosition = GMSCameraPosition()
var markerPin = GMSMarker()
var currentLocationCircle = GMSCircle()
var locationManager = CLLocationManager()
var currentLocation: CLLocation?
var zoomLevel: Float = 18.0

@IBOutlet var mapView: GMSMapView!

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

 func setupClustering() {
    let iconGenerator = CustomClusterIconGenerator()
    let algorithm = GMUNonHierarchicalDistanceBasedAlgorithm()
    let renderer = CustomRendererMarkers(mapView: mapView,
                                         clusterIconGenerator: iconGenerator)
    clusterManager = GMUClusterManager(map: mapView, algorithm: algorithm,
                                       renderer: renderer)
    clusterManager.cluster()
    clusterManager.setDelegate(self, mapDelegate: self)
}

private func setClusterParkingPin() {

    clusterManager.clearItems()
    for object in AppUtilites.sharedInstance.safeParkingArray {
        
        let model = object as! AMParkModel
        let lat = Double(model.latitude)!
        let lng = Double(model.longitude)!
        let position = CLLocationCoordinate2DMake(lat, lng)
        let marker = GMSMarker(position: position)
        marker.icon = HFAsset.ic_p_pin_orange.image 
        let userData = model
        let item = POIItem(position: position, marker: marker, userData: userData)
        self.clusterManager.add(item)
        self.setCurrentPin(zoomLevel: 14.0)
        
    }
}

2 :

 class POIItem: NSObject, GMUClusterItem {
        var position: CLLocationCoordinate2D
       var userData: AnyObject!
       @objc var marker: GMSMarker!


  init(position: CLLocationCoordinate2D, marker: GMSMarker, userData: AnyObject) {
    self.position = position
    self.marker = marker
    self.userData = userData
   }
 }

3 :

  class CustomRendererMarkers: GMUDefaultClusterRenderer {
var mapView:GMSMapView?
let kGMUAnimationDuration: Double = 0.5

override init(mapView: GMSMapView, clusterIconGenerator iconGenerator: GMUClusterIconGenerator) {
    
    super.init(mapView: mapView, clusterIconGenerator: iconGenerator)
}

func markerWithPosition(position: CLLocationCoordinate2D, from: CLLocationCoordinate2D, userData: AnyObject, clusterIcon: UIImage, animated: Bool) -> GMSMarker {
    let initialPosition = animated ? from : position
    let marker = GMSMarker(position: initialPosition)
    marker.userData! = userData
    if clusterIcon.cgImage != nil {
        marker.icon = clusterIcon
    }
    else {
        marker.icon = self.getCustomTitleItem(userData: userData)
        
    }
    marker.map = mapView
    if animated
    {
        CATransaction.begin()
        CAAnimation.init().duration = kGMUAnimationDuration
        marker.layer.latitude = position.latitude
        marker.layer.longitude = position.longitude
        CATransaction.commit()
    }
    return marker
}

func getCustomTitleItem(userData: AnyObject) -> UIImage {
    let item = userData as! POIItem
    return item.marker.icon!
 }
}

4 :

class CustomClusterIconGenerator: GMUDefaultClusterIconGenerator {

override func icon(forSize size: UInt) -> UIImage {
    let image = textToImage(drawText: (String(size) as NSString) as String,
                            inImage: HFAsset.ic_parking_clustering.image,
                            font: UIFont.init(name: "Montserrat-Medium", size: 12.0)!)
    return image
}

private func textToImage(drawText text: String, inImage image: UIImage, font: UIFont) -> UIImage {
    var pinCount = text
    if Int(text)! > 9999 {
        pinCount = "+9999"
    }
    
    UIGraphicsBeginImageContext(image.size)
    image.draw(in: CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height))
    
    let textStyle = NSMutableParagraphStyle()
    textStyle.alignment = NSTextAlignment.center
    let textColor = UIColor.white
    let attributes=[
        NSAttributedStringKey.font: font,
        NSAttributedStringKey.paragraphStyle: textStyle,
        NSAttributedStringKey.foregroundColor: textColor,
        NSAttributedStringKey.backgroundColor: appThemeColor]
    
    // vertically center (depending on font)
    let textH = font.lineHeight
    let textY = (image.size.height-textH)/3
    let textRect = CGRect(x: 0, y: textY, width: image.size.width, height: textH)
    pinCount.draw(in: textRect.integral, withAttributes: attributes)
    let result = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return result!
  }

 }

5 :

    func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool {
    
    if markerPin == marker {
        return true
    }
    
    if ((marker.userData as? GMUStaticCluster) != nil) {   // Cluster Pin tap
        UIView.animate(withDuration: 0.5, delay: 0.2, options: [.curveEaseOut],
                       animations: {
                        let newCamera = GMSCameraPosition.camera(withTarget: marker.position,
                                                                 zoom: self.mapView.camera.zoom + 0.8)
                        let update = GMSCameraUpdate.setCamera(newCamera)
                        self.mapView.animate(with: update)
        }, completion: {
            finished in
        })
    }
   
    if let poiItem = marker.userData as? POIItem {   // Cluster Parking Pin tap
        let Detail = self.storyboard?.instantiateViewController(withIdentifier: "HFParkingFullDetailsVC")as! HFParkingFullDetailsVC
        Detail.parkModel = (poiItem.userData as? AMParkModel)!
        self.navigationController?.pushViewController(Detail, animated: true)
    }
person Paresh Mangukiya    schedule 16.05.2019

Если вы включили исходные файлы Google-Maps-iOS-Utils в свой проект, есть один «грязный» способ изменить значок маркера.

К сожалению, общедоступных методов для установки пользовательского значка нет, но вы можете изменить его в исходном файле.

В Google Map Utils/Clustering/View/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"]
                                          animated:shouldAnimate];
 ...

}

Чем вы могли бы настроить свой менеджер кластера (Swift)

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


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

}
person A.Grz    schedule 25.07.2016
comment
Устарело, проверьте stackoverflow.com/a/53928566/5790492 и github.com/googlemaps/google-maps-ios-utils/blob/master/ - person Nik Kov; 27.06.2019

Если вам нужно только изменить значок или цвет, вы можете добавить ведра с несколькими цветами/изображениями при инициализации вашего GMUDefaultClusterIconGenerator (или только один, как в случае ниже, если вам нужен только один цвет). Я использовал большое число (больше, чем максимальное количество элементов кластера), чтобы все кластеры имели одинаковый цвет. Чтобы использовать несколько цветов, вы можете добавить несколько ведер и несколько цветов.

let iconGenerator = GMUDefaultClusterIconGenerator.init(buckets: [99999], backgroundColors: [UIColor.red])
let algorithm = GMUNonHierarchicalDistanceBasedAlgorithm()
let renderer = GMUDefaultClusterRenderer(mapView: googleMapView, clusterIconGenerator: iconGenerator)

clusterManager = GMUClusterManager(map: googleMapView, algorithm: algorithm, renderer: renderer)
clusterManager.setDelegate(self, mapDelegate: self)

Чтобы использовать изображение в качестве фона вашего кластера, вы можете предоставить группу backgroundImages для своих корзин:

let iconGenerator = GMUDefaultClusterIconGenerator.init(buckets: [99999], backgroundImages: [UIImage(named: "YOUR_IMAGE_HERE")!])
person Jon C    schedule 22.11.2019