diff --git a/Examples/UIExplorer/ImageExample.js b/Examples/UIExplorer/ImageExample.js index 163396f89..60fc38dab 100644 --- a/Examples/UIExplorer/ImageExample.js +++ b/Examples/UIExplorer/ImageExample.js @@ -442,7 +442,8 @@ exports.examples = [ title: 'Image Size', render: function() { return ; - } + }, + platform: 'ios', }, ]; diff --git a/Examples/UIExplorer/MapViewExample.js b/Examples/UIExplorer/MapViewExample.js index 3df5d4ded..498a5f83d 100644 --- a/Examples/UIExplorer/MapViewExample.js +++ b/Examples/UIExplorer/MapViewExample.js @@ -19,6 +19,7 @@ var React = require('react-native'); var { Image, MapView, + PropTypes, StyleSheet, Text, TextInput, @@ -33,36 +34,23 @@ var regionText = { longitudeDelta: '0', }; -type MapRegion = { - latitude: number, - longitude: number, - latitudeDelta?: number, - longitudeDelta?: number, -}; - -type MapRegionInputState = { - region: MapRegion, -}; - var MapRegionInput = React.createClass({ propTypes: { - region: React.PropTypes.shape({ - latitude: React.PropTypes.number.isRequired, - longitude: React.PropTypes.number.isRequired, - latitudeDelta: React.PropTypes.number, - longitudeDelta: React.PropTypes.number, + region: PropTypes.shape({ + latitude: PropTypes.number.isRequired, + longitude: PropTypes.number.isRequired, + latitudeDelta: PropTypes.number, + longitudeDelta: PropTypes.number, }), - onChange: React.PropTypes.func.isRequired, + onChange: PropTypes.func.isRequired, }, - getInitialState(): MapRegionInputState { + getInitialState() { return { region: { latitude: 0, longitude: 0, - latitudeDelta: 0, - longitudeDelta: 0, } }; }, @@ -164,36 +152,14 @@ var MapRegionInput = React.createClass({ }); -type Annotations = Array<{ - animateDrop?: boolean, - latitude: number, - longitude: number, - title?: string, - subtitle?: string, - hasLeftCallout?: boolean, - hasRightCallout?: boolean, - onLeftCalloutPress?: Function, - onRightCalloutPress?: Function, - tintColor?: number | string, - image?: any, - id?: string, - view?: ReactElement, - leftCalloutView?: ReactElement, - rightCalloutView?: ReactElement, - detailCalloutView?: ReactElement, -}>; -type MapViewExampleState = { - isFirstLoad: boolean, - mapRegion?: MapRegion, - mapRegionInput?: MapRegion, - annotations?: Annotations, -}; - var MapViewExample = React.createClass({ - getInitialState(): MapViewExampleState { + getInitialState() { return { isFirstLoad: true, + mapRegion: undefined, + mapRegionInput: undefined, + annotations: [], }; }, @@ -215,7 +181,7 @@ var MapViewExample = React.createClass({ ); }, - _getAnnotations(region): Annotations { + _getAnnotations(region) { return [{ longitude: region.longitude, latitude: region.latitude, @@ -249,16 +215,13 @@ var MapViewExample = React.createClass({ }); -type AnnotationExampleState = { - isFirstLoad: boolean, - annotations?: Annotations, - mapRegion?: MapRegion, -}; var AnnotationExample = React.createClass({ - getInitialState(): AnnotationExampleState { + getInitialState() { return { isFirstLoad: true, + annotations: [], + mapRegion: undefined, }; }, @@ -351,6 +314,17 @@ exports.examples = [ }}/>; } }, + { + title: 'Draggable pin', + render() { + return { + console.log('Drag state: ' + event.state); + }, + }}/>; + } + }, { title: 'Custom pin color', render() { diff --git a/Libraries/Components/MapView/MapView.js b/Libraries/Components/MapView/MapView.js index 29257d1db..2ccb4b8cb 100644 --- a/Libraries/Components/MapView/MapView.js +++ b/Libraries/Components/MapView/MapView.js @@ -28,7 +28,16 @@ const requireNativeComponent = require('requireNativeComponent'); type Event = Object; +export type AnnotationDragState = $Enum<{ + idle: string; + starting: string; + dragging: string; + canceling: string; + ending: string; +}>; + const MapView = React.createClass({ + mixins: [NativeMethodsMixin], propTypes: { @@ -144,6 +153,18 @@ const MapView = React.createClass({ * Whether the pin drop should be animated or not */ animateDrop: React.PropTypes.bool, + + /** + * Whether the pin should be draggable or not + * @platform ios + */ + draggable: React.PropTypes.bool, + + /** + * Event that fires when the annotation drag state changes. + * @platform ios + */ + onDragStateChange: React.PropTypes.func, /** * Annotation title/subtile. @@ -376,6 +397,24 @@ const MapView = React.createClass({ } } }; + var onAnnotationDragStateChange = (event: Event) => { + if (!annotations) { + return; + } + // Find the annotation with the id that was pressed + for (let i = 0, l = annotations.length; i < l; i++) { + let annotation = annotations[i]; + if (annotation.id === event.nativeEvent.annotationId) { + // Update location + annotation.latitude = event.nativeEvent.latitude; + annotation.longitude = event.nativeEvent.longitude; + // Call callback + annotation.onDragStateChange && + annotation.onDragStateChange(event.nativeEvent); + break; + } + } + } } // TODO: these should be separate events, to reduce bridge traffic @@ -399,6 +438,7 @@ const MapView = React.createClass({ overlays={overlays} onPress={onPress} onChange={onChange} + onAnnotationDragStateChange={onAnnotationDragStateChange} /> ); }, @@ -430,7 +470,11 @@ MapView.PinColors = PinColors && { }; const RCTMap = requireNativeComponent('RCTMap', MapView, { - nativeOnly: {onChange: true, onPress: true} + nativeOnly: { + onAnnotationDragStateChange: true, + onChange: true, + onPress: true + } }); module.exports = MapView; diff --git a/React/Views/RCTConvert+MapKit.m b/React/Views/RCTConvert+MapKit.m index 369ec2446..354b133b6 100644 --- a/React/Views/RCTConvert+MapKit.m +++ b/React/Views/RCTConvert+MapKit.m @@ -42,22 +42,23 @@ RCT_ENUM_CONVERTER(MKMapType, (@{ json = [self NSDictionary:json]; RCTMapAnnotation *annotation = [RCTMapAnnotation new]; annotation.coordinate = [self CLLocationCoordinate2D:json]; - annotation.title = [RCTConvert NSString:json[@"title"]]; - annotation.subtitle = [RCTConvert NSString:json[@"subtitle"]]; - annotation.identifier = [RCTConvert NSString:json[@"id"]]; - annotation.hasLeftCallout = [RCTConvert BOOL:json[@"hasLeftCallout"]]; - annotation.hasRightCallout = [RCTConvert BOOL:json[@"hasRightCallout"]]; - annotation.animateDrop = [RCTConvert BOOL:json[@"animateDrop"]]; - annotation.tintColor = [RCTConvert UIColor:json[@"tintColor"]]; - annotation.image = [RCTConvert UIImage:json[@"image"]]; + annotation.draggable = [self BOOL:json[@"draggable"]]; + annotation.title = [self NSString:json[@"title"]]; + annotation.subtitle = [self NSString:json[@"subtitle"]]; + annotation.identifier = [self NSString:json[@"id"]]; + annotation.hasLeftCallout = [self BOOL:json[@"hasLeftCallout"]]; + annotation.hasRightCallout = [self BOOL:json[@"hasRightCallout"]]; + annotation.animateDrop = [self BOOL:json[@"animateDrop"]]; + annotation.tintColor = [self UIColor:json[@"tintColor"]]; + annotation.image = [self UIImage:json[@"image"]]; annotation.viewIndex = - [RCTConvert NSInteger:json[@"viewIndex"] ?: @(NSNotFound)]; + [self NSInteger:json[@"viewIndex"] ?: @(NSNotFound)]; annotation.leftCalloutViewIndex = - [RCTConvert NSInteger:json[@"leftCalloutViewIndex"] ?: @(NSNotFound)]; + [self NSInteger:json[@"leftCalloutViewIndex"] ?: @(NSNotFound)]; annotation.rightCalloutViewIndex = - [RCTConvert NSInteger:json[@"rightCalloutViewIndex"] ?: @(NSNotFound)]; + [self NSInteger:json[@"rightCalloutViewIndex"] ?: @(NSNotFound)]; annotation.detailCalloutViewIndex = - [RCTConvert NSInteger:json[@"detailCalloutViewIndex"] ?: @(NSNotFound)]; + [self NSInteger:json[@"detailCalloutViewIndex"] ?: @(NSNotFound)]; return annotation; } @@ -66,19 +67,19 @@ RCT_ARRAY_CONVERTER(RCTMapAnnotation) + (RCTMapOverlay *)RCTMapOverlay:(id)json { json = [self NSDictionary:json]; - NSArray *locations = [RCTConvert NSDictionaryArray:json[@"coordinates"]]; + NSArray *locations = [self NSDictionaryArray:json[@"coordinates"]]; CLLocationCoordinate2D coordinates[locations.count]; NSUInteger index = 0; for (NSDictionary *location in locations) { - coordinates[index++] = [RCTConvert CLLocationCoordinate2D:location]; + coordinates[index++] = [self CLLocationCoordinate2D:location]; } RCTMapOverlay *overlay = [RCTMapOverlay polylineWithCoordinates:coordinates count:locations.count]; - overlay.strokeColor = [RCTConvert UIColor:json[@"strokeColor"]]; - overlay.identifier = [RCTConvert NSString:json[@"id"]]; - overlay.lineWidth = [RCTConvert CGFloat:json[@"lineWidth"] ?: @1]; + overlay.strokeColor = [self UIColor:json[@"strokeColor"]]; + overlay.identifier = [self NSString:json[@"id"]]; + overlay.lineWidth = [self CGFloat:json[@"lineWidth"] ?: @1]; return overlay; } diff --git a/React/Views/RCTMap.h b/React/Views/RCTMap.h index 63e01473e..e7a076810 100644 --- a/React/Views/RCTMap.h +++ b/React/Views/RCTMap.h @@ -30,6 +30,7 @@ RCT_EXTERN const CGFloat RCTMapZoomBoundBuffer; @property (nonatomic, copy) RCTBubblingEventBlock onChange; @property (nonatomic, copy) RCTBubblingEventBlock onPress; +@property (nonatomic, copy) RCTBubblingEventBlock onAnnotationDragStateChange; - (void)setAnnotations:(NSArray *)annotations; - (void)setOverlays:(NSArray *)overlays; diff --git a/React/Views/RCTMapAnnotation.h b/React/Views/RCTMapAnnotation.h index eb03ebaf9..ec5b4edc3 100644 --- a/React/Views/RCTMapAnnotation.h +++ b/React/Views/RCTMapAnnotation.h @@ -21,5 +21,6 @@ @property (nonatomic, assign) NSInteger leftCalloutViewIndex; @property (nonatomic, assign) NSInteger rightCalloutViewIndex; @property (nonatomic, assign) NSInteger detailCalloutViewIndex; +@property (nonatomic, assign) BOOL draggable; @end diff --git a/React/Views/RCTMapManager.m b/React/Views/RCTMapManager.m index 6718adde1..f12705b28 100644 --- a/React/Views/RCTMapManager.m +++ b/React/Views/RCTMapManager.m @@ -95,6 +95,7 @@ RCT_EXPORT_VIEW_PROPERTY(legalLabelInsets, UIEdgeInsets) RCT_EXPORT_VIEW_PROPERTY(mapType, MKMapType) RCT_EXPORT_VIEW_PROPERTY(annotations, NSArray) RCT_EXPORT_VIEW_PROPERTY(overlays, NSArray) +RCT_EXPORT_VIEW_PROPERTY(onAnnotationDragStateChange, RCTBubblingEventBlock) RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock) RCT_EXPORT_VIEW_PROPERTY(onPress, RCTBubblingEventBlock) RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, RCTMap) @@ -136,7 +137,6 @@ RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, RCTMap) - (void)mapView:(RCTMap *)mapView didSelectAnnotationView:(MKAnnotationView *)view { if (mapView.onPress && [view.annotation isKindOfClass:[RCTMapAnnotation class]]) { - RCTMapAnnotation *annotation = (RCTMapAnnotation *)view.annotation; mapView.onPress(@{ @"action": @"annotation-click", @@ -151,6 +151,30 @@ RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, RCTMap) } } +- (void)mapView:(RCTMap *)mapView annotationView:(MKAnnotationView *)view + didChangeDragState:(MKAnnotationViewDragState)newState + fromOldState:(MKAnnotationViewDragState)oldState +{ + static NSArray *states; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + states = @[@"idle", @"starting", @"dragging", @"canceling", @"ending"]; + }); + + if ([view.annotation isKindOfClass:[RCTMapAnnotation class]]) { + RCTMapAnnotation *annotation = (RCTMapAnnotation *)view.annotation; + if (mapView.onAnnotationDragStateChange) { + mapView.onAnnotationDragStateChange(@{ + @"state": states[newState], + @"oldState": states[oldState], + @"annotationId": annotation.identifier, + @"latitude": @(annotation.coordinate.latitude), + @"longitude": @(annotation.coordinate.longitude), + }); + } + } +} + - (MKAnnotationView *)mapView:(RCTMap *)mapView viewForAnnotation:(RCTMapAnnotation *)annotation { @@ -159,7 +183,6 @@ RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, RCTMap) } MKAnnotationView *annotationView; - annotationView.clipsToBounds = YES; if (annotation.viewIndex != NSNotFound) { NSString *reuseIdentifier = NSStringFromClass([RCTMapAnnotationView class]); @@ -205,7 +228,7 @@ RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, RCTMap) annotation.tintColor ?: [MKPinAnnotationView redPinColor]; } } - annotationView.canShowCallout = true; + annotationView.canShowCallout = (annotation.title.length > 0); if (annotation.leftCalloutViewIndex != NSNotFound) { annotationView.leftCalloutAccessoryView = @@ -255,6 +278,8 @@ RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, RCTMap) } } + annotationView.draggable = annotation.draggable; + return annotationView; }