Substantially improved ASMapNode and made it a lot clearer and less complex internally.

This commit is contained in:
Aaron Schubert
2015-12-03 11:22:05 +00:00
parent 863156b2dc
commit 79b4c95749
4 changed files with 127 additions and 113 deletions

View File

@@ -8,38 +8,36 @@
#import <AsyncDisplayKit/AsyncDisplayKit.h> #import <AsyncDisplayKit/AsyncDisplayKit.h>
#import <MapKit/MapKit.h> #import <MapKit/MapKit.h>
@interface ASMapNode : ASControlNode
@interface ASMapNode : ASImageNode
- (instancetype)initWithCoordinate:(CLLocationCoordinate2D)coordinate NS_DESIGNATED_INITIALIZER; - (instancetype)initWithCoordinate:(CLLocationCoordinate2D)coordinate NS_DESIGNATED_INITIALIZER;
/** /**
This is the snapshot shot image node, this will be hidden (but not nil) when .liveMap = YES This is the MKMapView that is the live map part of ASMapNode. This will be nil if .liveMap = NO. Note, MKMapView is *not* thread-safe.
*/ */
@property (nonatomic, readonly) ASImageNode *mapImage; @property (nonatomic, readonly) MKMapView *mapView;
/**
This is the ASDisplayNode that backs the MKMapView. This will be nil if .liveMap = NO. To access the underlying MKMapView, in order to set a delegate for example, use (MKMapView *)mapView.view;
*/
@property (nonatomic, readonly) ASDisplayNode *mapView;
/** /**
Set this to YES to turn the snapshot into an interactive MKMapView and vice versa. Defaults to NO. Set this to YES to turn the snapshot into an interactive MKMapView and vice versa. Defaults to NO.
*/ */
@property (nonatomic, assign, getter=isLiveMap) BOOL liveMap; @property (nonatomic, assign, getter=isLiveMap) BOOL liveMap;
/**
@abstract Explicitly set the size of the map and therefore the size of ASMapNode. Defaults to CGSizeMake(constrainedSize.max.width, 256).
@discussion If the mapSize width or height is greater than the available space, then ASMapNode will take the maximum space available.
@result The current size of the ASMapNode.
*/
@property (nonatomic, assign) CGSize mapSize;
/** /**
@abstract Whether ASMapNode should automatically request a new map snapshot to correspond to the new node size. Defaults to YES. @abstract Whether ASMapNode should automatically request a new map snapshot to correspond to the new node size. Defaults to YES.
@discussion If mapSize is set then this will be set to NO, since the size will be the same in all orientations. @discussion If mapSize is set then this will be set to NO, since the size will be the same in all orientations.
*/ */
@property (nonatomic, assign) BOOL automaticallyReloadsMapImageOnOrientationChange; @property (nonatomic, assign) BOOL needsMapReloadOnBoundsChange;
/** /**
Set the delegate of the MKMapView. Set the delegate of the MKMapView. This can be set even before mapView is created and will be set on the map in the case that the liveMap mode is engaged.
*/ */
@property (nonatomic, weak) id <MKMapViewDelegate> mapDelegate; @property (nonatomic, weak) id <MKMapViewDelegate> mapDelegate;
/** /**
* @discussion This method adds annotations to the static map view and also to the live map view. * @discussion This method set the annotations of the static map view and also to the live map view. Passing an empty array clears the map of any annotations.
* @param annotations An array of objects that conform to the MKAnnotation protocol * @param annotations An array of objects that conform to the MKAnnotation protocol
*/ */
- (void)addAnnotations:(NSArray *)annotations; - (void)setAnnotations:(NSArray *)annotations;
@end @end

View File

