diff --git a/Examples/UIExplorer/MapViewExample.js b/Examples/UIExplorer/MapViewExample.js index 44142a4af..407e40f00 100644 --- a/Examples/UIExplorer/MapViewExample.js +++ b/Examples/UIExplorer/MapViewExample.js @@ -17,10 +17,12 @@ var React = require('react-native'); var { + Image, MapView, StyleSheet, Text, TextInput, + TouchableOpacity, View, } = React; @@ -239,14 +241,14 @@ var MapViewExample = React.createClass({ }); -type CalloutMapViewExampleState = { +type AnnotationExampleState = { isFirstLoad: boolean, annotations?: Annotations, mapRegion?: MapRegion, }; -var CalloutMapViewExample = React.createClass({ +var AnnotationExample = React.createClass({ - getInitialState(): CalloutMapViewExampleState { + getInitialState(): AnnotationExampleState { return { isFirstLoad: true, }; @@ -260,11 +262,7 @@ var CalloutMapViewExample = React.createClass({ annotations: [{ longitude: region.longitude, latitude: region.latitude, - title: 'More Info...', - hasRightCallout: true, - onRightCalloutPress: () => { - alert('You Are Here'); - }, + ...this.props.annotation, }], }); }; @@ -282,141 +280,6 @@ var CalloutMapViewExample = React.createClass({ }); -type CustomPinColorMapViewExampleState = { - isFirstLoad: boolean, - annotations?: Annotations, - mapRegion?: MapRegion, -}; -var CustomPinColorMapViewExample = React.createClass({ - - getInitialState(): CustomPinColorMapViewExampleState { - return { - isFirstLoad: true, - }; - }, - - render() { - if (this.state.isFirstLoad) { - var onRegionChangeComplete = (region) => { - this.setState({ - isFirstLoad: false, - annotations: [{ - longitude: region.longitude, - latitude: region.latitude, - title: 'You Are Purple', - tintColor: MapView.PinColors.PURPLE, - }], - }); - }; - } - - return ( - - ); - }, - -}); - -type CustomPinImageMapViewExampleState = { - isFirstLoad: boolean, - annotations?: Annotations, - mapRegion?: MapRegion, -}; -var CustomPinImageMapViewExample = React.createClass({ - - getInitialState(): CustomPinImageMapViewExampleState { - return { - isFirstLoad: true, - }; - }, - - render() { - if (this.state.isFirstLoad) { - var onRegionChangeComplete = (region) => { - this.setState({ - isFirstLoad: false, - annotations: [{ - longitude: region.longitude, - latitude: region.latitude, - title: 'Thumbs Up!', - image: require('image!uie_thumb_big'), - }], - }); - }; - } - - return ( - - ); - }, - -}); - -type Overlays = Array<{ - coordinates?: Array<{ - latitude: number, - longitude: number, - }>, - lineWidth?: number, - strokeColor?: string, - fillColor?: string, - id?: string, -}>; -type CustomOverlayMapViewExampleState = { - isFirstLoad: boolean, - overlays?: Overlays, - annotations?: Annotations, - mapRegion?: MapRegion, -}; -var CustomOverlayMapViewExample = React.createClass({ - - getInitialState(): CustomOverlayMapViewExampleState { - return { - isFirstLoad: true, - }; - }, - - render() { - if (this.state.isFirstLoad) { - var onRegionChangeComplete = (region) => { - this.setState({ - isFirstLoad: false, - overlays: [{ - coordinates:[ - {latitude: 32.47, longitude: -107.85}, - {latitude: 45.13, longitude: -94.48}, - {latitude: 39.27, longitude: -83.25}, - {latitude: 32.47, longitude: -107.85}, - ], - strokeColor: '#f007', - lineWidth: 3, - }], - }); - }; - } - - return ( - - ); - }, - -}); - var styles = StyleSheet.create({ map: { height: 150, @@ -451,36 +314,92 @@ exports.description = 'Base component to display maps'; exports.examples = [ { title: 'Map', - render(): ReactElement { return ; } + render() { + return ; + } }, { title: 'Map shows user location', render() { - return ; + return ; } }, { title: 'Callout example', render() { - return ; + return { + alert('You Are Here'); + }}> + + + ), + }}/>; } }, { title: 'Custom pin color', render() { - return ; + return ; } }, { title: 'Custom pin image', render() { - return ; + return ; + } + }, + { + title: 'Custom pin view', + render() { + return + + Thumbs Up! + + + , + }}/>; } }, { title: 'Custom overlay', render() { - return ; + return ; } }, ]; diff --git a/Libraries/Components/MapView/MapView.js b/Libraries/Components/MapView/MapView.js index 747e366fc..8fcddb77c 100644 --- a/Libraries/Components/MapView/MapView.js +++ b/Libraries/Components/MapView/MapView.js @@ -11,78 +11,31 @@ */ 'use strict'; -var EdgeInsetsPropType = require('EdgeInsetsPropType'); -var Image = require('Image'); -var NativeMethodsMixin = require('NativeMethodsMixin'); -var Platform = require('Platform'); -var RCTMap = require('UIManager').RCTMap; -var RCTMapConstants = RCTMap && RCTMap.Constants; -var React = require('React'); -var View = require('View'); +const EdgeInsetsPropType = require('EdgeInsetsPropType'); +const Image = require('Image'); +const NativeMethodsMixin = require('NativeMethodsMixin'); +const Platform = require('Platform'); +const RCTMapConfig = require('UIManager').RCTMap; +const RCTMapConstants = RCTMapConfig && RCTMapConfig.Constants; +const React = require('React'); +const StyleSheet = require('StyleSheet'); +const View = require('View'); -var processColor = require('processColor'); -var resolveAssetSource = require('resolveAssetSource'); -var requireNativeComponent = require('requireNativeComponent'); +const processColor = require('processColor'); +const resolveAssetSource = require('resolveAssetSource'); +const requireNativeComponent = require('requireNativeComponent'); type Event = Object; type MapRegion = { latitude: number; longitude: number; - latitudeDelta: number; - longitudeDelta: number; + latitudeDelta?: ?number; + longitudeDelta?: ?number; }; -var MapView = React.createClass({ +const MapView = React.createClass({ mixins: [NativeMethodsMixin], - checkAnnotationIds: function (annotations: Array) { - - var newAnnotations = annotations.map(function (annotation) { - if (!annotation.id) { - // TODO: add a base64 (or similar) encoder here - annotation.id = encodeURIComponent(JSON.stringify(annotation)); - } - return annotation; - }); - - this.setState({ - annotations: newAnnotations - }); - }, - - checkOverlayIds: function (overlays: Array) { - - var newOverlays = overlays.map(function (overlay) { - if (!overlay.id) { - // TODO: add a base64 (or similar) encoder here - overlay.id = encodeURIComponent(JSON.stringify(overlay)); - } - return overlay; - }); - - this.setState({ - overlays: newOverlays - }); - }, - - componentWillMount: function() { - if (this.props.annotations) { - this.checkAnnotationIds(this.props.annotations); - } - if (this.props.overlays) { - this.checkOverlayIds(this.props.overlays); - } - }, - - componentWillReceiveProps: function(nextProps: Object) { - if (nextProps.annotations) { - this.checkAnnotationIds(nextProps.annotations); - } - if (nextProps.overlays) { - this.checkOverlayIds(nextProps.overlays); - } - }, - propTypes: { ...View.propTypes, /** @@ -151,6 +104,8 @@ var MapView = React.createClass({ * - standard: standard road map (default) * - satellite: satellite view * - hybrid: satellite view with roads and points of interest overlaid + * + * @platform ios */ mapType: React.PropTypes.oneOf([ 'standard', @@ -175,12 +130,13 @@ var MapView = React.createClass({ * Distance between the minimum and the maximum latitude/longitude * to be displayed. */ - latitudeDelta: React.PropTypes.number.isRequired, - longitudeDelta: React.PropTypes.number.isRequired, + latitudeDelta: React.PropTypes.number, + longitudeDelta: React.PropTypes.number, }), /** * Map annotations with title/subtitle. + * @platform ios */ annotations: React.PropTypes.arrayOf(React.PropTypes.shape({ /** @@ -199,19 +155,14 @@ var MapView = React.createClass({ */ title: React.PropTypes.string, subtitle: React.PropTypes.string, - + /** - * Whether the Annotation has callout buttons. + * Callout views. */ - hasLeftCallout: React.PropTypes.bool, - hasRightCallout: React.PropTypes.bool, - - /** - * Event handlers for callout buttons. - */ - onLeftCalloutPress: React.PropTypes.func, - onRightCalloutPress: React.PropTypes.func, - + leftCalloutView: React.PropTypes.element, + rightCalloutView: React.PropTypes.element, + detailCalloutView: React.PropTypes.element, + /** * The pin color. This can be any valid color string, or you can use one * of the predefined PinColors constants. Applies to both standard pins @@ -220,24 +171,40 @@ var MapView = React.createClass({ * Note that on iOS 8 and earlier, only the standard PinColor constants * are supported for regular pins. For custom pin images, any tintColor * value is supported on all iOS versions. - * @platform ios */ - tintColor: React.PropTypes.string, + tintColor: React.PropTypes.oneOfType([ + React.PropTypes.string, + React.PropTypes.number + ]), /** * Custom pin image. This must be a static image resource inside the app. - * @platform ios */ image: Image.propTypes.source, + /** + * Custom pin view. If set, this replaces the pin or custom pin image. + */ + view: React.PropTypes.element, + /** * annotation id */ id: React.PropTypes.string, + + /** + * Deprecated. Use the left/right/detailsCalloutView props instead. + */ + hasLeftCallout: React.PropTypes.bool, + hasRightCallout: React.PropTypes.bool, + onLeftCalloutPress: React.PropTypes.func, + onRightCalloutPress: React.PropTypes.func, + })), /** * Map overlays + * @platform ios */ overlays: React.PropTypes.arrayOf(React.PropTypes.shape({ /** @@ -252,8 +219,14 @@ var MapView = React.createClass({ * Line attributes */ lineWidth: React.PropTypes.number, - strokeColor: React.PropTypes.string, - fillColor: React.PropTypes.string, + strokeColor: React.PropTypes.oneOfType([ + React.PropTypes.string, + React.PropTypes.number + ]), + fillColor: React.PropTypes.oneOfType([ + React.PropTypes.string, + React.PropTypes.number + ]), /** * Overlay id @@ -263,17 +236,20 @@ var MapView = React.createClass({ /** * Maximum size of area that can be displayed. + * @platform ios */ maxDelta: React.PropTypes.number, /** * Minimum size of area that can be displayed. + * @platform ios */ minDelta: React.PropTypes.number, /** * Insets for the map's legal label, originally at bottom left of the map. * See `EdgeInsetsPropType.js` for more information. + * @platform ios */ legalLabelInsets: EdgeInsetsPropType, @@ -299,22 +275,81 @@ var MapView = React.createClass({ }, render: function() { - - let {annotations, overlays} = this.props; - annotations = annotations && annotations.map((annotation: Object) => { - let {tintColor, image} = annotation; + let children = [], {annotations, overlays} = this.props; + annotations = annotations && annotations.map((annotation: Object, index: number) => { + let { + id, + image, + tintColor, + view, + leftCalloutView, + rightCalloutView, + detailCalloutView, + } = annotation; + + if (!view && image && tintColor) { + view = ; + } + if (view) { + var viewIndex = children.length; + children.push(React.cloneElement(view, { + style: [styles.annotationView, view.props.style || {}] + })); + } + if (leftCalloutView) { + var leftCalloutViewIndex = children.length; + children.push(React.cloneElement(leftCalloutView, { + style: [styles.calloutView, leftCalloutView.props.style || {}] + })); + } + if (rightCalloutView) { + var rightCalloutViewIndex = children.length; + children.push(React.cloneElement(rightCalloutView, { + style: [styles.calloutView, rightCalloutView.props.style || {}] + })); + } + if (detailCalloutView) { + var detailCalloutViewIndex = children.length; + children.push(React.cloneElement(detailCalloutView, { + style: [styles.calloutView, detailCalloutView.props.style || {}] + })); + } + ['hasLeftCallout', 'onLeftCalloutPress'].forEach(key => { + if (annotation[key]) { + console.warn('`' + key + '` is deprecated. Use leftCalloutView instead.'); + } + }); + ['hasRightCallout', 'onRightCalloutPress'].forEach(key => { + if (annotation[key]) { + console.warn('`' + key + '` is deprecated. Use rightCalloutView instead.'); + } + }); return { ...annotation, tintColor: tintColor && processColor(tintColor), - image: image && resolveAssetSource(image), + viewIndex, + leftCalloutViewIndex, + rightCalloutViewIndex, + detailCalloutViewIndex, + view: undefined, + leftCalloutView: undefined, + rightCalloutView: undefined, + detailCalloutView: undefined, + id: id || String(index), }; }); - overlays = overlays && overlays.map((overlay: Object) => { - let {strokeColor, fillColor} = overlay; + overlays = overlays && overlays.map((overlay: Object, index: number) => { + let {id, fillColor, strokeColor} = overlay; return { ...overlay, strokeColor: strokeColor && processColor(strokeColor), fillColor: fillColor && processColor(fillColor), + id: id || String(index), }; }); @@ -364,6 +399,7 @@ var MapView = React.createClass({ *)RCTMapAnnotationArray:(id)json; - -typedef NSArray RCTMapOverlayArray; + (NSArray *)RCTMapOverlayArray:(id)json; @end diff --git a/React/Views/RCTConvert+MapKit.m b/React/Views/RCTConvert+MapKit.m index 78729fe04..369ec2446 100644 --- a/React/Views/RCTConvert+MapKit.m +++ b/React/Views/RCTConvert+MapKit.m @@ -50,9 +50,14 @@ RCT_ENUM_CONVERTER(MKMapType, (@{ annotation.animateDrop = [RCTConvert BOOL:json[@"animateDrop"]]; annotation.tintColor = [RCTConvert UIColor:json[@"tintColor"]]; annotation.image = [RCTConvert UIImage:json[@"image"]]; - if (annotation.tintColor && annotation.image) { - annotation.image = [annotation.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; - } + annotation.viewIndex = + [RCTConvert NSInteger:json[@"viewIndex"] ?: @(NSNotFound)]; + annotation.leftCalloutViewIndex = + [RCTConvert NSInteger:json[@"leftCalloutViewIndex"] ?: @(NSNotFound)]; + annotation.rightCalloutViewIndex = + [RCTConvert NSInteger:json[@"rightCalloutViewIndex"] ?: @(NSNotFound)]; + annotation.detailCalloutViewIndex = + [RCTConvert NSInteger:json[@"detailCalloutViewIndex"] ?: @(NSNotFound)]; return annotation; } diff --git a/React/Views/RCTMap.h b/React/Views/RCTMap.h index a83bb93da..63e01473e 100644 --- a/React/Views/RCTMap.h +++ b/React/Views/RCTMap.h @@ -31,7 +31,7 @@ RCT_EXTERN const CGFloat RCTMapZoomBoundBuffer; @property (nonatomic, copy) RCTBubblingEventBlock onChange; @property (nonatomic, copy) RCTBubblingEventBlock onPress; -- (void)setAnnotations:(RCTMapAnnotationArray *)annotations; -- (void)setOverlays:(RCTMapOverlayArray *)overlays; +- (void)setAnnotations:(NSArray *)annotations; +- (void)setOverlays:(NSArray *)overlays; @end diff --git a/React/Views/RCTMap.m b/React/Views/RCTMap.m index 4f542c4c4..6996eb2b3 100644 --- a/React/Views/RCTMap.m +++ b/React/Views/RCTMap.m @@ -23,6 +23,7 @@ const CGFloat RCTMapZoomBoundBuffer = 0.01; { UIView *_legalLabel; CLLocationManager *_locationManager; + NSMutableArray *_reactSubviews; } - (instancetype)init @@ -30,6 +31,7 @@ const CGFloat RCTMapZoomBoundBuffer = 0.01; if ((self = [super init])) { _hasStartedRendering = NO; + _reactSubviews = [NSMutableArray new]; // Find Apple link label for (UIView *subview in self.subviews) { @@ -49,6 +51,21 @@ const CGFloat RCTMapZoomBoundBuffer = 0.01; [_regionChangeObserveTimer invalidate]; } +- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex +{ + [_reactSubviews insertObject:subview atIndex:atIndex]; +} + +- (void)removeReactSubview:(UIView *)subview +{ + [_reactSubviews removeObject:subview]; +} + +- (NSArray *)reactSubviews +{ + return _reactSubviews; +} + - (void)layoutSubviews { [super layoutSubviews]; @@ -111,7 +128,7 @@ const CGFloat RCTMapZoomBoundBuffer = 0.01; // TODO: this doesn't preserve order. Should it? If so we should change the // algorithm. If not, it would be more efficient to use an NSSet -- (void)setAnnotations:(RCTMapAnnotationArray *)annotations +- (void)setAnnotations:(NSArray *)annotations { NSMutableArray *newAnnotationIDs = [NSMutableArray new]; NSMutableArray *annotationsToDelete = [NSMutableArray new]; @@ -154,7 +171,7 @@ const CGFloat RCTMapZoomBoundBuffer = 0.01; // TODO: this doesn't preserve order. Should it? If so we should change the // algorithm. If not, it would be more efficient to use an NSSet -- (void)setOverlays:(RCTMapOverlayArray *)overlays +- (void)setOverlays:(NSArray *)overlays { NSMutableArray *newOverlayIDs = [NSMutableArray new]; NSMutableArray *overlaysToDelete = [NSMutableArray new]; diff --git a/React/Views/RCTMapAnnotation.h b/React/Views/RCTMapAnnotation.h index 0f4479c48..eb03ebaf9 100644 --- a/React/Views/RCTMapAnnotation.h +++ b/React/Views/RCTMapAnnotation.h @@ -17,5 +17,9 @@ @property (nonatomic, assign) BOOL animateDrop; @property (nonatomic, strong) UIColor *tintColor; @property (nonatomic, strong) UIImage *image; +@property (nonatomic, assign) NSInteger viewIndex; +@property (nonatomic, assign) NSInteger leftCalloutViewIndex; +@property (nonatomic, assign) NSInteger rightCalloutViewIndex; +@property (nonatomic, assign) NSInteger detailCalloutViewIndex; @end diff --git a/React/Views/RCTMapManager.m b/React/Views/RCTMapManager.m index e662d4868..3ef674850 100644 --- a/React/Views/RCTMapManager.m +++ b/React/Views/RCTMapManager.m @@ -67,8 +67,8 @@ RCT_EXPORT_VIEW_PROPERTY(maxDelta, CGFloat) RCT_EXPORT_VIEW_PROPERTY(minDelta, CGFloat) RCT_EXPORT_VIEW_PROPERTY(legalLabelInsets, UIEdgeInsets) RCT_EXPORT_VIEW_PROPERTY(mapType, MKMapType) -RCT_EXPORT_VIEW_PROPERTY(annotations, RCTMapAnnotationArray) -RCT_EXPORT_VIEW_PROPERTY(overlays, RCTMapOverlayArray) +RCT_EXPORT_VIEW_PROPERTY(annotations, NSArray) +RCT_EXPORT_VIEW_PROPERTY(overlays, NSArray) RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock) RCT_EXPORT_VIEW_PROPERTY(onPress, RCTBubblingEventBlock) RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, RCTMap) @@ -125,77 +125,124 @@ RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, RCTMap) } } -- (MKAnnotationView *)mapView:(__unused MKMapView *)mapView viewForAnnotation:(RCTMapAnnotation *)annotation +- (MKAnnotationView *)mapView:(RCTMap *)mapView + viewForAnnotation:(RCTMapAnnotation *)annotation { if (![annotation isKindOfClass:[RCTMapAnnotation class]]) { return nil; } MKAnnotationView *annotationView; - if (annotation.image) { - if (annotation.tintColor) { + annotationView.clipsToBounds = YES; + if (annotation.viewIndex != NSNotFound) { - NSString *const reuseIdentifier = @"RCTImageViewAnnotation"; - NSInteger imageViewTag = 99; - annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:reuseIdentifier]; - if (!annotationView) { - annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:reuseIdentifier]; - UIImageView *imageView = [UIImageView new]; - imageView.tag = imageViewTag; - [annotationView addSubview:imageView]; - } - - UIImageView *imageView = (UIImageView *)[annotationView viewWithTag:imageViewTag]; - imageView.image = annotation.image; - imageView.tintColor = annotation.tintColor; - [imageView sizeToFit]; - imageView.center = CGPointZero; - - } else { - - NSString *reuseIdentifier = NSStringFromClass([MKAnnotationView class]); - annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:reuseIdentifier] ?: [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:reuseIdentifier]; - annotationView.image = annotation.image; + NSString *const reuseIdentifier = @"RCTCustomViewAnnotation"; + annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:reuseIdentifier]; + if (!annotationView) { + annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation + reuseIdentifier:reuseIdentifier]; } + for (UIView *view in annotationView.subviews) { + [view removeFromSuperview]; + } + UIView *reactView = mapView.reactSubviews[annotation.viewIndex]; + annotationView.bounds = reactView.frame; + [annotationView addSubview:reactView]; + + } else if (annotation.image) { + + NSString *reuseIdentifier = NSStringFromClass([MKAnnotationView class]); + annotationView = + [mapView dequeueReusableAnnotationViewWithIdentifier:reuseIdentifier] ?: + [[MKAnnotationView alloc] initWithAnnotation:annotation + reuseIdentifier:reuseIdentifier]; + annotationView.image = annotation.image; + } else { NSString *reuseIdentifier = NSStringFromClass([MKPinAnnotationView class]); - annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:reuseIdentifier] ?: [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:reuseIdentifier]; + annotationView = + [mapView dequeueReusableAnnotationViewWithIdentifier:reuseIdentifier] ?: + [[MKPinAnnotationView alloc] initWithAnnotation:annotation + reuseIdentifier:reuseIdentifier]; ((MKPinAnnotationView *)annotationView).animatesDrop = annotation.animateDrop; #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0 if (![annotationView respondsToSelector:@selector(pinTintColor)]) { - NSString *hexColor = annotation.tintColor ? RCTColorToHexString(annotation.tintColor.CGColor) : RCTMapPinRed; - ((MKPinAnnotationView *)annotationView).pinColor = [RCTConvert MKPinAnnotationColor:hexColor]; + NSString *hexColor = annotation.tintColor ? + RCTColorToHexString(annotation.tintColor.CGColor) : RCTMapPinRed; + ((MKPinAnnotationView *)annotationView).pinColor = + [RCTConvert MKPinAnnotationColor:hexColor]; } else #endif { - ((MKPinAnnotationView *)annotationView).pinTintColor = annotation.tintColor ?: [MKPinAnnotationView redPinColor]; + ((MKPinAnnotationView *)annotationView).pinTintColor = + annotation.tintColor ?: [MKPinAnnotationView redPinColor]; } } annotationView.canShowCallout = true; - annotationView.leftCalloutAccessoryView = nil; - if (annotation.hasLeftCallout) { - annotationView.leftCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure]; + if (annotation.leftCalloutViewIndex != NSNotFound) { + annotationView.leftCalloutAccessoryView = + mapView.reactSubviews[annotation.leftCalloutViewIndex]; + } else if (annotation.hasLeftCallout) { + annotationView.leftCalloutAccessoryView = + [UIButton buttonWithType:UIButtonTypeDetailDisclosure]; + } else { + annotationView.leftCalloutAccessoryView = nil; } - annotationView.rightCalloutAccessoryView = nil; - if (annotation.hasRightCallout) { - annotationView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure]; + if (annotation.rightCalloutViewIndex != NSNotFound) { + annotationView.rightCalloutAccessoryView = + mapView.reactSubviews[annotation.rightCalloutViewIndex]; + } else if (annotation.hasRightCallout) { + annotationView.rightCalloutAccessoryView = + [UIButton buttonWithType:UIButtonTypeDetailDisclosure]; + } else { + annotationView.rightCalloutAccessoryView = nil; + } + + //http://stackoverflow.com/questions/32581049/mapkit-ios-9-detailcalloutaccessoryview-usage + if ([annotationView respondsToSelector:@selector(detailCalloutAccessoryView)]) { + if (annotation.detailCalloutViewIndex != NSNotFound) { + UIView *calloutView = mapView.reactSubviews[annotation.detailCalloutViewIndex]; + NSLayoutConstraint *widthConstraint = + [NSLayoutConstraint constraintWithItem:calloutView + attribute:NSLayoutAttributeWidth + relatedBy:NSLayoutRelationEqual + toItem:nil + attribute:NSLayoutAttributeNotAnAttribute + multiplier:1 + constant:calloutView.frame.size.width]; + [calloutView addConstraint:widthConstraint]; + NSLayoutConstraint *heightConstraint = + [NSLayoutConstraint constraintWithItem:calloutView + attribute:NSLayoutAttributeHeight + relatedBy:NSLayoutRelationEqual + toItem:nil + attribute:NSLayoutAttributeNotAnAttribute + multiplier:1 + constant:calloutView.frame.size.height]; + [calloutView addConstraint:heightConstraint]; + annotationView.detailCalloutAccessoryView = calloutView; + } else { + annotationView.detailCalloutAccessoryView = nil; + } } return annotationView; } -- (MKOverlayRenderer *)mapView:(__unused MKMapView *)mapView rendererForOverlay:(RCTMapOverlay *)overlay +- (MKOverlayRenderer *)mapView:(__unused MKMapView *)mapView + rendererForOverlay:(RCTMapOverlay *)overlay { if ([overlay isKindOfClass:[RCTMapOverlay class]]) { - MKPolylineRenderer *polylineRenderer = [[MKPolylineRenderer alloc] initWithPolyline:overlay]; + MKPolylineRenderer *polylineRenderer = + [[MKPolylineRenderer alloc] initWithPolyline:overlay]; polylineRenderer.strokeColor = overlay.strokeColor; polylineRenderer.lineWidth = overlay.lineWidth; return polylineRenderer; @@ -204,11 +251,12 @@ RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, RCTMap) } } -- (void)mapView:(RCTMap *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control +- (void)mapView:(RCTMap *)mapView annotationView:(MKAnnotationView *)view + calloutAccessoryControlTapped:(UIControl *)control { if (mapView.onPress) { - // Pass to js + // Pass to JS RCTMapAnnotation *annotation = (RCTMapAnnotation *)view.annotation; mapView.onPress(@{ @"side": (control == view.leftCalloutAccessoryView) ? @"left" : @"right", @@ -236,13 +284,15 @@ RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, RCTMap) { [self _regionChanged:mapView]; - mapView.regionChangeObserveTimer = [NSTimer timerWithTimeInterval:RCTMapRegionChangeObserveInterval - target:self - selector:@selector(_onTick:) - userInfo:@{ RCTMapViewKey: mapView } - repeats:YES]; + mapView.regionChangeObserveTimer = + [NSTimer timerWithTimeInterval:RCTMapRegionChangeObserveInterval + target:self + selector:@selector(_onTick:) + userInfo:@{ RCTMapViewKey: mapView } + repeats:YES]; - [[NSRunLoop mainRunLoop] addTimer:mapView.regionChangeObserveTimer forMode:NSRunLoopCommonModes]; + [[NSRunLoop mainRunLoop] addTimer:mapView.regionChangeObserveTimer + forMode:NSRunLoopCommonModes]; } - (void)mapView:(RCTMap *)mapView regionDidChangeAnimated:(__unused BOOL)animated @@ -288,15 +338,18 @@ RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, RCTMap) // move, it's likely the map will auto zoom to max/min from time to time. // So let's try to make map zoom back to 99% max or 101% min so that there is // some buffer, and moving the map won't constantly hit the max/min bound. - if (mapView.maxDelta > FLT_EPSILON && region.span.longitudeDelta > mapView.maxDelta) { + if (mapView.maxDelta > FLT_EPSILON && + region.span.longitudeDelta > mapView.maxDelta) { needZoom = YES; newLongitudeDelta = mapView.maxDelta * (1 - RCTMapZoomBoundBuffer); - } else if (mapView.minDelta > FLT_EPSILON && region.span.longitudeDelta < mapView.minDelta) { + } else if (mapView.minDelta > FLT_EPSILON && + region.span.longitudeDelta < mapView.minDelta) { needZoom = YES; newLongitudeDelta = mapView.minDelta * (1 + RCTMapZoomBoundBuffer); } if (needZoom) { - region.span.latitudeDelta = region.span.latitudeDelta / region.span.longitudeDelta * newLongitudeDelta; + region.span.latitudeDelta = + region.span.latitudeDelta / region.span.longitudeDelta * newLongitudeDelta; region.span.longitudeDelta = newLongitudeDelta; mapView.region = region; }