Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MapView] Support for annotation callouts, annotation press, callout presses and pin animation #1247

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 82 additions & 1 deletion Libraries/Components/MapView/MapView.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,34 @@ type MapRegion = {
var MapView = React.createClass({
mixins: [NativeMethodsMixin],

checkAnnotationIds: function (annotations: Array<Object>) {

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
});
},

componentWillMount: function() {
if (this.props.annotations) {
this.checkAnnotationIds(this.props.annotations);
}
},

componentWillReceiveProps: function(nextProps: Object) {
if (nextProps.annotations) {
this.checkAnnotationIds(nextProps.annotations);
}
},

propTypes: {
/**
* Used to style and layout the `MapView`. See `StyleSheet.js` and
Expand Down Expand Up @@ -126,11 +154,34 @@ var MapView = React.createClass({
latitude: React.PropTypes.number.isRequired,
longitude: React.PropTypes.number.isRequired,

/**
* Whether the pin drop should be animated or not
*/
animateDrop: React.PropTypes.bool,

/**
* Annotation title/subtile.
*/
title: React.PropTypes.string,
subtitle: React.PropTypes.string,

/**
* Whether the Annotation has callout buttons.
*/
hasLeftCallout: React.PropTypes.bool,
hasRightCallout: React.PropTypes.bool,

/**
* Event handlers for callout buttons.
*/
onLeftCalloutPress: React.PropTypes.func,
onRightCalloutPress: React.PropTypes.func,

/**
* annotation id
*/
id: React.PropTypes.string

})),

/**
Expand Down Expand Up @@ -158,6 +209,11 @@ var MapView = React.createClass({
* Callback that is called once, when the user is done moving the map.
*/
onRegionChangeComplete: React.PropTypes.func,

/**
* Callback that is called once, when the user is clicked on a annotation.
*/
onAnnotationPress: React.PropTypes.func,
},

_onChange: function(event: Event) {
Expand All @@ -170,8 +226,33 @@ var MapView = React.createClass({
}
},

_onPress: function(event: Event) {
if (event.nativeEvent.action === "annotation-click")
this.props.onAnnotationPress && this.props.onAnnotationPress(event.nativeEvent.annotation);

if (event.nativeEvent.action === "callout-click") {
if (!this.props.annotations) {
return;
}

// Find the annotation with the id of what has been pressed
for (var i = 0; i < this.props.annotations.length; i++) {
var annotation = this.props.annotations[i];
if (annotation['id'] === event.nativeEvent.annotationId) {
// Pass the right function
if (event.nativeEvent.side === "left")
annotation.onLeftCalloutPress && annotation.onLeftCalloutPress(event.nativeEvent);

if (event.nativeEvent.side === "right")
annotation.onRightCalloutPress && annotation.onRightCalloutPress(event.nativeEvent);
}
}

}
},

render: function() {
return <RCTMap {...this.props} onChange={this._onChange} />;
return <RCTMap {...this.props} onPress={this._onPress} onChange={this._onChange} />;
},
});

Expand Down
18 changes: 18 additions & 0 deletions React/Modules/RCTPointAnnotation.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// RCTPointAnnotation.h
// React
//
// Created by David Mohl on 5/12/15.
// Copyright (c) 2015 Facebook. All rights reserved.
//

#import <MapKit/MapKit.h>

@interface RCTPointAnnotation : MKPointAnnotation <MKAnnotation>

@property NSString *identifier;
@property BOOL hasLeftCallout;
@property BOOL hasRightCallout;
@property BOOL animateDrop;

@end
13 changes: 13 additions & 0 deletions React/Modules/RCTPointAnnotation.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// RCTPointAnnotation.m
// React
//
// Created by David Mohl on 5/12/15.
// Copyright (c) 2015 Facebook. All rights reserved.
//

#import "RCTPointAnnotation.h"

@implementation RCTPointAnnotation

