如何在 Swift 中实现 GMUClusterRenderer

How to implement GMUClusterRenderer in Swift

我正在为 iOS 使用 Google 地图 API 并且想使用标记聚类实用程序。我想出了如何显示集群标记,但我想自定义标记。有人可以解释如何 set/change 每个标记或群集标记的图标和标题吗?示例代码会很有帮助。

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 和更新函数。

如果您将 Google-Maps-iOS-Utils 源文件包含到您的项目中,则有一种 "dirty" 更改标记图标的方法。

很遗憾,没有 public 方法来设置自定义图标,但您可以在源文件中进行更改。

在Google地图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)

}

我设法找到 "clean" 解决方案,尽管它仍然令人困惑。但它有效!

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) 进入Google-Maps-iOS-Utils文件夹中的GMUDefaultClusterRenderer class,导入MarkerManager.h class找到并更改此方法:

// 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 class, 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 class 并覆盖 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 中,在 generateClusterItems 方法中初始化 POIItem:

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

现在您可以将逻辑设置为拥有多个自定义标记。

我在 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

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 是你的标记 class subclass NSObjectGMUClusterItem : (你可以使用默认的 class GMUClusterItem 但如果您需要一些其他属性,您可以将其子class)

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)

如果您只需要更改图标或颜色,您可以在初始化 GMUDefaultClusterIconGenerator 时添加具有多个 colors/images 的存储桶(如果您只需要一种颜色,则只需添加一个存储桶,如下例所示)。我使用了大量(高于集群项目的最大数量)以便所有集群都具有相同的颜色。要使用多种颜色,您可以添加多个桶和多种颜色。

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)

要使用图像作为集群背景,您可以为存储桶提供一组背景图像:

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