@@ -14,19 +14,18 @@
@interface ASMapNode() @interface ASMapNode()
{ {
ASDN::RecursiveMutex _propertyLock; ASDN::RecursiveMutex _propertyLock;
CGSize _nodeSize;
MKMapSnapshotter *_snapshotter; MKMapSnapshotter *_snapshotter;
MKMapSnapshotOptions *_options; MKMapSnapshotOptions *_options;
CGSize _maxSize;
NSArray *_annotations; NSArray *_annotations;
ASDisplayNode *_mapNode;
CLLocationCoordinate2D _centerCoordinateOfMap;
} }
@end @end
@implementation ASMapNode @implementation ASMapNode
@synthesize liveMap = _liveMap; @synthesize liveMap = _liveMap;
@synthesize mapSize = _mapSize; @synthesize needsMapReloadOnBoundsChange = _needsMapReloadOnBoundsChange;
@synthesize automaticallyReloadsMapImageOnOrientationChange = _automaticallyReloadsMapImageOnOrientationChange;
@synthesize mapDelegate = _mapDelegate; @synthesize mapDelegate = _mapDelegate;
- (instancetype)initWithCoordinate:(CLLocationCoordinate2D)coordinate - (instancetype)initWithCoordinate:(CLLocationCoordinate2D)coordinate
@@ -35,26 +34,23 @@
return nil; return nil;
} }
self.backgroundColor = ASDisplayNodeDefaultPlaceholderColor(); self.backgroundColor = ASDisplayNodeDefaultPlaceholderColor();
_automaticallyReloadsMapImageOnOrientationChange = YES; self.clipsToBounds = YES;
_needsMapReloadOnBoundsChange = YES;
_liveMap = NO; _liveMap = NO;
_centerCoordinateOfMap = kCLLocationCoordinate2DInvalid;
_options = [[MKMapSnapshotOptions alloc] init]; _options = [[MKMapSnapshotOptions alloc] init];
_options.region = MKCoordinateRegionMakeWithDistance(coordinate, 1000, 1000);; _options.region = MKCoordinateRegionMakeWithDistance(coordinate, 1000, 1000);;
_mapImage = [[ASImageNode alloc]init];
_mapImage.clipsToBounds = YES;
[self addSubnode:_mapImage];
_maxSize = self.bounds.size;
return self; return self;
} }
- (void)addAnnotations:(NSArray *)annotations - (void)setAnnotations:(NSArray *)annotations
{ {
ASDN::MutexLocker l(_propertyLock); ASDN::MutexLocker l(_propertyLock);
if (annotations.count == 0) {
return;
}
_annotations = [annotations copy]; _annotations = [annotations copy];
if (annotations.count != _annotations.count && _mapImage.image) { if (annotations.count != _annotations.count) {
// Redraw // Redraw
[self setNeedsDisplay]; [self setNeedsDisplay];
} }
@@ -63,8 +59,9 @@
- (void)setUpSnapshotter - (void)setUpSnapshotter
{ {
if (!_snapshotter) { if (!_snapshotter) {
_options.size = _nodeSize; ASDisplayNodeAssert(!CGSizeEqualToSize(CGSizeZero, self.calculatedSize), @"self.calculatedSize can not be zero. Make sure that you are setting a preferredFrameSize or wrapping ASMapNode in a ASRatioLayoutSpec or similar.");
_snapshotter = [[MKMapSnapshotter alloc] initWithOptions:_options]; _options.size = self.calculatedSize;
_snapshotter = [[MKMapSnapshotter alloc] initWithOptions:_options];
} }
} }
@@ -77,60 +74,42 @@
- (void)setLiveMap:(BOOL)liveMap - (void)setLiveMap:(BOOL)liveMap
{ {
ASDN::MutexLocker l(_propertyLock); ASDN::MutexLocker l(_propertyLock);
if (liveMap == _liveMap) return; if (liveMap == _liveMap) {
return;
}
_liveMap = liveMap; _liveMap = liveMap;
liveMap ? [self addLiveMap] : [self removeLiveMap]; liveMap ? [self addLiveMap] : [self removeLiveMap];
} }
- (CGSize)mapSize
- (BOOL)needsMapReloadOnBoundsChange
{ {
ASDN::MutexLocker l(_propertyLock); ASDN::MutexLocker l(_propertyLock);
return _mapSize; return _needsMapReloadOnBoundsChange;
} }
- (void)setMapSize:(CGSize)mapSize - (void)setNeedsMapReloadOnBoundsChange:(BOOL)needsMapReloadOnBoundsChange
{ {
ASDN::MutexLocker l(_propertyLock); ASDN::MutexLocker l(_propertyLock);
if (CGSizeEqualToSize(mapSize,_mapSize)) { _needsMapReloadOnBoundsChange = needsMapReloadOnBoundsChange;
return;
}
_mapSize = mapSize;
_nodeSize = _mapSize;
_automaticallyReloadsMapImageOnOrientationChange = NO;
[self setNeedsLayout];
}
- (BOOL)automaticallyReloadsMapImageOnOrientationChange
{
ASDN::MutexLocker l(_propertyLock);
return _automaticallyReloadsMapImageOnOrientationChange;
}
- (void)setAutomaticallyReloadsMapImageOnOrientationChange:(BOOL)automaticallyReloadsMapImageOnOrientationChange
{
ASDN::MutexLocker l(_propertyLock);
if (_automaticallyReloadsMapImageOnOrientationChange == automaticallyReloadsMapImageOnOrientationChange) {
return;
}
_automaticallyReloadsMapImageOnOrientationChange = automaticallyReloadsMapImageOnOrientationChange;
} }
- (void)fetchData - (void)fetchData
{ {
[super fetchData]; [super fetchData];
[self setUpSnapshotter]; if (_liveMap && !_mapNode) {
[self takeSnapshot]; [self addLiveMap];
}
else {
[self setUpSnapshotter];
[self takeSnapshot];
}
} }
- (void)clearFetchedData - (void)clearFetchedData
{ {
[super clearFetchedData]; [super clearFetchedData];
if (_mapView) { [self removeLiveMap];
[_mapView removeFromSupernode];
_mapView = nil;
}
_mapImage.image = nil;
} }
- (void)takeSnapshot - (void)takeSnapshot
@@ -141,29 +120,31 @@
UIImage *image = snapshot.image; UIImage *image = snapshot.image;
CGRect finalImageRect = CGRectMake(0, 0, image.size.width, image.size.height); CGRect finalImageRect = CGRectMake(0, 0, image.size.width, image.size.height);
// 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;
UIGraphicsBeginImageContextWithOptions(image.size, YES, image.scale); UIGraphicsBeginImageContextWithOptions(image.size, YES, image.scale);
[image drawAtPoint:CGPointMake(0, 0)]; [image drawAtPoint:CGPointMake(0, 0)];
for (id<MKAnnotation>annotation in _annotations) if (_annotations.count > 0 ) {
{ // Get a standard annotation view pin. Future implementations should use a custom annotation image property.
CGPoint point = [snapshot pointForCoordinate:annotation.coordinate]; MKAnnotationView *pin = [[MKPinAnnotationView alloc] initWithAnnotation:nil reuseIdentifier:@""];
if (CGRectContainsPoint(finalImageRect, point)) UIImage *pinImage = pin.image;
for (id<MKAnnotation>annotation in _annotations)
{ {
CGPoint pinCenterOffset = pin.centerOffset; CGPoint point = [snapshot pointForCoordinate:annotation.coordinate];
point.x -= pin.bounds.size.width / 2.0; if (CGRectContainsPoint(finalImageRect, point))
point.y -= pin.bounds.size.height / 2.0; {
point.x += pinCenterOffset.x; CGPoint pinCenterOffset = pin.centerOffset;
point.y += pinCenterOffset.y; point.x -= pin.bounds.size.width / 2.0;
[pinImage drawAtPoint:point]; point.y -= pin.bounds.size.height / 2.0;
point.x += pinCenterOffset.x;
point.y += pinCenterOffset.y;
[pinImage drawAtPoint:point];
}
} }
} }
UIImage *finalImage = UIGraphicsGetImageFromCurrentImageContext(); UIImage *finalImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext(); UIGraphicsEndImageContext();
_mapImage.image = finalImage; self.image = finalImage;
} }
}]; }];
} }
@@ -172,7 +153,7 @@
- (void)resetSnapshotter - (void)resetSnapshotter
{ {
if (!_snapshotter.isLoading) { if (!_snapshotter.isLoading) {
_options.size = _nodeSize; _options.size = self.calculatedSize;
_snapshotter = [[MKMapSnapshotter alloc] initWithOptions:_options]; _snapshotter = [[MKMapSnapshotter alloc] initWithOptions:_options];
} }
} }
@@ -180,52 +161,45 @@
#pragma mark - Action #pragma mark - Action
- (void)addLiveMap - (void)addLiveMap
{ {
if (self.isNodeLoaded && !_mapView) { if (self.isNodeLoaded && !_mapNode) {
_mapView = [[ASDisplayNode alloc]initWithViewBlock:^UIView *{ _mapNode = [[ASDisplayNode alloc]initWithViewBlock:^UIView *{
MKMapView *mapView = [[MKMapView alloc]initWithFrame:CGRectMake(0.0f, 0.0f, self.calculatedSize.width, self.calculatedSize.height)]; _mapView = [[MKMapView alloc]initWithFrame:CGRectMake(0.0f, 0.0f, self.calculatedSize.width, self.calculatedSize.height)];
mapView.delegate = _mapDelegate; _mapView.delegate = _mapDelegate;
[mapView setRegion:_options.region]; [_mapView setRegion:_options.region];
[mapView addAnnotations:_annotations]; [_mapView addAnnotations:_annotations];
return mapView; return _mapView;
}]; }];
[self addSubnode:_mapView]; [self addSubnode:_mapNode];
_mapImage.hidden = YES;
if (CLLocationCoordinate2DIsValid(_centerCoordinateOfMap)) {
[_mapView setCenterCoordinate:_centerCoordinateOfMap];
}
} }
} }
- (void)removeLiveMap - (void)removeLiveMap
{ {
if (_mapView) { if (_mapNode) {
[_mapView removeFromSupernode]; _centerCoordinateOfMap = _mapView.centerCoordinate;
[_mapNode removeFromSupernode];
_mapView = nil; _mapView = nil;
_mapImage.hidden = NO; _mapNode = nil;
} }
self.image = nil;
} }
#pragma mark - Layout #pragma mark - Layout
- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize
{
_nodeSize = CGSizeEqualToSize(CGSizeZero, _mapSize) ? CGSizeMake(constrainedSize.width, _options.size.height) : _mapSize;
if (_mapImage) {
[_mapImage calculateSizeThatFits:_nodeSize];
}
return _nodeSize;
}
// Layout isn't usually needed in the box model, but since we are making use of MKMapView which is hidden in an ASDisplayNode this is preferred. // Layout isn't usually needed in the box model, but since we are making use of MKMapView which is hidden in an ASDisplayNode this is preferred.
- (void)layout - (void)layout
{ {
[super layout]; [super layout];
if (_mapView) { if (_mapView) {
MKMapView *mapView = (MKMapView *)_mapView.view; _mapView.frame = CGRectMake(0.0f, 0.0f, self.calculatedSize.width, self.calculatedSize.height);
mapView.frame = CGRectMake(0.0f, 0.0f, self.calculatedSize.width, self.calculatedSize.height);
} }
else { else {
_mapImage.frame = CGRectMake(0.0f, 0.0f, self.calculatedSize.width, self.calculatedSize.height); // If our bounds.size is different from our current snapshot size, then let's request a new image from MKMapSnapshotter.
if (!CGSizeEqualToSize(_maxSize, self.bounds.size)) { if (!CGSizeEqualToSize(_options.size, self.bounds.size)) {
_mapImage.preferredFrameSize = self.bounds.size; if (_needsMapReloadOnBoundsChange && self.image) {
_maxSize = self.bounds.size;
if (_automaticallyReloadsMapImageOnOrientationChange && _mapImage.image) {
[self resetSnapshotter]; [self resetSnapshotter];
[self takeSnapshot]; [self takeSnapshot];
} }

View File

@@ -128,6 +128,7 @@
05E2127E19D4DB510098F589 /* Frameworks */, 05E2127E19D4DB510098F589 /* Frameworks */,
05E2127F19D4DB510098F589 /* Resources */, 05E2127F19D4DB510098F589 /* Resources */,
F012A6F39E0149F18F564F50 /* Copy Pods Resources */, F012A6F39E0149F18F564F50 /* Copy Pods Resources */,
860D1494A00C2E990C93A4D9 /* Embed Pods Frameworks */,
); );
buildRules = ( buildRules = (
); );
@@ -184,6 +185,21 @@
/* End PBXResourcesBuildPhase section */ /* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */
860D1494A00C2E990C93A4D9 /* Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Embed Pods Frameworks";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */ = { E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;

View File

@@ -29,6 +29,7 @@ static const CGFloat kInnerPadding = 10.0f;
ASNetworkImageNode *_imageNode; ASNetworkImageNode *_imageNode;
ASTextNode *_textNode; ASTextNode *_textNode;
ASDisplayNode *_divider; ASDisplayNode *_divider;
ASMapNode *_map;
BOOL _isImageEnlarged; BOOL _isImageEnlarged;
BOOL _swappedTextAndImage; BOOL _swappedTextAndImage;
} }
@@ -88,6 +89,22 @@ static const CGFloat kInnerPadding = 10.0f;
// _imageNode.contentMode = UIViewContentModeCenter; // _imageNode.contentMode = UIViewContentModeCenter;
[_imageNode addTarget:self action:@selector(toggleNodesSwap) forControlEvents:ASControlNodeEventTouchUpInside]; [_imageNode addTarget:self action:@selector(toggleNodesSwap) forControlEvents:ASControlNodeEventTouchUpInside];
[self addSubnode:_imageNode]; [self addSubnode:_imageNode];
MKPointAnnotation *point1 = [[MKPointAnnotation alloc]init];
point1.coordinate = CLLocationCoordinate2DMake(55.864237, -4.251806);
point1.title = @"Best fish & chip shop";
point1.subtitle = @"Everrrrrr";
MKPointAnnotation *point2 = [[MKPointAnnotation alloc]init];
point2.coordinate = CLLocationCoordinate2DMake(55.861, -4.251806);
point2.title = @"The 2nd Best fish & chip shop";
point2.subtitle = @"Everrrrrr";
_map = [[ASMapNode alloc]initWithCoordinate:CLLocationCoordinate2DMake(55.864237, -4.251806)];
[_map addTarget:self action:@selector(makeMapInteractive) forControlEvents:ASControlNodeEventTouchUpInside];
[_map setAnnotations:@[point1,point2]];
[self addSubnode:_map];
// lorem ipsum text, plus some nice styling // lorem ipsum text, plus some nice styling
_textNode = [[ASTextNode alloc] init]; _textNode = [[ASTextNode alloc] init];
@@ -131,16 +148,25 @@ static const CGFloat kInnerPadding = 10.0f;
NSParagraphStyleAttributeName: style }; NSParagraphStyleAttributeName: style };
} }
- (void)makeMapInteractive
{
[_map setLiveMap:YES];
}
#if UseAutomaticLayout #if UseAutomaticLayout
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize - (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{ {
_imageNode.preferredFrameSize = _isImageEnlarged ? CGSizeMake(2.0 * kImageSize, 2.0 * kImageSize) : CGSizeMake(kImageSize, kImageSize); _imageNode.preferredFrameSize = _isImageEnlarged ? CGSizeMake(2.0 * kImageSize, 2.0 * kImageSize) : CGSizeMake(kImageSize, kImageSize);
_textNode.flexShrink = YES; _textNode.flexShrink = YES;
ASRatioLayoutSpec *ratioSpec = [[ASRatioLayoutSpec alloc]init];
ratioSpec.ratio = 0.5;
ratioSpec.child = _map;
ASStackLayoutSpec *stackSpec = [[ASStackLayoutSpec alloc] init]; ASStackLayoutSpec *stackSpec = [[ASStackLayoutSpec alloc] init];
stackSpec.direction = ASStackLayoutDirectionHorizontal; stackSpec.direction = ASStackLayoutDirectionVertical;
stackSpec.spacing = kInnerPadding; stackSpec.spacing = kInnerPadding;
[stackSpec setChildren:!_swappedTextAndImage ? @[_imageNode, _textNode] : @[_textNode, _imageNode]]; [stackSpec setChildren:!_swappedTextAndImage ? @[ratioSpec,_imageNode, _textNode] : @[ratioSpec,_textNode, _imageNode]];
ASInsetLayoutSpec *insetSpec = [[ASInsetLayoutSpec alloc] init]; ASInsetLayoutSpec *insetSpec = [[ASInsetLayoutSpec alloc] init];
insetSpec.insets = UIEdgeInsetsMake(kOuterPadding, kOuterPadding, kOuterPadding, kOuterPadding); insetSpec.insets = UIEdgeInsetsMake(kOuterPadding, kOuterPadding, kOuterPadding, kOuterPadding);