blob: b15ab70331f3d57ee04c22271de4b6f55fa85d44 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "GoogleMapController.h"
#import "JsonConversions.h"
#pragma mark - Conversion of JSON-like values sent via platform channels. Forward declarations.
static NSDictionary* PositionToJson(GMSCameraPosition* position);
static NSArray* LocationToJson(CLLocationCoordinate2D position);
static GMSCameraPosition* ToOptionalCameraPosition(NSDictionary* json);
static GMSCoordinateBounds* ToOptionalBounds(NSArray* json);
static GMSCameraUpdate* ToCameraUpdate(NSArray* data);
static NSDictionary* GMSCoordinateBoundsToJson(GMSCoordinateBounds* bounds);
static void InterpretMapOptions(NSDictionary* data, id<FLTGoogleMapOptionsSink> sink);
static double ToDouble(NSNumber* data) { return [FLTGoogleMapJsonConversions toDouble:data]; }
@implementation FLTGoogleMapFactory {
NSObject<FlutterPluginRegistrar>* _registrar;
}
- (instancetype)initWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
self = [super init];
if (self) {
_registrar = registrar;
}
return self;
}
- (NSObject<FlutterMessageCodec>*)createArgsCodec {
return [FlutterStandardMessageCodec sharedInstance];
}
- (NSObject<FlutterPlatformView>*)createWithFrame:(CGRect)frame
viewIdentifier:(int64_t)viewId
arguments:(id _Nullable)args {
return [[FLTGoogleMapController alloc] initWithFrame:frame
viewIdentifier:viewId
arguments:args
registrar:_registrar];
}
@end
@implementation FLTGoogleMapController {
GMSMapView* _mapView;
int64_t _viewId;
FlutterMethodChannel* _channel;
BOOL _trackCameraPosition;
NSObject<FlutterPluginRegistrar>* _registrar;
// Used for the temporary workaround for a bug that the camera is not properly positioned at
// initialization. https://github.com/flutter/flutter/issues/24806
// TODO(cyanglaz): Remove this temporary fix once the Maps SDK issue is resolved.
// https://github.com/flutter/flutter/issues/27550
BOOL _cameraDidInitialSetup;
FLTMarkersController* _markersController;
FLTPolylinesController* _polylinesController;
}
- (instancetype)initWithFrame:(CGRect)frame
viewIdentifier:(int64_t)viewId
arguments:(id _Nullable)args
registrar:(NSObject<FlutterPluginRegistrar>*)registrar {
if ([super init]) {
_viewId = viewId;
GMSCameraPosition* camera = ToOptionalCameraPosition(args[@"initialCameraPosition"]);
_mapView = [GMSMapView mapWithFrame:frame camera:camera];
_mapView.accessibilityElementsHidden = NO;
_trackCameraPosition = NO;
InterpretMapOptions(args[@"options"], self);
NSString* channelName =
[NSString stringWithFormat:@"plugins.flutter.io/google_maps_%lld", viewId];
_channel = [FlutterMethodChannel methodChannelWithName:channelName
binaryMessenger:registrar.messenger];
__weak __typeof__(self) weakSelf = self;
[_channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
if (weakSelf) {
[weakSelf onMethodCall:call result:result];
}
}];
_mapView.delegate = weakSelf;
_registrar = registrar;
_cameraDidInitialSetup = NO;
_markersController = [[FLTMarkersController alloc] init:_channel
mapView:_mapView
registrar:registrar];
_polylinesController = [[FLTPolylinesController alloc] init:_channel
mapView:_mapView
registrar:registrar];
id markersToAdd = args[@"markersToAdd"];
if ([markersToAdd isKindOfClass:[NSArray class]]) {
[_markersController addMarkers:markersToAdd];
}
id polylinesToAdd = args[@"polylinesToAdd"];
if ([polylinesToAdd isKindOfClass:[NSArray class]]) {
[_polylinesController addPolylines:polylinesToAdd];
}
}
return self;
}
- (UIView*)view {
return _mapView;
}
- (void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
if ([call.method isEqualToString:@"map#show"]) {
[self showAtX:ToDouble(call.arguments[@"x"]) Y:ToDouble(call.arguments[@"y"])];
result(nil);
} else if ([call.method isEqualToString:@"map#hide"]) {
[self hide];
result(nil);
} else if ([call.method isEqualToString:@"camera#animate"]) {
[self animateWithCameraUpdate:ToCameraUpdate(call.arguments[@"cameraUpdate"])];
result(nil);
} else if ([call.method isEqualToString:@"camera#move"]) {
[self moveWithCameraUpdate:ToCameraUpdate(call.arguments[@"cameraUpdate"])];
result(nil);
} else if ([call.method isEqualToString:@"map#update"]) {
InterpretMapOptions(call.arguments[@"options"], self);
result(PositionToJson([self cameraPosition]));
} else if ([call.method isEqualToString:@"map#getVisibleRegion"]) {
if (_mapView != nil) {
GMSVisibleRegion visibleRegion = _mapView.projection.visibleRegion;
GMSCoordinateBounds* bounds = [[GMSCoordinateBounds alloc] initWithRegion:visibleRegion];
result(GMSCoordinateBoundsToJson(bounds));
} else {
result([FlutterError errorWithCode:@"GoogleMap uninitialized"
message:@"getVisibleRegion called prior to map initialization"
details:nil]);
}
} else if ([call.method isEqualToString:@"map#waitForMap"]) {
result(nil);
} else if ([call.method isEqualToString:@"markers#update"]) {
id markersToAdd = call.arguments[@"markersToAdd"];
if ([markersToAdd isKindOfClass:[NSArray class]]) {
[_markersController addMarkers:markersToAdd];
}
id markersToChange = call.arguments[@"markersToChange"];
if ([markersToChange isKindOfClass:[NSArray class]]) {
[_markersController changeMarkers:markersToChange];
}
id markerIdsToRemove = call.arguments[@"markerIdsToRemove"];
if ([markerIdsToRemove isKindOfClass:[NSArray class]]) {
[_markersController removeMarkerIds:markerIdsToRemove];
}
result(nil);
} else if ([call.method isEqualToString:@"polylines#update"]) {
id polylinesToAdd = call.arguments[@"polylinesToAdd"];
if ([polylinesToAdd isKindOfClass:[NSArray class]]) {
[_polylinesController addPolylines:polylinesToAdd];
}
id polylinesToChange = call.arguments[@"polylinesToChange"];
if ([polylinesToChange isKindOfClass:[NSArray class]]) {
[_polylinesController changePolylines:polylinesToChange];
}
id polylineIdsToRemove = call.arguments[@"polylineIdsToRemove"];
if ([polylineIdsToRemove isKindOfClass:[NSArray class]]) {
[_polylinesController removePolylineIds:polylineIdsToRemove];
}
result(nil);
} else if ([call.method isEqualToString:@"map#isCompassEnabled"]) {
NSNumber* isCompassEnabled = @(_mapView.settings.compassButton);
result(isCompassEnabled);
} else if ([call.method isEqualToString:@"map#getMinMaxZoomLevels"]) {
NSArray* zoomLevels = @[ @(_mapView.minZoom), @(_mapView.maxZoom) ];
result(zoomLevels);
} else if ([call.method isEqualToString:@"map#isZoomGesturesEnabled"]) {
NSNumber* isZoomGesturesEnabled = @(_mapView.settings.zoomGestures);
result(isZoomGesturesEnabled);
} else if ([call.method isEqualToString:@"map#isTiltGesturesEnabled"]) {
NSNumber* isTiltGesturesEnabled = @(_mapView.settings.tiltGestures);
result(isTiltGesturesEnabled);
} else if ([call.method isEqualToString:@"map#isRotateGesturesEnabled"]) {
NSNumber* isRotateGesturesEnabled = @(_mapView.settings.rotateGestures);
result(isRotateGesturesEnabled);
} else if ([call.method isEqualToString:@"map#isScrollGesturesEnabled"]) {
NSNumber* isScrollGesturesEnabled = @(_mapView.settings.scrollGestures);
result(isScrollGesturesEnabled);
} else {
result(FlutterMethodNotImplemented);
}
}
- (void)showAtX:(CGFloat)x Y:(CGFloat)y {
_mapView.frame =
CGRectMake(x, y, CGRectGetWidth(_mapView.frame), CGRectGetHeight(_mapView.frame));
_mapView.hidden = NO;
}
- (void)hide {
_mapView.hidden = YES;
}
- (void)animateWithCameraUpdate:(GMSCameraUpdate*)cameraUpdate {
[_mapView animateWithCameraUpdate:cameraUpdate];
}
- (void)moveWithCameraUpdate:(GMSCameraUpdate*)cameraUpdate {
[_mapView moveCamera:cameraUpdate];
}
- (GMSCameraPosition*)cameraPosition {
if (_trackCameraPosition) {
return _mapView.camera;
} else {
return nil;
}
}
#pragma mark - FLTGoogleMapOptionsSink methods
- (void)setCamera:(GMSCameraPosition*)camera {
_mapView.camera = camera;
}
- (void)setCameraTargetBounds:(GMSCoordinateBounds*)bounds {
_mapView.cameraTargetBounds = bounds;
}
- (void)setCompassEnabled:(BOOL)enabled {
_mapView.settings.compassButton = enabled;
}
- (void)setMapType:(GMSMapViewType)mapType {
_mapView.mapType = mapType;
}
- (void)setMinZoom:(float)minZoom maxZoom:(float)maxZoom {
[_mapView setMinZoom:minZoom maxZoom:maxZoom];
}
- (void)setRotateGesturesEnabled:(BOOL)enabled {
_mapView.settings.rotateGestures = enabled;
}
- (void)setScrollGesturesEnabled:(BOOL)enabled {
_mapView.settings.scrollGestures = enabled;
}
- (void)setTiltGesturesEnabled:(BOOL)enabled {
_mapView.settings.tiltGestures = enabled;
}
- (void)setTrackCameraPosition:(BOOL)enabled {
_trackCameraPosition = enabled;
}
- (void)setZoomGesturesEnabled:(BOOL)enabled {
_mapView.settings.zoomGestures = enabled;
}
- (void)setMyLocationEnabled:(BOOL)enabled {
_mapView.myLocationEnabled = enabled;
_mapView.settings.myLocationButton = enabled;
}
#pragma mark - GMSMapViewDelegate methods
- (void)mapView:(GMSMapView*)mapView willMove:(BOOL)gesture {
[_channel invokeMethod:@"camera#onMoveStarted" arguments:@{@"isGesture" : @(gesture)}];
}
- (void)mapView:(GMSMapView*)mapView didChangeCameraPosition:(GMSCameraPosition*)position {
if (!_cameraDidInitialSetup) {
// We suspected a bug in the iOS Google Maps SDK caused the camera is not properly positioned at
// initialization. https://github.com/flutter/flutter/issues/24806
// This temporary workaround fix is provided while the actual fix in the Google Maps SDK is
// still being investigated.
// TODO(cyanglaz): Remove this temporary fix once the Maps SDK issue is resolved.
// https://github.com/flutter/flutter/issues/27550
_cameraDidInitialSetup = YES;
[mapView moveCamera:[GMSCameraUpdate setCamera:_mapView.camera]];
}
if (_trackCameraPosition) {
[_channel invokeMethod:@"camera#onMove" arguments:@{@"position" : PositionToJson(position)}];
}
}
- (void)mapView:(GMSMapView*)mapView idleAtCameraPosition:(GMSCameraPosition*)position {
[_channel invokeMethod:@"camera#onIdle" arguments:@{}];
}
- (BOOL)mapView:(GMSMapView*)mapView didTapMarker:(GMSMarker*)marker {
NSString* markerId = marker.userData[0];
return [_markersController onMarkerTap:markerId];
}
- (void)mapView:(GMSMapView*)mapView didTapInfoWindowOfMarker:(GMSMarker*)marker {
NSString* markerId = marker.userData[0];
[_markersController onInfoWindowTap:markerId];
}
- (void)mapView:(GMSMapView*)mapView didTapOverlay:(GMSOverlay*)overlay {
NSString* polylineId = overlay.userData[0];
[_polylinesController onPolylineTap:polylineId];
}
- (void)mapView:(GMSMapView*)mapView didTapAtCoordinate:(CLLocationCoordinate2D)coordinate {
[_channel invokeMethod:@"map#onTap" arguments:@{@"position" : LocationToJson(coordinate)}];
}
@end
#pragma mark - Implementations of JSON conversion functions.
static NSArray* LocationToJson(CLLocationCoordinate2D position) {
return @[ @(position.latitude), @(position.longitude) ];
}
static NSDictionary* PositionToJson(GMSCameraPosition* position) {
if (!position) {
return nil;
}
return @{
@"target" : LocationToJson([position target]),
@"zoom" : @([position zoom]),
@"bearing" : @([position bearing]),
@"tilt" : @([position viewingAngle]),
};
}
static NSDictionary* GMSCoordinateBoundsToJson(GMSCoordinateBounds* bounds) {
if (!bounds) {
return nil;
}
return @{
@"southwest" : LocationToJson([bounds southWest]),
@"northeast" : LocationToJson([bounds northEast]),
};
}
static float ToFloat(NSNumber* data) { return [FLTGoogleMapJsonConversions toFloat:data]; }
static CLLocationCoordinate2D ToLocation(NSArray* data) {
return [FLTGoogleMapJsonConversions toLocation:data];
}
static int ToInt(NSNumber* data) { return [FLTGoogleMapJsonConversions toInt:data]; }
static BOOL ToBool(NSNumber* data) { return [FLTGoogleMapJsonConversions toBool:data]; }
static CGPoint ToPoint(NSArray* data) { return [FLTGoogleMapJsonConversions toPoint:data]; }
static GMSCameraPosition* ToCameraPosition(NSDictionary* data) {
return [GMSCameraPosition cameraWithTarget:ToLocation(data[@"target"])
zoom:ToFloat(data[@"zoom"])
bearing:ToDouble(data[@"bearing"])
viewingAngle:ToDouble(data[@"tilt"])];
}
static GMSCameraPosition* ToOptionalCameraPosition(NSDictionary* json) {
return json ? ToCameraPosition(json) : nil;
}
static GMSCoordinateBounds* ToBounds(NSArray* data) {
return [[GMSCoordinateBounds alloc] initWithCoordinate:ToLocation(data[0])
coordinate:ToLocation(data[1])];
}
static GMSCoordinateBounds* ToOptionalBounds(NSArray* data) {
return (data[0] == [NSNull null]) ? nil : ToBounds(data[0]);
}
static GMSMapViewType ToMapViewType(NSNumber* json) {
int value = ToInt(json);
return (GMSMapViewType)(value == 0 ? 5 : value);
}
static GMSCameraUpdate* ToCameraUpdate(NSArray* data) {
NSString* update = data[0];
if ([update isEqualToString:@"newCameraPosition"]) {
return [GMSCameraUpdate setCamera:ToCameraPosition(data[1])];
} else if ([update isEqualToString:@"newLatLng"]) {
return [GMSCameraUpdate setTarget:ToLocation(data[1])];
} else if ([update isEqualToString:@"newLatLngBounds"]) {
return [GMSCameraUpdate fitBounds:ToBounds(data[1]) withPadding:ToDouble(data[2])];
} else if ([update isEqualToString:@"newLatLngZoom"]) {
return [GMSCameraUpdate setTarget:ToLocation(data[1]) zoom:ToFloat(data[2])];
} else if ([update isEqualToString:@"scrollBy"]) {
return [GMSCameraUpdate scrollByX:ToDouble(data[1]) Y:ToDouble(data[2])];
} else if ([update isEqualToString:@"zoomBy"]) {
if (data.count == 2) {
return [GMSCameraUpdate zoomBy:ToFloat(data[1])];
} else {
return [GMSCameraUpdate zoomBy:ToFloat(data[1]) atPoint:ToPoint(data[2])];
}
} else if ([update isEqualToString:@"zoomIn"]) {
return [GMSCameraUpdate zoomIn];
} else if ([update isEqualToString:@"zoomOut"]) {
return [GMSCameraUpdate zoomOut];
} else if ([update isEqualToString:@"zoomTo"]) {
return [GMSCameraUpdate zoomTo:ToFloat(data[1])];
}
return nil;
}
static void InterpretMapOptions(NSDictionary* data, id<FLTGoogleMapOptionsSink> sink) {
NSArray* cameraTargetBounds = data[@"cameraTargetBounds"];
if (cameraTargetBounds) {
[sink setCameraTargetBounds:ToOptionalBounds(cameraTargetBounds)];
}
NSNumber* compassEnabled = data[@"compassEnabled"];
if (compassEnabled) {
[sink setCompassEnabled:ToBool(compassEnabled)];
}
NSNumber* mapType = data[@"mapType"];
if (mapType) {
[sink setMapType:ToMapViewType(mapType)];
}
NSArray* zoomData = data[@"minMaxZoomPreference"];
if (zoomData) {
float minZoom = (zoomData[0] == [NSNull null]) ? kGMSMinZoomLevel : ToFloat(zoomData[0]);
float maxZoom = (zoomData[1] == [NSNull null]) ? kGMSMaxZoomLevel : ToFloat(zoomData[1]);
[sink setMinZoom:minZoom maxZoom:maxZoom];
}
NSNumber* rotateGesturesEnabled = data[@"rotateGesturesEnabled"];
if (rotateGesturesEnabled) {
[sink setRotateGesturesEnabled:ToBool(rotateGesturesEnabled)];
}
NSNumber* scrollGesturesEnabled = data[@"scrollGesturesEnabled"];
if (scrollGesturesEnabled) {
[sink setScrollGesturesEnabled:ToBool(scrollGesturesEnabled)];
}
NSNumber* tiltGesturesEnabled = data[@"tiltGesturesEnabled"];
if (tiltGesturesEnabled) {
[sink setTiltGesturesEnabled:ToBool(tiltGesturesEnabled)];
}
NSNumber* trackCameraPosition = data[@"trackCameraPosition"];
if (trackCameraPosition) {
[sink setTrackCameraPosition:ToBool(trackCameraPosition)];
}
NSNumber* zoomGesturesEnabled = data[@"zoomGesturesEnabled"];
if (zoomGesturesEnabled) {
[sink setZoomGesturesEnabled:ToBool(zoomGesturesEnabled)];
}
NSNumber* myLocationEnabled = data[@"myLocationEnabled"];
if (myLocationEnabled) {
[sink setMyLocationEnabled:ToBool(myLocationEnabled)];
}
}