Как реализовать 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 и функциями обновления.

 Feldur24 июл. 2016 г., 12:45
Вы должны показать, что вы пытались.

Ответы на вопрос(4)

на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.

Решение Вопроса

есть один «грязный» способ изменить значок маркера.

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

В Google Map Utils / Кластеризация / Просмотр / 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)

}
 Nik Kov27 июн. 2019 г., 17:23
 Pan Mluvčí03 окт. 2016 г., 13:31
благодарю вас. Это ответ, который я ищу.

Swift 4.2 :

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

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

willRenderMarker будет вызывать каждый раз, когда будет отображаться маркер (как cluster marker, и 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)

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

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.i,nit().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")

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

 nja10 янв. 2018 г., 17:39
В классе POIItem просто добавьте @objc перед "var marker", я отредактировал решение. Простите за поздний ответ.
 Aleksandrs Muravjovs14 авг. 2019 г., 15:46
Спасибо, это работает!!! Единственное, что по какой-то причине не работает, это то, что когда я нажимаю на кластер, он падает вместо увеличения.
 bradford gray06 нояб. 2017 г., 23:13
Мне нравится ваше решение, и оно работает для Swift 3, но теперь в Swift 4 вы получаете «неявную точку входа Objective C, добавьте явное '@objc' в объявление, чтобы испустить точку входа Objective C в Swift 4". Вы нашли другой обходной путь?
 bradford gray10 янв. 2018 г., 18:05
Спасибо за ответ.

Ваш ответ на вопрос