Skip to content
This repository has been archived by the owner on Feb 2, 2023. It is now read-only.

[ASMapNode] Add custom pin annotation for static maps #1890

6 changes: 6 additions & 0 deletions AsyncDisplayKit/ASMapNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<MKAnnotation> annotation, CGPoint *centerOffset);

@end

NS_ASSUME_NONNULL_END
Expand Down
41 changes: 36 additions & 5 deletions AsyncDisplayKit/ASMapNode.mm
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,14 @@ - (void)setRegion:(MKCoordinateRegion)region
self.options = options;
}

- (void)setMapDelegate:(id<MKMapViewDelegate>)mapDelegate {
_mapDelegate = mapDelegate;

if (_mapView) {
_mapView.delegate = mapDelegate;
}
}

#pragma mark - Snapshotter

- (void)takeSnapshot
Expand Down Expand Up @@ -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<MKAnnotation> annotation in annotations) {
if (strongSelf.imageForStaticMapAnnotationBlock) {
// Get custom annotation view pin 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];
}
}

Copy link
Contributor

@george-gw george-gw Aug 8, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this can be changed to:

Always get the default pin, just in case it might be used (better here than in a for loop)

CGPoint defaultPinCenterOffset = CGPointZero;
UIImage *defaultImage = [strongSelf.class defaultPinImageWithCenterOffset:&defaultPinCenterOffset];

Now that we have the default image, we can loop through the annotations, if the property is not set or if it returns nil, we will just use the default pin.

for (id<MKAnnotation> annotation in annotations) {
  UIImage *pinImage = nil;
  CGPoint pinCenterOffset = CGPointZero;

  if (strongSelf.imageForStaticMapAnnotationBlock) {
    pinImage = strongSelf.imageForStaticMapAnnotationBlock(annotation, &pinCenterOffset);
  }

  if (!pinImage) {
    pinImage = defaultImage;
    pinCenterOffset = defaultPinCenterOffset;
  }

[...]

The idea is that if we are to use the defaultImage it is most likely going to be for more than one annotation, and this way we don't have to call the defaultPin several times since it will always return the same value anyway.

What do you think?

PS: If we won't go this way, either remove the comments that you have, or change the references from 'annotation view' to 'image for pin' or something to be consistent with the new code.

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;
Expand All @@ -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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of instantiating an MKPinAnnotationView every time (which can get very expensive!) let's make this a class method + (UIImage *)defaultPinImageWithCenterOffset: and wrap the body of it in a dispatch_once (see my last comment for example code).

return pin.image;
}

- (void)setUpSnapshotter
{
_snapshotter = [[MKMapSnapshotter alloc] initWithOptions:self.options];
Expand Down
8 changes: 7 additions & 1 deletion examples/ASMapNode/Sample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "<group>"; };
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 = "<group>"; };
5E5E62831D13F39400D81E38 /* MapHandlerNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MapHandlerNode.m; sourceTree = "<group>"; };
5E5E62831D13F39400D81E38 /* MapHandlerNode.m */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; path = MapHandlerNode.m; sourceTree = "<group>"; 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 = "<group>"; };
694993D31C8B334F00491CA5 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
Expand All @@ -30,6 +31,8 @@
694993DC1C8B334F00491CA5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
694993DF1C8B334F00491CA5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
694993E11C8B334F00491CA5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
905C815C1D362E9400EA2625 /* CustomMapAnnotation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CustomMapAnnotation.h; sourceTree = "<group>"; };
905C815D1D362E9400EA2625 /* CustomMapAnnotation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CustomMapAnnotation.m; sourceTree = "<group>"; };
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 = "<group>"; };
/* End PBXFileReference section */

Expand Down Expand Up @@ -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 */,
);
Expand Down Expand Up @@ -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;
};
Expand Down
6 changes: 6 additions & 0 deletions examples/ASMapNode/Sample/Assets.xcassets/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Original file line number Diff line number Diff line change
@@ -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"
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -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"
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 28 additions & 0 deletions examples/ASMapNode/Sample/CustomMapAnnotation.h
Original file line number Diff line number Diff line change
@@ -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 <Foundation/Foundation.h>
#import <MapKit/MapKit.h>

@interface CustomMapAnnotation : NSObject<MKAnnotation>

@property (assign, nonatomic) CLLocationCoordinate2D coordinate;
@property (copy, nonatomic, nullable) UIImage *image;
@property (copy, nonatomic, nullable) NSString *title;
@property (copy, nonatomic, nullable) NSString *subtitle;

@end
22 changes: 22 additions & 0 deletions examples/ASMapNode/Sample/CustomMapAnnotation.m
Original file line number Diff line number Diff line change
@@ -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
61 changes: 61 additions & 0 deletions examples/ASMapNode/Sample/MapHandlerNode.m
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
//

#import "MapHandlerNode.h"
#import "CustomMapAnnotation.h"

#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>

Expand Down Expand Up @@ -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<MKAnnotation> annotation, CGPoint *centerOffset){
MapHandlerNode *grabbedSelf = weakSelf;
if (grabbedSelf) {
if ([annotation isKindOfClass:[CustomMapAnnotation class]]) {
MKAnnotationView *av = [grabbedSelf annotationViewForAnnotation:annotation];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm unfortunately we just cannot allocate views in here, since we're off the main thread. Since CustomMapAnnotation has an image property, let's use that. Also let's make sure to set centerOffset to 21,21

return av.image;
Copy link
Contributor

@Adlai-Holler Adlai-Holler Aug 18, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't allocate views in this block, since we're off the main thread. @michalziman Since CustomMapAnnotation has an image property, can you modify this like:

CustomMapAnnotation *customAnnotation = (CustomMapAnnotation *)annotation;
return customAnnotation.image;

}
}
return nil;
};

[self addAnnotations];
}

#pragma mark - Layout
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -235,6 +276,21 @@ - (BOOL)editableTextNode:(ASEditableTextNode *)editableTextNode shouldChangeText
return YES;
}

- (MKAnnotationView *)annotationViewForAnnotation:(id<MKAnnotation>)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 {
Expand All @@ -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<MKAnnotation>)annotation
{
return [self annotationViewForAnnotation:annotation];
}

@end