@end
6 changes: 6 additions & 0 deletions React/React.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
58114A171AAE854800E7D092 /* RCTPickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58114A151AAE854800E7D092 /* RCTPickerManager.m */; };
58114A501AAE93D500E7D092 /* RCTAsyncLocalStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 58114A4E1AAE93D500E7D092 /* RCTAsyncLocalStorage.m */; };
58C571C11AA56C1900CDF9C8 /* RCTDatePickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58C571BF1AA56C1900CDF9C8 /* RCTDatePickerManager.m */; };
63F014C01B02080B003B75D2 /* RCTPointAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = 63F014BF1B02080B003B75D2 /* RCTPointAnnotation.m */; };
830A229E1A66C68A008503DA /* RCTRootView.m in Sources */ = {isa = PBXBuildFile; fileRef = 830A229D1A66C68A008503DA /* RCTRootView.m */; };
830BA4551A8E3BDA00D53203 /* RCTCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 830BA4541A8E3BDA00D53203 /* RCTCache.m */; };
832348161A77A5AA00B55238 /* Layout.c in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FC71A68125100A75B9A /* Layout.c */; };
Expand Down Expand Up @@ -200,6 +201,8 @@
58114A4F1AAE93D500E7D092 /* RCTAsyncLocalStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAsyncLocalStorage.h; sourceTree = "<group>"; };
58C571BF1AA56C1900CDF9C8 /* RCTDatePickerManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDatePickerManager.m; sourceTree = "<group>"; };
58C571C01AA56C1900CDF9C8 /* RCTDatePickerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDatePickerManager.h; sourceTree = "<group>"; };
63F014BE1B02080B003B75D2 /* RCTPointAnnotation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPointAnnotation.h; sourceTree = "<group>"; };
63F014BF1B02080B003B75D2 /* RCTPointAnnotation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPointAnnotation.m; sourceTree = "<group>"; };
830213F31A654E0800B993E6 /* RCTBridgeModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTBridgeModule.h; sourceTree = "<group>"; };
830A229C1A66C68A008503DA /* RCTRootView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRootView.h; sourceTree = "<group>"; };
830A229D1A66C68A008503DA /* RCTRootView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRootView.m; sourceTree = "<group>"; };
Expand Down Expand Up @@ -278,6 +281,8 @@
13B07FEE1A69327A00A75B9A /* RCTTiming.m */,
13E067481A70F434002CDEE1 /* RCTUIManager.h */,
13E067491A70F434002CDEE1 /* RCTUIManager.m */,
63F014BE1B02080B003B75D2 /* RCTPointAnnotation.h */,
63F014BF1B02080B003B75D2 /* RCTPointAnnotation.m */,
);
path = Modules;
sourceTree = "<group>";
Expand Down Expand Up @@ -573,6 +578,7 @@
830BA4551A8E3BDA00D53203 /* RCTCache.m in Sources */,
137327E71AA5CF210034F82E /* RCTTabBar.m in Sources */,
00C1A2B31AC0B7E000E89A1C /* RCTDevMenu.m in Sources */,
63F014C01B02080B003B75D2 /* RCTPointAnnotation.m in Sources */,
14435CE51AAC4AE100FC20F4 /* RCTMap.m in Sources */,
134FCB3E1A6E7F0800051CC8 /* RCTWebViewExecutor.m in Sources */,
13B0801C1A69489C00A75B9A /* RCTNavItem.m in Sources */,
Expand Down
6 changes: 5 additions & 1 deletion React/Views/RCTConvert+MapKit.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
//

#import <MapKit/MapKit.h>

#import "RCTPointAnnotation.h"
#import "RCTConvert.h"

@interface RCTConvert (MapKit)
Expand All @@ -16,8 +16,12 @@
+ (MKCoordinateRegion)MKCoordinateRegion:(id)json;
+ (MKShape *)MKShape:(id)json;
+ (MKMapType)MKMapType:(id)json;
+ (RCTPointAnnotation *)RCTPointAnnotation:(id)json;

typedef NSArray MKShapeArray;
+ (MKShapeArray *)MKShapeArray:(id)json;

typedef NSArray RCTPointAnnotationArray;
+ (RCTPointAnnotationArray *)RCTPointAnnotationArray:(id)json;

@end
18 changes: 17 additions & 1 deletion React/Views/RCTConvert+MapKit.m
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
//

#import "RCTConvert+MapKit.h"

#import "RCTConvert+CoreLocation.h"
#import "RCTPointAnnotation.h"

@implementation RCTConvert(MapKit)

Expand Down Expand Up @@ -49,4 +49,20 @@ + (MKShape *)MKShape:(id)json
@"hybrid": @(MKMapTypeHybrid),
}), MKMapTypeStandard, integerValue)

