blob: dd07e791a888fc8b812369e358811bbbc1ff3d28 [file] [log] [blame]
// Copyright 2013 The Flutter 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 "GoogleMapMarkerController.h"
#import "FLTGoogleMapJSONConversions.h"
@interface FLTGoogleMapMarkerController ()
@property(strong, nonatomic) GMSMarker *marker;
@property(weak, nonatomic) GMSMapView *mapView;
@property(assign, nonatomic, readwrite) BOOL consumeTapEvents;
@end
@implementation FLTGoogleMapMarkerController
- (instancetype)initMarkerWithPosition:(CLLocationCoordinate2D)position
identifier:(NSString *)identifier
mapView:(GMSMapView *)mapView {
self = [super init];
if (self) {
_marker = [GMSMarker markerWithPosition:position];
_mapView = mapView;
_marker.userData = @[ identifier ];
}
return self;
}
- (void)showInfoWindow {
self.mapView.selectedMarker = self.marker;
}
- (void)hideInfoWindow {
if (self.mapView.selectedMarker == self.marker) {
self.mapView.selectedMarker = nil;
}
}
- (BOOL)isInfoWindowShown {
return self.mapView.selectedMarker == self.marker;
}
- (void)removeMarker {
self.marker.map = nil;
}
- (void)setAlpha:(float)alpha {
self.marker.opacity = alpha;
}
- (void)setAnchor:(CGPoint)anchor {
self.marker.groundAnchor = anchor;
}
- (void)setDraggable:(BOOL)draggable {
self.marker.draggable = draggable;
}
- (void)setFlat:(BOOL)flat {
self.marker.flat = flat;
}
- (void)setIcon:(UIImage *)icon {
self.marker.icon = icon;
}
- (void)setInfoWindowAnchor:(CGPoint)anchor {
self.marker.infoWindowAnchor = anchor;
}
- (void)setInfoWindowTitle:(NSString *)title snippet:(NSString *)snippet {
self.marker.title = title;
self.marker.snippet = snippet;
}
- (void)setPosition:(CLLocationCoordinate2D)position {
self.marker.position = position;
}
- (void)setRotation:(CLLocationDegrees)rotation {
self.marker.rotation = rotation;
}
- (void)setVisible:(BOOL)visible {
self.marker.map = visible ? self.mapView : nil;
}
- (void)setZIndex:(int)zIndex {
self.marker.zIndex = zIndex;
}
- (void)interpretMarkerOptions:(NSDictionary *)data
registrar:(NSObject<FlutterPluginRegistrar> *)registrar {
NSNumber *alpha = data[@"alpha"];
if (alpha && alpha != (id)[NSNull null]) {
[self setAlpha:[alpha floatValue]];
}
NSArray *anchor = data[@"anchor"];
if (anchor && anchor != (id)[NSNull null]) {
[self setAnchor:[FLTGoogleMapJSONConversions pointFromArray:anchor]];
}
NSNumber *draggable = data[@"draggable"];
if (draggable && draggable != (id)[NSNull null]) {
[self setDraggable:[draggable boolValue]];
}
NSArray *icon = data[@"icon"];
if (icon && icon != (id)[NSNull null]) {
UIImage *image = [self extractIconFromData:icon registrar:registrar];
[self setIcon:image];
}
NSNumber *flat = data[@"flat"];
if (flat && flat != (id)[NSNull null]) {
[self setFlat:[flat boolValue]];
}
NSNumber *consumeTapEvents = data[@"consumeTapEvents"];
if (consumeTapEvents && consumeTapEvents != (id)[NSNull null]) {
[self setConsumeTapEvents:[consumeTapEvents boolValue]];
}
[self interpretInfoWindow:data];
NSArray *position = data[@"position"];
if (position && position != (id)[NSNull null]) {
[self setPosition:[FLTGoogleMapJSONConversions locationFromLatLong:position]];
}
NSNumber *rotation = data[@"rotation"];
if (rotation && rotation != (id)[NSNull null]) {
[self setRotation:[rotation doubleValue]];
}
NSNumber *visible = data[@"visible"];
if (visible && visible != (id)[NSNull null]) {
[self setVisible:[visible boolValue]];
}
NSNumber *zIndex = data[@"zIndex"];
if (zIndex && zIndex != (id)[NSNull null]) {
[self setZIndex:[zIndex intValue]];
}
}
- (void)interpretInfoWindow:(NSDictionary *)data {
NSDictionary *infoWindow = data[@"infoWindow"];
if (infoWindow && infoWindow != (id)[NSNull null]) {
NSString *title = infoWindow[@"title"];
NSString *snippet = infoWindow[@"snippet"];
if (title && title != (id)[NSNull null]) {
[self setInfoWindowTitle:title snippet:snippet];
}
NSArray *infoWindowAnchor = infoWindow[@"infoWindowAnchor"];
if (infoWindowAnchor && infoWindowAnchor != (id)[NSNull null]) {
[self setInfoWindowAnchor:[FLTGoogleMapJSONConversions pointFromArray:infoWindowAnchor]];
}
}
}
- (UIImage *)extractIconFromData:(NSArray *)iconData
registrar:(NSObject<FlutterPluginRegistrar> *)registrar {
UIImage *image;
if ([iconData.firstObject isEqualToString:@"defaultMarker"]) {
CGFloat hue = (iconData.count == 1) ? 0.0f : [iconData[1] doubleValue];
image = [GMSMarker markerImageWithColor:[UIColor colorWithHue:hue / 360.0
saturation:1.0
brightness:0.7
alpha:1.0]];
} else if ([iconData.firstObject isEqualToString:@"fromAsset"]) {
if (iconData.count == 2) {
image = [UIImage imageNamed:[registrar lookupKeyForAsset:iconData[1]]];
} else {
image = [UIImage imageNamed:[registrar lookupKeyForAsset:iconData[1]
fromPackage:iconData[2]]];
}
} else if ([iconData.firstObject isEqualToString:@"fromAssetImage"]) {
if (iconData.count == 3) {
image = [UIImage imageNamed:[registrar lookupKeyForAsset:iconData[1]]];
id scaleParam = iconData[2];
image = [self scaleImage:image by:scaleParam];
} else {
NSString *error =
[NSString stringWithFormat:@"'fromAssetImage' should have exactly 3 arguments. Got: %lu",
(unsigned long)iconData.count];
NSException *exception = [NSException exceptionWithName:@"InvalidBitmapDescriptor"
reason:error
userInfo:nil];
@throw exception;
}
} else if ([iconData[0] isEqualToString:@"fromBytes"]) {
if (iconData.count == 2) {
@try {
FlutterStandardTypedData *byteData = iconData[1];
CGFloat screenScale = [[UIScreen mainScreen] scale];
image = [UIImage imageWithData:[byteData data] scale:screenScale];
} @catch (NSException *exception) {
@throw [NSException exceptionWithName:@"InvalidByteDescriptor"
reason:@"Unable to interpret bytes as a valid image."
userInfo:nil];
}
} else {
NSString *error = [NSString
stringWithFormat:@"fromBytes should have exactly one argument, the bytes. Got: %lu",
(unsigned long)iconData.count];
NSException *exception = [NSException exceptionWithName:@"InvalidByteDescriptor"
reason:error
userInfo:nil];
@throw exception;
}
}
return image;
}
- (UIImage *)scaleImage:(UIImage *)image by:(id)scaleParam {
double scale = 1.0;
if ([scaleParam isKindOfClass:[NSNumber class]]) {
scale = [scaleParam doubleValue];
}
if (fabs(scale - 1) > 1e-3) {
return [UIImage imageWithCGImage:[image CGImage]
scale:(image.scale * scale)
orientation:(image.imageOrientation)];
}
return image;
}
@end
@interface FLTMarkersController ()
@property(strong, nonatomic) NSMutableDictionary *markerIdentifierToController;
@property(strong, nonatomic) FlutterMethodChannel *methodChannel;
@property(weak, nonatomic) NSObject<FlutterPluginRegistrar> *registrar;
@property(weak, nonatomic) GMSMapView *mapView;
@end
@implementation FLTMarkersController
- (instancetype)initWithMethodChannel:(FlutterMethodChannel *)methodChannel
mapView:(GMSMapView *)mapView
registrar:(NSObject<FlutterPluginRegistrar> *)registrar {
self = [super init];
if (self) {
_methodChannel = methodChannel;
_mapView = mapView;
_markerIdentifierToController = [[NSMutableDictionary alloc] init];
_registrar = registrar;
}
return self;
}
- (void)addMarkers:(NSArray *)markersToAdd {
for (NSDictionary *marker in markersToAdd) {
CLLocationCoordinate2D position = [FLTMarkersController getPosition:marker];
NSString *identifier = marker[@"markerId"];
FLTGoogleMapMarkerController *controller =
[[FLTGoogleMapMarkerController alloc] initMarkerWithPosition:position
identifier:identifier
mapView:self.mapView];
[controller interpretMarkerOptions:marker registrar:self.registrar];
self.markerIdentifierToController[identifier] = controller;
}
}
- (void)changeMarkers:(NSArray *)markersToChange {
for (NSDictionary *marker in markersToChange) {
NSString *identifier = marker[@"markerId"];
FLTGoogleMapMarkerController *controller = self.markerIdentifierToController[identifier];
if (!controller) {
continue;
}
[controller interpretMarkerOptions:marker registrar:self.registrar];
}
}
- (void)removeMarkersWithIdentifiers:(NSArray *)identifiers {
for (NSString *identifier in identifiers) {
FLTGoogleMapMarkerController *controller = self.markerIdentifierToController[identifier];
if (!controller) {
continue;
}
[controller removeMarker];
[self.markerIdentifierToController removeObjectForKey:identifier];
}
}
- (BOOL)didTapMarkerWithIdentifier:(NSString *)identifier {
if (!identifier) {
return NO;
}
FLTGoogleMapMarkerController *controller = self.markerIdentifierToController[identifier];
if (!controller) {
return NO;
}
[self.methodChannel invokeMethod:@"marker#onTap" arguments:@{@"markerId" : identifier}];
return controller.consumeTapEvents;
}
- (void)didStartDraggingMarkerWithIdentifier:(NSString *)identifier
location:(CLLocationCoordinate2D)location {
if (!identifier) {
return;
}
FLTGoogleMapMarkerController *controller = self.markerIdentifierToController[identifier];
if (!controller) {
return;
}
[self.methodChannel invokeMethod:@"marker#onDragStart"
arguments:@{
@"markerId" : identifier,
@"position" : [FLTGoogleMapJSONConversions arrayFromLocation:location]
}];
}
- (void)didDragMarkerWithIdentifier:(NSString *)identifier
location:(CLLocationCoordinate2D)location {
if (!identifier) {
return;
}
FLTGoogleMapMarkerController *controller = self.markerIdentifierToController[identifier];
if (!controller) {
return;
}
[self.methodChannel invokeMethod:@"marker#onDrag"
arguments:@{
@"markerId" : identifier,
@"position" : [FLTGoogleMapJSONConversions arrayFromLocation:location]
}];
}
- (void)didEndDraggingMarkerWithIdentifier:(NSString *)identifier
location:(CLLocationCoordinate2D)location {
FLTGoogleMapMarkerController *controller = self.markerIdentifierToController[identifier];
if (!controller) {
return;
}
[self.methodChannel invokeMethod:@"marker#onDragEnd"
arguments:@{
@"markerId" : identifier,
@"position" : [FLTGoogleMapJSONConversions arrayFromLocation:location]
}];
}
- (void)didTapInfoWindowOfMarkerWithIdentifier:(NSString *)identifier {
if (identifier && self.markerIdentifierToController[identifier]) {
[self.methodChannel invokeMethod:@"infoWindow#onTap" arguments:@{@"markerId" : identifier}];
}
}
- (void)showMarkerInfoWindowWithIdentifier:(NSString *)identifier result:(FlutterResult)result {
FLTGoogleMapMarkerController *controller = self.markerIdentifierToController[identifier];
if (controller) {
[controller showInfoWindow];
result(nil);
} else {
result([FlutterError errorWithCode:@"Invalid markerId"
message:@"showInfoWindow called with invalid markerId"
details:nil]);
}
}
- (void)hideMarkerInfoWindowWithIdentifier:(NSString *)identifier result:(FlutterResult)result {
FLTGoogleMapMarkerController *controller = self.markerIdentifierToController[identifier];
if (controller) {
[controller hideInfoWindow];
result(nil);
} else {
result([FlutterError errorWithCode:@"Invalid markerId"
message:@"hideInfoWindow called with invalid markerId"
details:nil]);
}
}
- (void)isInfoWindowShownForMarkerWithIdentifier:(NSString *)identifier
result:(FlutterResult)result {
FLTGoogleMapMarkerController *controller = self.markerIdentifierToController[identifier];
if (controller) {
result(@([controller isInfoWindowShown]));
} else {
result([FlutterError errorWithCode:@"Invalid markerId"
message:@"isInfoWindowShown called with invalid markerId"
details:nil]);
}
}
+ (CLLocationCoordinate2D)getPosition:(NSDictionary *)marker {
NSArray *position = marker[@"position"];
return [FLTGoogleMapJSONConversions locationFromLatLong:position];
}
@end