diff --git a/AsyncDisplayKit/ASMapNode.h b/AsyncDisplayKit/ASMapNode.h index 5af8547ae5..1d9b96c7be 100644 --- a/AsyncDisplayKit/ASMapNode.h +++ b/AsyncDisplayKit/ASMapNode.h @@ -70,6 +70,12 @@ typedef NS_OPTIONS(NSUInteger, ASMapNodeShowAnnotationsOptions) */ @property (nonatomic, assign) ASMapNodeShowAnnotationsOptions showAnnotationsOptions; +/** + * @abstract The block which should return annotation image for static map based on provided annotation. + * @discussion This block is executed on an arbitrary serial queue. If this block is nil, standard pin is used. + */ +@property (nonatomic, copy, nullable) UIImage * _Nullable (^imageForStaticMapAnnotationBlock)(id annotation, CGPoint *centerOffset); + @end NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/ASMapNode.mm b/AsyncDisplayKit/ASMapNode.mm index a7d8aa6d80..7c64abef9a 100644 --- a/AsyncDisplayKit/ASMapNode.mm +++ b/AsyncDisplayKit/ASMapNode.mm @@ -167,6 +167,14 @@ - (void)setRegion:(MKCoordinateRegion)region self.options = options; } +- (void)setMapDelegate:(id)mapDelegate { + _mapDelegate = mapDelegate; + + if (_mapView) { + _mapView.delegate = mapDelegate; + } +} + #pragma mark - Snapshotter - (void)takeSnapshot @@ -209,15 +217,27 @@ - (void)takeSnapshot UIGraphicsBeginImageContextWithOptions(image.size, YES, image.scale); [image drawAtPoint:CGPointZero]; - // Get a standard annotation view pin. Future implementations should use a custom annotation image property. - MKAnnotationView *pin = [[MKPinAnnotationView alloc] initWithAnnotation:nil reuseIdentifier:@""]; - UIImage *pinImage = pin.image; - CGSize pinSize = pin.bounds.size; + UIImage *pinImage; + CGPoint pinCenterOffset = CGPointZero; + + // Get a standard annotation view pin if there is no custom annotation block. + if (!strongSelf.imageForStaticMapAnnotationBlock) { + pinImage = [strongSelf.class defaultPinImageWithCenterOffset:&pinCenterOffset]; + } for (id annotation in annotations) { + if (strongSelf.imageForStaticMapAnnotationBlock) { + // Get custom annotation image from custom annotation block. + pinImage = strongSelf.imageForStaticMapAnnotationBlock(annotation, &pinCenterOffset); + if (!pinImage) { + // just for case block returned nil, which can happen + pinImage = [strongSelf.class defaultPinImageWithCenterOffset:&pinCenterOffset]; + } + } + CGPoint point = [snapshot pointForCoordinate:annotation.coordinate]; if (CGRectContainsPoint(finalImageRect, point)) { - CGPoint pinCenterOffset = pin.centerOffset; + CGSize pinSize = pinImage.size; point.x -= pinSize.width / 2.0; point.y -= pinSize.height / 2.0; point.x += pinCenterOffset.x; @@ -235,6 +255,17 @@ - (void)takeSnapshot }]; } ++ (UIImage *)defaultPinImageWithCenterOffset:(CGPoint *)centerOffset +{ + static MKAnnotationView *pin; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + pin = [[MKPinAnnotationView alloc] initWithAnnotation:nil reuseIdentifier:@""]; + }); + *centerOffset = pin.centerOffset; + return pin.image; +} + - (void)setUpSnapshotter { _snapshotter = [[MKMapSnapshotter alloc] initWithOptions:self.options]; diff --git a/examples/ASMapNode/Sample.xcodeproj/project.pbxproj b/examples/ASMapNode/Sample.xcodeproj/project.pbxproj index b7da6bb298..1bc3e8c8b3 100644 --- a/examples/ASMapNode/Sample.xcodeproj/project.pbxproj +++ b/examples/ASMapNode/Sample.xcodeproj/project.pbxproj @@ -14,13 +14,14 @@ 694993D81C8B334F00491CA5 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 694993D71C8B334F00491CA5 /* ViewController.m */; }; 694993DD1C8B334F00491CA5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 694993DC1C8B334F00491CA5 /* Assets.xcassets */; }; 694993E01C8B334F00491CA5 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 694993DE1C8B334F00491CA5 /* LaunchScreen.storyboard */; }; + 905C815E1D362E9400EA2625 /* CustomMapAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = 905C815D1D362E9400EA2625 /* CustomMapAnnotation.m */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 15AD337503831C4D33FF8B3A /* Pods-Sample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig"; sourceTree = ""; }; 465082D55CCF1B0CB1AEBACC /* libPods-Sample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Sample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 5E5E62821D13F39400D81E38 /* MapHandlerNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MapHandlerNode.h; sourceTree = ""; }; - 5E5E62831D13F39400D81E38 /* MapHandlerNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MapHandlerNode.m; sourceTree = ""; }; + 5E5E62831D13F39400D81E38 /* MapHandlerNode.m */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; path = MapHandlerNode.m; sourceTree = ""; tabWidth = 2; }; 694993CD1C8B334F00491CA5 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 694993D11C8B334F00491CA5 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 694993D31C8B334F00491CA5 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; @@ -30,6 +31,8 @@ 694993DC1C8B334F00491CA5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 694993DF1C8B334F00491CA5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 694993E11C8B334F00491CA5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 905C815C1D362E9400EA2625 /* CustomMapAnnotation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CustomMapAnnotation.h; sourceTree = ""; }; + 905C815D1D362E9400EA2625 /* CustomMapAnnotation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CustomMapAnnotation.m; sourceTree = ""; }; 97482F27BE2F7583EFE1BC2C /* Pods-Sample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.release.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -89,6 +92,8 @@ 694993D71C8B334F00491CA5 /* ViewController.m */, 5E5E62821D13F39400D81E38 /* MapHandlerNode.h */, 5E5E62831D13F39400D81E38 /* MapHandlerNode.m */, + 905C815C1D362E9400EA2625 /* CustomMapAnnotation.h */, + 905C815D1D362E9400EA2625 /* CustomMapAnnotation.m */, 694993DC1C8B334F00491CA5 /* Assets.xcassets */, 694993D01C8B334F00491CA5 /* Supporting Files */, ); @@ -229,6 +234,7 @@ 694993D81C8B334F00491CA5 /* ViewController.m in Sources */, 694993D51C8B334F00491CA5 /* AppDelegate.m in Sources */, 694993D21C8B334F00491CA5 /* main.m in Sources */, + 905C815E1D362E9400EA2625 /* CustomMapAnnotation.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/examples/ASMapNode/Sample/Assets.xcassets/Contents.json b/examples/ASMapNode/Sample/Assets.xcassets/Contents.json new file mode 100644 index 0000000000..da4a164c91 --- /dev/null +++ b/examples/ASMapNode/Sample/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/ASMapNode/Sample/Assets.xcassets/Hill.imageset/Contents.json b/examples/ASMapNode/Sample/Assets.xcassets/Hill.imageset/Contents.json new file mode 100644 index 0000000000..273884cba6 --- /dev/null +++ b/examples/ASMapNode/Sample/Assets.xcassets/Hill.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "hill.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "hill@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "hill@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/ASMapNode/Sample/Assets.xcassets/Hill.imageset/hill.png b/examples/ASMapNode/Sample/Assets.xcassets/Hill.imageset/hill.png new file mode 100644 index 0000000000..8998668eb0 Binary files /dev/null and b/examples/ASMapNode/Sample/Assets.xcassets/Hill.imageset/hill.png differ diff --git a/examples/ASMapNode/Sample/Assets.xcassets/Hill.imageset/hill@2x.png b/examples/ASMapNode/Sample/Assets.xcassets/Hill.imageset/hill@2x.png new file mode 100644 index 0000000000..d64af0dd9d Binary files /dev/null and b/examples/ASMapNode/Sample/Assets.xcassets/Hill.imageset/hill@2x.png differ diff --git a/examples/ASMapNode/Sample/Assets.xcassets/Hill.imageset/hill@3x.png b/examples/ASMapNode/Sample/Assets.xcassets/Hill.imageset/hill@3x.png new file mode 100644 index 0000000000..761c66684a Binary files /dev/null and b/examples/ASMapNode/Sample/Assets.xcassets/Hill.imageset/hill@3x.png differ diff --git a/examples/ASMapNode/Sample/Assets.xcassets/Water.imageset/Contents.json b/examples/ASMapNode/Sample/Assets.xcassets/Water.imageset/Contents.json new file mode 100644 index 0000000000..f54c1c3b60 --- /dev/null +++ b/examples/ASMapNode/Sample/Assets.xcassets/Water.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "water.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "water@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "water@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/ASMapNode/Sample/Assets.xcassets/Water.imageset/water.png b/examples/ASMapNode/Sample/Assets.xcassets/Water.imageset/water.png new file mode 100644 index 0000000000..cdff6fd035 Binary files /dev/null and b/examples/ASMapNode/Sample/Assets.xcassets/Water.imageset/water.png differ diff --git a/examples/ASMapNode/Sample/Assets.xcassets/Water.imageset/water@2x.png b/examples/ASMapNode/Sample/Assets.xcassets/Water.imageset/water@2x.png new file mode 100644 index 0000000000..2cd019f20c Binary files /dev/null and b/examples/ASMapNode/Sample/Assets.xcassets/Water.imageset/water@2x.png differ diff --git a/examples/ASMapNode/Sample/Assets.xcassets/Water.imageset/water@3x.png b/examples/ASMapNode/Sample/Assets.xcassets/Water.imageset/water@3x.png new file mode 100644 index 0000000000..e45cd67f2d Binary files /dev/null and b/examples/ASMapNode/Sample/Assets.xcassets/Water.imageset/water@3x.png differ diff --git a/examples/ASMapNode/Sample/CustomMapAnnotation.h b/examples/ASMapNode/Sample/CustomMapAnnotation.h new file mode 100644 index 0000000000..d94f0153f4 --- /dev/null +++ b/examples/ASMapNode/Sample/CustomMapAnnotation.h @@ -0,0 +1,28 @@ +// +// CustomMapAnnotation.h +// ASDKMapTest +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +#import +#import + +@interface CustomMapAnnotation : NSObject + +@property (assign, nonatomic) CLLocationCoordinate2D coordinate; +@property (copy, nonatomic, nullable) UIImage *image; +@property (copy, nonatomic, nullable) NSString *title; +@property (copy, nonatomic, nullable) NSString *subtitle; + +@end diff --git a/examples/ASMapNode/Sample/CustomMapAnnotation.m b/examples/ASMapNode/Sample/CustomMapAnnotation.m new file mode 100644 index 0000000000..a5da10ac94 --- /dev/null +++ b/examples/ASMapNode/Sample/CustomMapAnnotation.m @@ -0,0 +1,22 @@ +// +// CustomMapAnnotation.m +// ASDKMapTest +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +#import "CustomMapAnnotation.h" + +@implementation CustomMapAnnotation + +@end diff --git a/examples/ASMapNode/Sample/MapHandlerNode.m b/examples/ASMapNode/Sample/MapHandlerNode.m index 8a78445bb2..fc55eb6bc6 100644 --- a/examples/ASMapNode/Sample/MapHandlerNode.m +++ b/examples/ASMapNode/Sample/MapHandlerNode.m @@ -16,6 +16,7 @@ // #import "MapHandlerNode.h" +#import "CustomMapAnnotation.h" #import @@ -90,6 +91,22 @@ - (void)didLoad [_liveMapToggleButton setTitle:[self liveMapStr] withFont:nil withColor:[UIColor blueColor] forState:ASControlStateNormal]; [_liveMapToggleButton setTitle:[self liveMapStr] withFont:[UIFont systemFontOfSize:14] withColor:[UIColor blueColor] forState:ASControlStateHighlighted]; [_liveMapToggleButton addTarget:self action:@selector(toggleLiveMap) forControlEvents:ASControlNodeEventTouchUpInside]; + + // avoiding retain cycles + __weak MapHandlerNode *weakSelf = self; + + self.mapNode.imageForStaticMapAnnotationBlock = ^UIImage *(id annotation, CGPoint *centerOffset){ + MapHandlerNode *grabbedSelf = weakSelf; + if (grabbedSelf) { + if ([annotation isKindOfClass:[CustomMapAnnotation class]]) { + CustomMapAnnotation *customAnnotation = (CustomMapAnnotation *)annotation; + return customAnnotation.image; + } + } + return nil; + }; + + [self addAnnotations]; } #pragma mark - Layout @@ -183,6 +200,30 @@ -(void)toggleLiveMap #pragma mark - Helpers +- (void)addAnnotations { + + MKPointAnnotation *brno = [MKPointAnnotation new]; + brno.coordinate = CLLocationCoordinate2DMake(49.2002211, 16.6078411); + brno.title = @"Brno city"; + + CustomMapAnnotation *atlantic = [CustomMapAnnotation new]; + atlantic.coordinate = CLLocationCoordinate2DMake(38.6442228, -29.9956942); + atlantic.title = @"Atlantic ocean"; + atlantic.image = [UIImage imageNamed:@"Water"]; + + CustomMapAnnotation *kilimanjaro = [CustomMapAnnotation new]; + kilimanjaro.coordinate = CLLocationCoordinate2DMake(-3.075833, 37.353333); + kilimanjaro.title = @"Kilimanjaro"; + kilimanjaro.image = [UIImage imageNamed:@"Hill"]; + + CustomMapAnnotation *mtblanc = [CustomMapAnnotation new]; + mtblanc.coordinate = CLLocationCoordinate2DMake(45.8325, 6.864444); + mtblanc.title = @"Mont Blanc"; + mtblanc.image = [UIImage imageNamed:@"Hill"]; + + self.mapNode.annotations = @[brno, atlantic, kilimanjaro, mtblanc]; +} + -(NSString *)liveMapStr { return _mapNode.liveMap ? @"Live Map is ON" : @"Live Map is OFF"; @@ -235,6 +276,21 @@ - (BOOL)editableTextNode:(ASEditableTextNode *)editableTextNode shouldChangeText return YES; } +- (MKAnnotationView *)annotationViewForAnnotation:(id)annotation +{ + MKAnnotationView *av; + if ([annotation isKindOfClass:[CustomMapAnnotation class]]) { + av = [[MKAnnotationView alloc] init]; + av.centerOffset = CGPointMake(21, 21); + av.image = [(CustomMapAnnotation *)annotation image]; + } else { + av = [[MKPinAnnotationView alloc] initWithAnnotation:nil reuseIdentifier:@""]; + } + + av.opaque = NO; + return av; +} + #pragma mark - MKMapViewDelegate - (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated { @@ -244,4 +300,9 @@ - (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated { _deltaLonEditableNode.attributedText = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%f", mapView.region.span.longitudeDelta]]; } +- (MKAnnotationView *)mapView:(MKMapView *)__unused mapView viewForAnnotation:(id)annotation +{ + return [self annotationViewForAnnotation:annotation]; +} + @end