It appears the issue is with the ios/Plugin/Map.swift
that is included with the npm
package. This is the file that is included with the package:
import Foundation
import GoogleMaps
import Capacitor
import GoogleMapsUtils
public struct LatLng: Codable {
let lat: Double
let lng: Double
}
class GMViewController: UIViewController {
var mapViewBounds: [String: Double]!
var GMapView: GMSMapView!
var cameraPosition: [String: Double]!
private var clusterManager: GMUClusterManager?
var clusteringEnabled: Bool {
return clusterManager != nil
}
override func viewDidLoad() {
super.viewDidLoad()
let camera = GMSCameraPosition.camera(withLatitude: cameraPosition["latitude"] ?? 0, longitude: cameraPosition["longitude"] ?? 0, zoom: Float(cameraPosition["zoom"] ?? 12))
let frame = CGRect(x: mapViewBounds["x"] ?? 0, y: mapViewBounds["y"] ?? 0, width: mapViewBounds["width"] ?? 0, height: mapViewBounds["height"] ?? 0)
self.GMapView = GMSMapView.map(withFrame: frame, camera: camera)
self.view = GMapView
}
func initClusterManager() {
let iconGenerator = GMUDefaultClusterIconGenerator()
let algorithm = GMUNonHierarchicalDistanceBasedAlgorithm()
let renderer = GMUDefaultClusterRenderer(mapView: self.GMapView, clusterIconGenerator: iconGenerator)
self.clusterManager = GMUClusterManager(map: self.GMapView, algorithm: algorithm, renderer: renderer)
}
func destroyClusterManager() {
self.clusterManager = nil
}
func addMarkersToCluster(markers: [GMSMarker]) {
if let clusterManager = clusterManager {
clusterManager.add(markers)
clusterManager.cluster()
}
}
func removeMarkersFromCluster(markers: [GMSMarker]) {
if let clusterManager = clusterManager {
markers.forEach { marker in
clusterManager.remove(marker)
}
clusterManager.cluster()
}
}
}
public class Map {
var id: String
var config: GoogleMapConfig
var mapViewController: GMViewController
var targetViewController: UIView?
var markers = [Int: GMSMarker]()
private var delegate: CapacitorGoogleMapsPlugin
init(id: String, config: GoogleMapConfig, delegate: CapacitorGoogleMapsPlugin) {
self.id = id
self.config = config
self.delegate = delegate
self.mapViewController = GMViewController()
self.render()
}
func render() {
DispatchQueue.main.async {
self.mapViewController.mapViewBounds = [
"width": self.config.width,
"height": self.config.height,
"x": self.config.x,
"y": self.config.y
]
self.mapViewController.cameraPosition = [
"latitude": self.config.center.lat,
"longitude": self.config.center.lng,
"zoom": self.config.zoom
]
if let bridge = self.delegate.bridge {
for item in bridge.webView!.getAllSubViews() {
if let typeClass = NSClassFromString("WKChildScrollView"), item.isKind(of: typeClass) {
(item as? UIScrollView)?.isScrollEnabled = true
if item.bounds.width == self.config.width && item.bounds.height == self.config.height && (item as? UIView)?.tag == 0 {
self.targetViewController = item
break
}
}
}
if let target = self.targetViewController {
target.tag = 1
target.removeAllSubview()
self.mapViewController.view.frame = target.bounds
target.addSubview(self.mapViewController.view)
self.mapViewController.GMapView.delegate = self.delegate
}
self.delegate.notifyListeners("onMapReady", data: [
"mapId": self.id
])
}
}
}
func updateRender(frame: CGRect, mapBounds: CGRect) {
DispatchQueue.main.async {
self.mapViewController.view.layer.mask = nil
var updatedFrame = self.mapViewController.view.frame
updatedFrame.origin.x = mapBounds.origin.x
updatedFrame.origin.y = mapBounds.origin.y
self.mapViewController.view.frame = updatedFrame
var maskBounds: [CGRect] = []
if !frame.contains(mapBounds) {
maskBounds.append(contentsOf: self.getFrameOverflowBounds(frame: frame, mapBounds: mapBounds))
}
if maskBounds.count > 0 {
let maskLayer = CAShapeLayer()
let path = CGMutablePath()
path.addRect(self.mapViewController.view.bounds)
maskBounds.forEach { b in
path.addRect(b)
}
maskLayer.path = path
maskLayer.fillRule = .evenOdd
self.mapViewController.view.layer.mask = maskLayer
}
self.mapViewController.view.layoutIfNeeded()
}
}
func destroy() {
DispatchQueue.main.async {
self.targetViewController?.tag = 0
self.mapViewController.view = nil
}
}
func addMarker(marker: Marker) throws -> Int {
var markerHash = 0
DispatchQueue.main.sync {
let newMarker = GMSMarker()
newMarker.position = CLLocationCoordinate2D(latitude: marker.coordinate.lat, longitude: marker.coordinate.lng)
newMarker.title = marker.title
newMarker.snippet = marker.snippet
newMarker.isFlat = marker.isFlat ?? false
newMarker.opacity = marker.opacity ?? 1
newMarker.isDraggable = marker.draggable ?? false
if self.mapViewController.clusteringEnabled {
self.mapViewController.addMarkersToCluster(markers: [newMarker])
} else {
newMarker.map = self.mapViewController.GMapView
}
self.markers[newMarker.hash.hashValue] = newMarker
markerHash = newMarker.hash.hashValue
}
return markerHash
}
func addMarkers(markers: [Marker]) throws -> [Int] {
var markerHashes: [Int] = []
DispatchQueue.main.sync {
var googleMapsMarkers: [GMSMarker] = []
markers.forEach { marker in
let newMarker = GMSMarker()
newMarker.position = CLLocationCoordinate2D(latitude: marker.coordinate.lat, longitude: marker.coordinate.lng)
newMarker.title = marker.title
newMarker.snippet = marker.snippet
newMarker.isFlat = marker.isFlat ?? false
newMarker.opacity = marker.opacity ?? 1
newMarker.isDraggable = marker.draggable ?? false
if self.mapViewController.clusteringEnabled {
googleMapsMarkers.append(newMarker)
} else {
newMarker.map = self.mapViewController.GMapView
}
self.markers[newMarker.hash.hashValue] = newMarker
markerHashes.append(newMarker.hash.hashValue)
}
if self.mapViewController.clusteringEnabled {
self.mapViewController.addMarkersToCluster(markers: googleMapsMarkers)
}
}
return markerHashes
}
func enableClustering() {
if !self.mapViewController.clusteringEnabled {
DispatchQueue.main.sync {
self.mapViewController.initClusterManager()
// add existing markers to the cluster
if !self.markers.isEmpty {
var existingMarkers: [GMSMarker] = []
for (_, marker) in self.markers {
marker.map = nil
existingMarkers.append(marker)
}
self.mapViewController.addMarkersToCluster(markers: existingMarkers)
}
}
}
}
func disableClustering() {
DispatchQueue.main.sync {
self.mapViewController.destroyClusterManager()
// add existing markers back to the map
if !self.markers.isEmpty {
for (_, marker) in self.markers {
marker.map = self.mapViewController.GMapView
}
}
}
}
func removeMarker(id: Int) throws {
if let marker = self.markers[id] {
DispatchQueue.main.async {
if self.mapViewController.clusteringEnabled {
self.mapViewController.removeMarkersFromCluster(markers: [marker])
}
marker.map = nil
self.markers.removeValue(forKey: id)
}
} else {
throw GoogleMapErrors.markerNotFound
}
}
func setCamera(config: GoogleMapCameraConfig) throws {
let currentCamera = self.mapViewController.GMapView.camera
let lat = config.coordinate?.lat ?? currentCamera.target.latitude
let lng = config.coordinate?.lng ?? currentCamera.target.longitude
let zoom = config.zoom ?? currentCamera.zoom
let bearing = config.bearing ?? Double(currentCamera.bearing)
let angle = config.angle ?? currentCamera.viewingAngle
let animate = config.animate ?? false
DispatchQueue.main.sync {
let newCamera = GMSCameraPosition(latitude: lat, longitude: lng, zoom: zoom, bearing: bearing, viewingAngle: angle)
if animate {
self.mapViewController.GMapView.animate(to: newCamera)
} else {
self.mapViewController.GMapView.camera = newCamera
}
}
}
func setMapType(mapType: GMSMapViewType) throws {
DispatchQueue.main.sync {
self.mapViewController.GMapView.mapType = mapType
}
}
func enableIndoorMaps(enabled: Bool) throws {
DispatchQueue.main.sync {
self.mapViewController.GMapView.isIndoorEnabled = enabled
}
}
func enableTrafficLayer(enabled: Bool) throws {
DispatchQueue.main.sync {
self.mapViewController.GMapView.isTrafficEnabled = enabled
}
}
func enableAccessibilityElements(enabled: Bool) throws {
DispatchQueue.main.sync {
self.mapViewController.GMapView.accessibilityElementsHidden = enabled
}
}
func enableCurrentLocation(enabled: Bool) throws {
DispatchQueue.main.sync {
self.mapViewController.GMapView.isMyLocationEnabled = enabled
}
}
func setPadding(padding: GoogleMapPadding) throws {
DispatchQueue.main.sync {
let mapInsets = UIEdgeInsets(top: CGFloat(padding.top), left: CGFloat(padding.left), bottom: CGFloat(padding.bottom), right: CGFloat(padding.right))
self.mapViewController.GMapView.padding = mapInsets
}
}
func removeMarkers(ids: [Int]) throws {
DispatchQueue.main.sync {
var markers: [GMSMarker] = []
ids.forEach { id in
if let marker = self.markers[id] {
marker.map = nil
self.markers.removeValue(forKey: id)
markers.append(marker)
}
}
if self.mapViewController.clusteringEnabled {
self.mapViewController.removeMarkersFromCluster(markers: markers)
}
}
}
private func getFrameOverflowBounds(frame: CGRect, mapBounds: CGRect) -> [CGRect] {
var intersections: [CGRect] = []
// get top overflow
if mapBounds.origin.y < frame.origin.y {
let height = frame.origin.y - mapBounds.origin.y
let width = mapBounds.width
intersections.append(CGRect(x: 0, y: 0, width: width, height: height))
}
// get bottom overflow
if (mapBounds.origin.y + mapBounds.height) > (frame.origin.y + frame.height) {
let height = (mapBounds.origin.y + mapBounds.height) - (frame.origin.y + frame.height)
let width = mapBounds.width
intersections.append(CGRect(x: 0, y: mapBounds.height, width: width, height: height))
}
return intersections
}
}
extension WKWebView {
override open func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
var hitView = super.hitTest(point, with: event)
if let typeClass = NSClassFromString("WKChildScrollView"), let tempHitView = hitView, tempHitView.isKind(of: typeClass) {
for item in tempHitView.subviews.reversed() {
let convertPoint = item.convert(point, from: self)
if let hitTestView = item.hitTest(convertPoint, with: event) {
hitView = hitTestView
break
}
}
}
return hitView
}
}
extension UIView {
private static var allSubviews: [UIView] = []
private func viewArray(root: UIView) -> [UIView] {
for view in root.subviews {
if view.isKind(of: UIView.self) {
UIView.allSubviews.append(view)
}
_ = viewArray(root: view)
}
return UIView.allSubviews
}
fileprivate func getAllSubViews() -> [UIView] {
UIView.allSubviews = []
return viewArray(root: self)
}
fileprivate func removeAllSubview() {
subviews.forEach {
$0.removeFromSuperview()
}
}
}
Compared to the file that is in the GitHub repo: https://raw.githubusercontent.com/ionic-team/capacitor-plugins/main/google-maps/ios/Plugin/Map.swift
Once I updated to that file, everything was working, as expected.