From e75c7465ef22a38235316ef0f1797d16c73f4b7c Mon Sep 17 00:00:00 2001 From: Gil Birman Date: Wed, 5 Oct 2016 12:55:44 -0700 Subject: [PATCH] [google] refactor AIRGoogleMapMarker to use DummyView (#649) - DummyView is used to create virtual tree - Add CustomMarkers example - Prevent annoying errors from showing up with OS_ACTIVITY_MODE disable, see here https://github.com/facebook/react-native/issues/9921#issuecomment-250078139 --- example/App.js | 2 + example/examples/CustomMarkers.js | 118 ++++++++++++++++++ .../AirMapsExplorer.xcodeproj/project.pbxproj | 6 + .../xcschemes/AirMapsExplorer.xcscheme | 7 ++ example/ios/Podfile.lock | 12 +- ios/AirGoogleMaps/AIRGoogleMapMarker.m | 66 ++++++---- ios/AirGoogleMaps/DummyView.h | 14 +++ ios/AirGoogleMaps/DummyView.m | 19 +++ 8 files changed, 217 insertions(+), 27 deletions(-) create mode 100644 example/examples/CustomMarkers.js create mode 100644 ios/AirGoogleMaps/DummyView.h create mode 100644 ios/AirGoogleMaps/DummyView.m diff --git a/example/App.js b/example/App.js index 7fa21bce1..625f67edb 100644 --- a/example/App.js +++ b/example/App.js @@ -21,6 +21,7 @@ import AnimatedMarkers from './examples/AnimatedMarkers'; import Callouts from './examples/Callouts'; import Overlays from './examples/Overlays'; import DefaultMarkers from './examples/DefaultMarkers'; +import CustomMarkers from './examples/CustomMarkers'; import CachedMap from './examples/CachedMap'; import LoadingMap from './examples/LoadingMap'; import TakeSnapshot from './examples/TakeSnapshot'; @@ -129,6 +130,7 @@ class App extends React.Component { [Callouts, 'Custom Callouts', true], [Overlays, 'Circles, Polygons, and Polylines', true, '(ios error)'], [DefaultMarkers, 'Default Markers', true], + [CustomMarkers, 'Custom Markers', true], [TakeSnapshot, 'Take Snapshot', true, '(incomplete)'], [CachedMap, 'Cached Map'], [LoadingMap, 'Map with loading'], diff --git a/example/examples/CustomMarkers.js b/example/examples/CustomMarkers.js new file mode 100644 index 000000000..5c5330263 --- /dev/null +++ b/example/examples/CustomMarkers.js @@ -0,0 +1,118 @@ +import React from 'react'; +import { + StyleSheet, + View, + Text, + Dimensions, + TouchableOpacity, +} from 'react-native'; + +import MapView from 'react-native-maps'; +import flagPinkImg from './assets/flag-pink.png'; + +const { width, height } = Dimensions.get('window'); + +const ASPECT_RATIO = width / height; +const LATITUDE = 37.78825; +const LONGITUDE = -122.4324; +const LATITUDE_DELTA = 0.0922; +const LONGITUDE_DELTA = LATITUDE_DELTA * ASPECT_RATIO; +let id = 0; + +class CustomMarkers extends React.Component { + constructor(props) { + super(props); + + this.state = { + region: { + latitude: LATITUDE, + longitude: LONGITUDE, + latitudeDelta: LATITUDE_DELTA, + longitudeDelta: LONGITUDE_DELTA, + }, + markers: [], + }; + + this.onMapPress = this.onMapPress.bind(this); + } + + onMapPress(e) { + this.setState({ + markers: [ + ...this.state.markers, + { + coordinate: e.nativeEvent.coordinate, + key: `foo${id++}`, + }, + ], + }); + } + + render() { + return ( + + + {this.state.markers.map(marker => ( + + ))} + + + this.setState({ markers: [] })} + style={styles.bubble} + > + Tap to create a marker of random color + + + + ); + } +} + +CustomMarkers.propTypes = { + provider: MapView.ProviderPropType, +}; + +const styles = StyleSheet.create({ + container: { + ...StyleSheet.absoluteFillObject, + justifyContent: 'flex-end', + alignItems: 'center', + }, + map: { + ...StyleSheet.absoluteFillObject, + }, + bubble: { + backgroundColor: 'rgba(255,255,255,0.7)', + paddingHorizontal: 18, + paddingVertical: 12, + borderRadius: 20, + }, + latlng: { + width: 200, + alignItems: 'stretch', + }, + button: { + width: 80, + paddingHorizontal: 12, + alignItems: 'center', + marginHorizontal: 10, + }, + buttonContainer: { + flexDirection: 'row', + marginVertical: 20, + backgroundColor: 'transparent', + }, +}); + +module.exports = CustomMarkers; diff --git a/example/ios/AirMapsExplorer.xcodeproj/project.pbxproj b/example/ios/AirMapsExplorer.xcodeproj/project.pbxproj index 2ed33f2e3..365d67b6e 100644 --- a/example/ios/AirMapsExplorer.xcodeproj/project.pbxproj +++ b/example/ios/AirMapsExplorer.xcodeproj/project.pbxproj @@ -33,6 +33,7 @@ 2166AB351D82EC56007538D7 /* AIRMapPolylineManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 2166AB141D82EC56007538D7 /* AIRMapPolylineManager.m */; }; 2166AB361D82EC56007538D7 /* SMCalloutView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2166AB171D82EC56007538D7 /* SMCalloutView.m */; }; 2166AB3E1D82EC56007538D7 /* RCTConvert+MoreMapKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 2166AB281D82EC56007538D7 /* RCTConvert+MoreMapKit.m */; }; + 21B248671DA4483D008FCB52 /* DummyView.m in Sources */ = {isa = PBXBuildFile; fileRef = 21B248661DA4483D008FCB52 /* DummyView.m */; }; 21D346651D933B8C002BAD8A /* AIRMapUrlTile.m in Sources */ = {isa = PBXBuildFile; fileRef = 21D346621D933B8C002BAD8A /* AIRMapUrlTile.m */; }; 21D346661D933B8C002BAD8A /* AIRMapUrlTileManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 21D346641D933B8C002BAD8A /* AIRMapUrlTileManager.m */; }; 21E6570A1D77591400B75EE5 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 21E657091D77591400B75EE5 /* SystemConfiguration.framework */; }; @@ -111,6 +112,8 @@ 2166AB171D82EC56007538D7 /* SMCalloutView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SMCalloutView.m; sourceTree = ""; }; 2166AB271D82EC56007538D7 /* RCTConvert+MoreMapKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RCTConvert+MoreMapKit.h"; sourceTree = ""; }; 2166AB281D82EC56007538D7 /* RCTConvert+MoreMapKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RCTConvert+MoreMapKit.m"; sourceTree = ""; }; + 21B248661DA4483D008FCB52 /* DummyView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DummyView.m; sourceTree = ""; }; + 21B248681DA44851008FCB52 /* DummyView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DummyView.h; sourceTree = ""; }; 21D346611D933B8C002BAD8A /* AIRMapUrlTile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AIRMapUrlTile.h; sourceTree = ""; }; 21D346621D933B8C002BAD8A /* AIRMapUrlTile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AIRMapUrlTile.m; sourceTree = ""; }; 21D346631D933B8C002BAD8A /* AIRMapUrlTileManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AIRMapUrlTileManager.h; sourceTree = ""; }; @@ -195,6 +198,8 @@ 213CA8E71D89F66A008623EC /* AIRGoogleMapMarker.m */, 213CA8E81D89F66A008623EC /* AIRGoogleMapMarkerManager.h */, 213CA8E91D89F66A008623EC /* AIRGoogleMapMarkerManager.m */, + 21B248661DA4483D008FCB52 /* DummyView.m */, + 21B248681DA44851008FCB52 /* DummyView.h */, ); name = AirGoogleMaps; path = ../../ios/AirGoogleMaps; @@ -512,6 +517,7 @@ 2166AB301D82EC56007538D7 /* AIRMapMarker.m in Sources */, 2166AB321D82EC56007538D7 /* AIRMapPolygon.m in Sources */, 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */, + 21B248671DA4483D008FCB52 /* DummyView.m in Sources */, 2166AB3E1D82EC56007538D7 /* RCTConvert+MoreMapKit.m in Sources */, 13B07FC11A68108700A75B9A /* main.m in Sources */, 2166AB331D82EC56007538D7 /* AIRMapPolygonManager.m in Sources */, diff --git a/example/ios/AirMapsExplorer.xcodeproj/xcshareddata/xcschemes/AirMapsExplorer.xcscheme b/example/ios/AirMapsExplorer.xcodeproj/xcshareddata/xcschemes/AirMapsExplorer.xcscheme index b4bbba00f..9b7c436e0 100644 --- a/example/ios/AirMapsExplorer.xcodeproj/xcshareddata/xcschemes/AirMapsExplorer.xcscheme +++ b/example/ios/AirMapsExplorer.xcodeproj/xcshareddata/xcschemes/AirMapsExplorer.xcscheme @@ -85,6 +85,13 @@ ReferencedContainer = "container:AirMapsExplorer.xcodeproj"> + + + + diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 9314559fd..6dd5ea99d 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,9 +1,9 @@ PODS: - - GoogleMaps (2.0.1): - - GoogleMaps/Maps (= 2.0.1) - - GoogleMaps/Base (2.0.1) - - GoogleMaps/Maps (2.0.1): - - GoogleMaps/Base (= 2.0.1) + - GoogleMaps (2.1.0): + - GoogleMaps/Maps (= 2.1.0) + - GoogleMaps/Base (2.1.0) + - GoogleMaps/Maps (2.1.0): + - GoogleMaps/Base (= 2.1.0) - React/Core (0.32.0): - React/CSSLayout - React/CSSLayout (0.32.0) @@ -45,7 +45,7 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native" SPEC CHECKSUMS: - GoogleMaps: f09da64fc987c1aa29394567c3cab5a3df83c402 + GoogleMaps: 06589b9a38097bce0cd6e90f0fd9b5e4b4a9344c React: 0245b401173a94a5fba0f440aab3fb2f79b738f3 COCOAPODS: 0.39.0 diff --git a/ios/AirGoogleMaps/AIRGoogleMapMarker.m b/ios/AirGoogleMaps/AIRGoogleMapMarker.m index 676fff59e..5c179e6aa 100644 --- a/ios/AirGoogleMaps/AIRGoogleMapMarker.m +++ b/ios/AirGoogleMaps/AIRGoogleMapMarker.m @@ -11,6 +11,7 @@ #import "AIRGoogleMapCallout.h" #import "RCTImageLoader.h" #import "RCTUtils.h" +#import "DummyView.h" CGRect unionRect(CGRect a, CGRect b) { return CGRectMake( @@ -27,6 +28,7 @@ - (id)eventFromMarker:(AIRGMSMarker*)marker; @implementation AIRGoogleMapMarker { RCTImageLoaderCancellationBlock _reloadImageCancellationBlock; __weak UIImageView *_iconImageView; + UIView *_iconView; } - (instancetype)init @@ -38,12 +40,28 @@ - (instancetype)init return self; } +- (void)layoutSubviews { + float width = 0; + float height = 0; + + for (UIView *v in [_iconView subviews]) { + + float fw = v.frame.origin.x + v.frame.size.width; + float fh = v.frame.origin.y + v.frame.size.height; + + width = MAX(fw, width); + height = MAX(fh, height); + } + + [_iconView setFrame:CGRectMake(0, 0, width, height)]; +} + - (id)eventFromMarker:(AIRGMSMarker*)marker { CLLocationCoordinate2D coordinate = marker.position; CGPoint position = [self.realMarker.map.projection pointForCoordinate:coordinate]; -return @{ + return @{ @"id": marker.identifier ?: @"unknown", @"position": @{ @"x": @(position.x), @@ -56,27 +74,33 @@ - (id)eventFromMarker:(AIRGMSMarker*)marker { }; } +- (void)iconViewInsertSubview:(UIView*)subview atIndex:(NSInteger)atIndex { + if (!_realMarker.iconView) { + _iconView = [[UIView alloc] init]; + _realMarker.iconView = _iconView; + } + [_iconView insertSubview:subview atIndex:atIndex]; +} + - (void)insertReactSubview:(id)subview atIndex:(NSInteger)atIndex { if ([subview isKindOfClass:[AIRGoogleMapCallout class]]) { self.calloutView = (AIRGoogleMapCallout *)subview; - } else { - // Custom UIView Marker - // NOTE: Originally I tried creating a new UIView here to encapsulate subview, - // but it would not sizeToFit properly. Not sure why. - [super insertReactSubview:(UIView*)subview atIndex:atIndex]; - [self sizeToFit]; - - // TODO: how to handle this circular reference properly? - _realMarker.iconView = self; + } else { // a child view of the marker + [self iconViewInsertSubview:(UIView*)subview atIndex:atIndex+1]; } + DummyView *dummySubview = [[DummyView alloc] initWithView:(UIView *)subview]; + [super insertReactSubview:(UIView*)dummySubview atIndex:atIndex]; } -- (void)removeReactSubview:(id)subview { +- (void)removeReactSubview:(id)dummySubview { + UIView* subview = ((DummyView*)dummySubview).view; + if ([subview isKindOfClass:[AIRGoogleMapCallout class]]) { self.calloutView = nil; } else { - [super removeReactSubview:(UIView*)subview]; + [(UIView*)subview removeFromSuperview]; } + [super removeReactSubview:(UIView*)dummySubview]; } - (void)showCalloutView { @@ -151,7 +175,6 @@ - (RCTBubblingEventBlock)onPress { - (void)setImageSrc:(NSString *)imageSrc { - _imageSrc = imageSrc; if (_reloadImageCancellationBlock) { @@ -172,11 +195,14 @@ - (void)setImageSrc:(NSString *)imageSrc } dispatch_async(dispatch_get_main_queue(), ^{ - if (_iconImageView) { - // TODO: doesn't work because image is blank (WHY??) - [_iconImageView setImage:image]; - return; - } + // TODO(gil): This way allows different image sizes + if (_iconImageView) [_iconImageView removeFromSuperview]; + + // ... but this way is more efficient? +// if (_iconImageView) { +// [_iconImageView setImage:image]; +// return; +// } UIImageView *imageView = [[UIImageView alloc] initWithImage:image]; @@ -197,9 +223,7 @@ - (void)setImageSrc:(NSString *)imageSrc [self setFrame:selfBounds]; _iconImageView = imageView; - - [super insertSubview:imageView atIndex:0]; - _realMarker.iconView = self; + [self iconViewInsertSubview:imageView atIndex:0]; // TODO: This could be a prop //_realMarker.groundAnchor = CGPointMake(0.75, 1); diff --git a/ios/AirGoogleMaps/DummyView.h b/ios/AirGoogleMaps/DummyView.h new file mode 100644 index 000000000..ba4be2834 --- /dev/null +++ b/ios/AirGoogleMaps/DummyView.h @@ -0,0 +1,14 @@ +// +// DummyView.h +// AirMapsExplorer +// +// Created by Gil Birman on 10/4/16. +// + +#import + + +@interface DummyView : UIView +@property (nonatomic, weak) UIView *view; +- (instancetype)initWithView:(UIView*)view; +@end diff --git a/ios/AirGoogleMaps/DummyView.m b/ios/AirGoogleMaps/DummyView.m new file mode 100644 index 000000000..9e8439707 --- /dev/null +++ b/ios/AirGoogleMaps/DummyView.m @@ -0,0 +1,19 @@ +// +// DummyView.m +// AirMapsExplorer +// +// Created by Gil Birman on 10/4/16. +// + +#import +#import "DummyView.h" + +@implementation DummyView +- (instancetype)initWithView:(UIView*)view +{ + if ((self = [super init])) { + self.view = view; + } + return self; +} +@end