+ (RCTPointAnnotation *)RCTPointAnnotation:(id)json
{
json = [self NSDictionary:json];
RCTPointAnnotation *shape = [[RCTPointAnnotation alloc] init];
shape.coordinate = [self CLLocationCoordinate2D:json];
shape.title = [RCTConvert NSString:json[@"title"]];
shape.subtitle = [RCTConvert NSString:json[@"subtitle"]];
shape.identifier = [RCTConvert NSString:json[@"id"]];
shape.hasLeftCallout = [RCTConvert BOOL:json[@"hasLeftCallout"]];
shape.hasRightCallout = [RCTConvert BOOL:json[@"hasRightCallout"]];
shape.animateDrop = [RCTConvert BOOL:json[@"animateDrop"]];
return shape;
}

RCT_ARRAY_CONVERTER(RCTPointAnnotation)

@end
3 changes: 2 additions & 1 deletion React/Views/RCTMap.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ extern const CGFloat RCTMapZoomBoundBuffer;
@property (nonatomic, assign) CGFloat maxDelta;
@property (nonatomic, assign) UIEdgeInsets legalLabelInsets;
@property (nonatomic, strong) NSTimer *regionChangeObserveTimer;
@property (nonatomic, strong) NSMutableArray *annotationIds;

- (void)setAnnotations:(MKShapeArray *)annotations;
- (void)setAnnotations:(RCTPointAnnotationArray *)annotations;

@end
60 changes: 50 additions & 10 deletions React/Views/RCTMap.m
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ @implementation RCTMap
- (instancetype)init
{
if ((self = [super init])) {

_hasStartedRendering = NO;

// Find Apple link label
for (UIView *subview in self.subviews) {
if ([NSStringFromClass(subview.class) isEqualToString:@"MKAttributionLabel"]) {
Expand All @@ -55,7 +55,7 @@ - (void)reactSetFrame:(CGRect)frame
- (void)layoutSubviews
{
[super layoutSubviews];

if (_legalLabel) {
dispatch_async(dispatch_get_main_queue(), ^{
CGRect frame = _legalLabel.frame;
Expand Down Expand Up @@ -86,7 +86,7 @@ - (void)setShowsUserLocation:(BOOL)showsUserLocation
}
}
super.showsUserLocation = showsUserLocation;

// If it needs to show user location, force map view centered
// on user's current location on user location updates
_followUserLocation = showsUserLocation;
Expand All @@ -99,25 +99,65 @@ - (void)setRegion:(MKCoordinateRegion)region animated:(BOOL)animated
if (!CLLocationCoordinate2DIsValid(region.center)) {
return;
}

// If new span values are nil, use old values instead
if (!region.span.latitudeDelta) {
region.span.latitudeDelta = self.region.span.latitudeDelta;
}
if (!region.span.longitudeDelta) {
region.span.longitudeDelta = self.region.span.longitudeDelta;
}

// Animate to new position
[super setRegion:region animated:animated];
}

- (void)setAnnotations:(MKShapeArray *)annotations
- (void)setAnnotations:(RCTPointAnnotationArray *)annotations
{
[self removeAnnotations:self.annotations];
if (annotations.count) {
[self addAnnotations:annotations];
NSMutableArray *newAnnotationIds = [[NSMutableArray alloc] init];
NSMutableArray *annotationsToDelete = [[NSMutableArray alloc] init];
NSMutableArray *annotationsToAdd = [[NSMutableArray alloc] init];

for (RCTPointAnnotation *annotation in annotations) {
if (![annotation isKindOfClass:[RCTPointAnnotation class]]) {
continue;
}

[newAnnotationIds addObject:annotation.identifier];

// If the current set does not contain the new annotation, mark it as add
if (![self.annotationIds containsObject:annotation.identifier]) {
[annotationsToAdd addObject:annotation];
}
}

for (RCTPointAnnotation *annotation in self.annotations) {
if (![annotation isKindOfClass:[RCTPointAnnotation class]]) {
continue;
}

// If the new set does not contain an existing annotation, mark it as delete
if (![newAnnotationIds containsObject:annotation.identifier]) {
[annotationsToDelete addObject:annotation];
}
}

if (annotationsToDelete.count) {
[self removeAnnotations:annotationsToDelete];
}

if (annotationsToAdd.count) {
[self addAnnotations:annotationsToAdd];
}

NSMutableArray *newIds = [[NSMutableArray alloc] init];
for (RCTPointAnnotation *anno in self.annotations) {
if ([anno isKindOfClass:[MKUserLocation class]]) {
continue;
}
[newIds addObject:anno.identifier];
}
self.annotationIds = newIds;
}

@end
Loading