From fe06f211caac2ed1acecffc34b691d996b35ea98 Mon Sep 17 00:00:00 2001 From: Aaron Schubert Date: Thu, 12 Nov 2015 12:13:42 +0000 Subject: [PATCH 01/35] Added an ASMapNode to AsyncDisplayKit --- AsyncDisplayKit.xcodeproj/project.pbxproj | 8 + AsyncDisplayKit/ASMapNode.h | 39 ++++ AsyncDisplayKit/ASMapNode.mm | 235 ++++++++++++++++++++++ 3 files changed, 282 insertions(+) create mode 100644 AsyncDisplayKit/ASMapNode.h create mode 100644 AsyncDisplayKit/ASMapNode.mm diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index cfb767f482..d6257c18a2 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -211,6 +211,8 @@ 509E68651B3AEDC5009B9150 /* CGRect+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E1F1B376416007741D0 /* CGRect+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; 509E68661B3AEDD7009B9150 /* CGRect+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E201B376416007741D0 /* CGRect+ASConvenience.m */; }; 6BDC61F61979037800E50D21 /* AsyncDisplayKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 92DD2FE31BF4B97E0074C9DD /* ASMapNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */; }; + 92DD2FE41BF4B97E0074C9DD /* ASMapNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */; }; 9B92C8851BC2EB6E00EE46B2 /* ASCollectionDataController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */; }; 9B92C8861BC2EB7600EE46B2 /* ASCollectionViewFlowLayoutInspector.m in Sources */ = {isa = PBXBuildFile; fileRef = 251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */; }; 9C49C36F1B853957000B0DD5 /* ASStackLayoutable.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C49C36E1B853957000B0DD5 /* ASStackLayoutable.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -599,6 +601,8 @@ 4640521C1A3F83C40061C0BA /* ASFlowLayoutController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASFlowLayoutController.mm; sourceTree = ""; }; 4640521D1A3F83C40061C0BA /* ASLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutController.h; sourceTree = ""; }; 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AsyncDisplayKit.h; sourceTree = ""; }; + 92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASMapNode.h; sourceTree = ""; }; + 92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASMapNode.mm; sourceTree = ""; }; 9C49C36E1B853957000B0DD5 /* ASStackLayoutable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASStackLayoutable.h; path = AsyncDisplayKit/Layout/ASStackLayoutable.h; sourceTree = ""; }; 9C5586671BD549CB00B50E3A /* ASAsciiArtBoxCreator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASAsciiArtBoxCreator.h; path = AsyncDisplayKit/Layout/ASAsciiArtBoxCreator.h; sourceTree = ""; }; 9C5586681BD549CB00B50E3A /* ASAsciiArtBoxCreator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASAsciiArtBoxCreator.m; path = AsyncDisplayKit/Layout/ASAsciiArtBoxCreator.m; sourceTree = ""; }; @@ -787,6 +791,8 @@ 058D09B1195D04C000B7D73C /* AsyncDisplayKit */ = { isa = PBXGroup; children = ( + 92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */, + 92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */, 055F1A3A19ABD43F004DAFF1 /* ASCellNode.h */, AC6456071B0A335000CF11B8 /* ASCellNode.m */, 18C2ED7C1B9B7DE800F627B3 /* ASCollectionNode.h */, @@ -1194,6 +1200,7 @@ ACF6ED4F1B17847A00DA7C62 /* ASStackPositionedLayout.h in Headers */, ACF6ED511B17847A00DA7C62 /* ASStackUnpositionedLayout.h in Headers */, 9C6BB3B21B8CC9C200F13F52 /* ASStaticLayoutable.h in Headers */, + 92DD2FE31BF4B97E0074C9DD /* ASMapNode.h in Headers */, ACF6ED311B17843500DA7C62 /* ASStaticLayoutSpec.h in Headers */, 055F1A3419ABD3E3004DAFF1 /* ASTableView.h in Headers */, 251B8EF71BBB3D690087C538 /* ASCollectionDataController.h in Headers */, @@ -1547,6 +1554,7 @@ AC6456091B0A335000CF11B8 /* ASCellNode.m in Sources */, ACF6ED1D1B17843500DA7C62 /* ASCenterLayoutSpec.mm in Sources */, 18C2ED801B9B7DE800F627B3 /* ASCollectionNode.m in Sources */, + 92DD2FE41BF4B97E0074C9DD /* ASMapNode.mm in Sources */, AC3C4A521A1139C100143C57 /* ASCollectionView.mm in Sources */, 205F0E1E1B373A2C007741D0 /* ASCollectionViewLayoutController.mm in Sources */, 058D0A13195D050800B7D73C /* ASControlNode.m in Sources */, diff --git a/AsyncDisplayKit/ASMapNode.h b/AsyncDisplayKit/ASMapNode.h new file mode 100644 index 0000000000..566e6dc0f3 --- /dev/null +++ b/AsyncDisplayKit/ASMapNode.h @@ -0,0 +1,39 @@ +/* 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. + */ +#import +#import +@interface ASMapNode : ASControlNode +- (instancetype)initWithCoordinate:(CLLocationCoordinate2D)coordinate NS_DESIGNATED_INITIALIZER; + +@property (nonatomic, readonly) ASImageNode *mapImage; +@property (nonatomic, readonly) ASDisplayNode *liveMap; +/** + Whether the map snapshot should turn into a MKMapView when tapped on. Defaults to YES. + */ +@property (nonatomic, assign) BOOL hasLiveMap; +/** + @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. + @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; +/** + Set the delegate of the MKMapView. + */ +@property (nonatomic, weak) id mapDelegate; +/** + * @discussion This method adds annotations to the static map view and also to the live map view. + * @param annotations An array of objects that conform to the MKAnnotation protocol + */ +- (void)addAnnotations:(NSArray *)annotations; +@end diff --git a/AsyncDisplayKit/ASMapNode.mm b/AsyncDisplayKit/ASMapNode.mm new file mode 100644 index 0000000000..869cf0ccfa --- /dev/null +++ b/AsyncDisplayKit/ASMapNode.mm @@ -0,0 +1,235 @@ +/* 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. + */ + +#import "ASMapNode.h" +#import +#import +#import + +@interface ASMapNode() +{ + ASDN::RecursiveMutex _propertyLock; + CGSize _nodeSize; + MKMapSnapshotter *_snapshotter; + MKMapSnapshotOptions *_options; + CGSize _maxSize; + NSArray *_annotations; +} +@end + +@implementation ASMapNode + +@synthesize hasLiveMap = _hasLiveMap; +@synthesize mapSize = _mapSize; +@synthesize automaticallyReloadsMapImageOnOrientationChange = _automaticallyReloadsMapImageOnOrientationChange; +@synthesize mapDelegate = _mapDelegate; + +- (instancetype)initWithCoordinate:(CLLocationCoordinate2D)coordinate +{ + if (!(self = [super init])) { + return nil; + } + self.backgroundColor = ASDisplayNodeDefaultPlaceholderColor(); + _hasLiveMap = YES; + _automaticallyReloadsMapImageOnOrientationChange = YES; + _options = [[MKMapSnapshotOptions alloc] init]; + _options.region = MKCoordinateRegionMakeWithDistance(coordinate, 1000, 1000);; + + _mapImage = [[ASImageNode alloc]init]; + _mapImage.clipsToBounds = YES; + [self addSubnode:_mapImage]; + [self updateGesture]; + _maxSize = self.bounds.size; + return self; +} + +- (void)addAnnotations:(NSArray *)annotations +{ + ASDN::MutexLocker l(_propertyLock); + if (annotations.count == 0) { + return; + } + _annotations = [annotations copy]; + if (annotations.count != _annotations.count && _mapImage.image) { + // Redraw + [self setNeedsDisplay]; + } +} + +- (void)setUpSnapshotter +{ + if (!_snapshotter) { + _options.size = _nodeSize; + _snapshotter = [[MKMapSnapshotter alloc] initWithOptions:_options]; + } +} + +- (BOOL)hasLiveMap +{ + ASDN::MutexLocker l(_propertyLock); + return _hasLiveMap; +} + +- (void)setHasLiveMap:(BOOL)hasLiveMap +{ + ASDN::MutexLocker l(_propertyLock); + if (hasLiveMap == _hasLiveMap) + return; + + _hasLiveMap = hasLiveMap; + [self updateGesture]; +} + +- (CGSize)mapSize +{ + ASDN::MutexLocker l(_propertyLock); + return _mapSize; +} + +- (void)setMapSize:(CGSize)mapSize +{ + ASDN::MutexLocker l(_propertyLock); + if (CGSizeEqualToSize(mapSize,_mapSize)) { + 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)updateGesture +{ + _hasLiveMap ? [self addTarget:self action:@selector(showLiveMap) forControlEvents:ASControlNodeEventTouchUpInside] : [self removeTarget:self action:@selector(showLiveMap) forControlEvents:ASControlNodeEventTouchUpInside]; +} + +- (void)fetchData +{ + [super fetchData]; + [self setUpSnapshotter]; + [self takeSnapshot]; +} + +- (void)clearFetchedData +{ + [super clearFetchedData]; + if (_liveMap) { + [_liveMap removeFromSupernode]; + _liveMap = nil; + } + _mapImage.image = nil; +} + +- (void)takeSnapshot +{ + if (!_snapshotter.isLoading) { + [_snapshotter startWithCompletionHandler:^(MKMapSnapshot *snapshot, NSError *error) { + if (!error) { + UIImage *image = snapshot.image; + 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); + [image drawAtPoint:CGPointMake(0, 0)]; + + for (idannotation in _annotations) + { + CGPoint point = [snapshot pointForCoordinate:annotation.coordinate]; + if (CGRectContainsPoint(finalImageRect, point)) + { + CGPoint pinCenterOffset = pin.centerOffset; + point.x -= pin.bounds.size.width / 2.0; + point.y -= pin.bounds.size.height / 2.0; + point.x += pinCenterOffset.x; + point.y += pinCenterOffset.y; + [pinImage drawAtPoint:point]; + } + } + UIImage *finalImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + _mapImage.image = finalImage; + } + }]; + } +} + +- (void)resetSnapshotter +{ + if (!_snapshotter.isLoading) { + _options.size = _nodeSize; + _snapshotter = [[MKMapSnapshotter alloc] initWithOptions:_options]; + } +} + +#pragma mark - Action +- (void)showLiveMap +{ + if (self.isNodeLoaded && !_liveMap) { + _liveMap = [[ASDisplayNode alloc]initWithViewBlock:^UIView *{ + MKMapView *mapView = [[MKMapView alloc]initWithFrame:CGRectMake(0.0f, 0.0f, self.calculatedSize.width, self.calculatedSize.height)]; + mapView.delegate = _mapDelegate; + [mapView setRegion:_options.region]; + [mapView addAnnotations:_annotations]; + return mapView; + }]; + [self addSubnode:_liveMap]; + _mapImage.image = nil; + } +} + +#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. +- (void)layout +{ + [super layout]; + if (_liveMap) { + MKMapView *mapView = (MKMapView *)_liveMap.view; + mapView.frame = CGRectMake(0.0f, 0.0f, self.calculatedSize.width, self.calculatedSize.height); + } + else { + _mapImage.frame = CGRectMake(0.0f, 0.0f, self.calculatedSize.width, self.calculatedSize.height); + if (!CGSizeEqualToSize(_maxSize, self.bounds.size)) { + _mapImage.preferredFrameSize = self.bounds.size; + _maxSize = self.bounds.size; + if (_automaticallyReloadsMapImageOnOrientationChange && _mapImage.image) { + [self resetSnapshotter]; + [self takeSnapshot]; + } + } + } +} + +@end From 46bf49cae7ad98203eecdcb699d94e2143d56283 Mon Sep 17 00:00:00 2001 From: Aaron Schubert Date: Thu, 12 Nov 2015 12:18:09 +0000 Subject: [PATCH 02/35] Fix indentation --- AsyncDisplayKit/ASMapNode.h | 1 + AsyncDisplayKit/ASMapNode.mm | 266 +++++++++++++++++------------------ 2 files changed, 134 insertions(+), 133 deletions(-) diff --git a/AsyncDisplayKit/ASMapNode.h b/AsyncDisplayKit/ASMapNode.h index 566e6dc0f3..4b5ca85873 100644 --- a/AsyncDisplayKit/ASMapNode.h +++ b/AsyncDisplayKit/ASMapNode.h @@ -5,6 +5,7 @@ * 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. */ + #import #import @interface ASMapNode : ASControlNode diff --git a/AsyncDisplayKit/ASMapNode.mm b/AsyncDisplayKit/ASMapNode.mm index 869cf0ccfa..fdf1067f49 100644 --- a/AsyncDisplayKit/ASMapNode.mm +++ b/AsyncDisplayKit/ASMapNode.mm @@ -13,12 +13,12 @@ @interface ASMapNode() { - ASDN::RecursiveMutex _propertyLock; - CGSize _nodeSize; - MKMapSnapshotter *_snapshotter; - MKMapSnapshotOptions *_options; - CGSize _maxSize; - NSArray *_annotations; + ASDN::RecursiveMutex _propertyLock; + CGSize _nodeSize; + MKMapSnapshotter *_snapshotter; + MKMapSnapshotOptions *_options; + CGSize _maxSize; + NSArray *_annotations; } @end @@ -34,202 +34,202 @@ if (!(self = [super init])) { return nil; } - self.backgroundColor = ASDisplayNodeDefaultPlaceholderColor(); - _hasLiveMap = YES; - _automaticallyReloadsMapImageOnOrientationChange = YES; - _options = [[MKMapSnapshotOptions alloc] init]; - _options.region = MKCoordinateRegionMakeWithDistance(coordinate, 1000, 1000);; - - _mapImage = [[ASImageNode alloc]init]; - _mapImage.clipsToBounds = YES; - [self addSubnode:_mapImage]; - [self updateGesture]; - _maxSize = self.bounds.size; - return self; + self.backgroundColor = ASDisplayNodeDefaultPlaceholderColor(); + _hasLiveMap = YES; + _automaticallyReloadsMapImageOnOrientationChange = YES; + _options = [[MKMapSnapshotOptions alloc] init]; + _options.region = MKCoordinateRegionMakeWithDistance(coordinate, 1000, 1000);; + + _mapImage = [[ASImageNode alloc]init]; + _mapImage.clipsToBounds = YES; + [self addSubnode:_mapImage]; + [self updateGesture]; + _maxSize = self.bounds.size; + return self; } - (void)addAnnotations:(NSArray *)annotations { - ASDN::MutexLocker l(_propertyLock); - if (annotations.count == 0) { - return; - } - _annotations = [annotations copy]; - if (annotations.count != _annotations.count && _mapImage.image) { - // Redraw - [self setNeedsDisplay]; - } + ASDN::MutexLocker l(_propertyLock); + if (annotations.count == 0) { + return; + } + _annotations = [annotations copy]; + if (annotations.count != _annotations.count && _mapImage.image) { + // Redraw + [self setNeedsDisplay]; + } } - (void)setUpSnapshotter { - if (!_snapshotter) { - _options.size = _nodeSize; - _snapshotter = [[MKMapSnapshotter alloc] initWithOptions:_options]; - } + if (!_snapshotter) { + _options.size = _nodeSize; + _snapshotter = [[MKMapSnapshotter alloc] initWithOptions:_options]; + } } - (BOOL)hasLiveMap { - ASDN::MutexLocker l(_propertyLock); - return _hasLiveMap; + ASDN::MutexLocker l(_propertyLock); + return _hasLiveMap; } - (void)setHasLiveMap:(BOOL)hasLiveMap { - ASDN::MutexLocker l(_propertyLock); - if (hasLiveMap == _hasLiveMap) - return; - - _hasLiveMap = hasLiveMap; - [self updateGesture]; + ASDN::MutexLocker l(_propertyLock); + if (hasLiveMap == _hasLiveMap) + return; + + _hasLiveMap = hasLiveMap; + [self updateGesture]; } - (CGSize)mapSize { - ASDN::MutexLocker l(_propertyLock); - return _mapSize; + ASDN::MutexLocker l(_propertyLock); + return _mapSize; } - (void)setMapSize:(CGSize)mapSize { - ASDN::MutexLocker l(_propertyLock); - if (CGSizeEqualToSize(mapSize,_mapSize)) { - return; - } - _mapSize = mapSize; - _nodeSize = _mapSize; - _automaticallyReloadsMapImageOnOrientationChange = NO; - [self setNeedsLayout]; + ASDN::MutexLocker l(_propertyLock); + if (CGSizeEqualToSize(mapSize,_mapSize)) { + return; + } + _mapSize = mapSize; + _nodeSize = _mapSize; + _automaticallyReloadsMapImageOnOrientationChange = NO; + [self setNeedsLayout]; } - (BOOL)automaticallyReloadsMapImageOnOrientationChange { - ASDN::MutexLocker l(_propertyLock); - return _automaticallyReloadsMapImageOnOrientationChange; + ASDN::MutexLocker l(_propertyLock); + return _automaticallyReloadsMapImageOnOrientationChange; } - (void)setAutomaticallyReloadsMapImageOnOrientationChange:(BOOL)automaticallyReloadsMapImageOnOrientationChange { - ASDN::MutexLocker l(_propertyLock); - if (_automaticallyReloadsMapImageOnOrientationChange == automaticallyReloadsMapImageOnOrientationChange) { - return; - } - _automaticallyReloadsMapImageOnOrientationChange = automaticallyReloadsMapImageOnOrientationChange; - + ASDN::MutexLocker l(_propertyLock); + if (_automaticallyReloadsMapImageOnOrientationChange == automaticallyReloadsMapImageOnOrientationChange) { + return; + } + _automaticallyReloadsMapImageOnOrientationChange = automaticallyReloadsMapImageOnOrientationChange; + } - (void)updateGesture { - _hasLiveMap ? [self addTarget:self action:@selector(showLiveMap) forControlEvents:ASControlNodeEventTouchUpInside] : [self removeTarget:self action:@selector(showLiveMap) forControlEvents:ASControlNodeEventTouchUpInside]; + _hasLiveMap ? [self addTarget:self action:@selector(showLiveMap) forControlEvents:ASControlNodeEventTouchUpInside] : [self removeTarget:self action:@selector(showLiveMap) forControlEvents:ASControlNodeEventTouchUpInside]; } - (void)fetchData { [super fetchData]; - [self setUpSnapshotter]; - [self takeSnapshot]; + [self setUpSnapshotter]; + [self takeSnapshot]; } - (void)clearFetchedData { - [super clearFetchedData]; - if (_liveMap) { - [_liveMap removeFromSupernode]; - _liveMap = nil; - } - _mapImage.image = nil; + [super clearFetchedData]; + if (_liveMap) { + [_liveMap removeFromSupernode]; + _liveMap = nil; + } + _mapImage.image = nil; } - (void)takeSnapshot { - if (!_snapshotter.isLoading) { - [_snapshotter startWithCompletionHandler:^(MKMapSnapshot *snapshot, NSError *error) { - if (!error) { - UIImage *image = snapshot.image; - 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); - [image drawAtPoint:CGPointMake(0, 0)]; - - for (idannotation in _annotations) - { - CGPoint point = [snapshot pointForCoordinate:annotation.coordinate]; - if (CGRectContainsPoint(finalImageRect, point)) - { - CGPoint pinCenterOffset = pin.centerOffset; - point.x -= pin.bounds.size.width / 2.0; - point.y -= pin.bounds.size.height / 2.0; - point.x += pinCenterOffset.x; - point.y += pinCenterOffset.y; - [pinImage drawAtPoint:point]; - } - } - UIImage *finalImage = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - _mapImage.image = finalImage; - } - }]; - } + if (!_snapshotter.isLoading) { + [_snapshotter startWithCompletionHandler:^(MKMapSnapshot *snapshot, NSError *error) { + if (!error) { + UIImage *image = snapshot.image; + 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); + [image drawAtPoint:CGPointMake(0, 0)]; + + for (idannotation in _annotations) + { + CGPoint point = [snapshot pointForCoordinate:annotation.coordinate]; + if (CGRectContainsPoint(finalImageRect, point)) + { + CGPoint pinCenterOffset = pin.centerOffset; + point.x -= pin.bounds.size.width / 2.0; + point.y -= pin.bounds.size.height / 2.0; + point.x += pinCenterOffset.x; + point.y += pinCenterOffset.y; + [pinImage drawAtPoint:point]; + } + } + UIImage *finalImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + _mapImage.image = finalImage; + } + }]; + } } - (void)resetSnapshotter { - if (!_snapshotter.isLoading) { - _options.size = _nodeSize; - _snapshotter = [[MKMapSnapshotter alloc] initWithOptions:_options]; - } + if (!_snapshotter.isLoading) { + _options.size = _nodeSize; + _snapshotter = [[MKMapSnapshotter alloc] initWithOptions:_options]; + } } #pragma mark - Action - (void)showLiveMap { - if (self.isNodeLoaded && !_liveMap) { - _liveMap = [[ASDisplayNode alloc]initWithViewBlock:^UIView *{ - MKMapView *mapView = [[MKMapView alloc]initWithFrame:CGRectMake(0.0f, 0.0f, self.calculatedSize.width, self.calculatedSize.height)]; - mapView.delegate = _mapDelegate; - [mapView setRegion:_options.region]; - [mapView addAnnotations:_annotations]; - return mapView; - }]; - [self addSubnode:_liveMap]; - _mapImage.image = nil; - } + if (self.isNodeLoaded && !_liveMap) { + _liveMap = [[ASDisplayNode alloc]initWithViewBlock:^UIView *{ + MKMapView *mapView = [[MKMapView alloc]initWithFrame:CGRectMake(0.0f, 0.0f, self.calculatedSize.width, self.calculatedSize.height)]; + mapView.delegate = _mapDelegate; + [mapView setRegion:_options.region]; + [mapView addAnnotations:_annotations]; + return mapView; + }]; + [self addSubnode:_liveMap]; + _mapImage.image = nil; + } } #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; + _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. - (void)layout { - [super layout]; - if (_liveMap) { - MKMapView *mapView = (MKMapView *)_liveMap.view; - mapView.frame = CGRectMake(0.0f, 0.0f, self.calculatedSize.width, self.calculatedSize.height); - } - else { - _mapImage.frame = CGRectMake(0.0f, 0.0f, self.calculatedSize.width, self.calculatedSize.height); - if (!CGSizeEqualToSize(_maxSize, self.bounds.size)) { - _mapImage.preferredFrameSize = self.bounds.size; - _maxSize = self.bounds.size; - if (_automaticallyReloadsMapImageOnOrientationChange && _mapImage.image) { - [self resetSnapshotter]; - [self takeSnapshot]; - } - } + [super layout]; + if (_liveMap) { + MKMapView *mapView = (MKMapView *)_liveMap.view; + mapView.frame = CGRectMake(0.0f, 0.0f, self.calculatedSize.width, self.calculatedSize.height); + } + else { + _mapImage.frame = CGRectMake(0.0f, 0.0f, self.calculatedSize.width, self.calculatedSize.height); + if (!CGSizeEqualToSize(_maxSize, self.bounds.size)) { + _mapImage.preferredFrameSize = self.bounds.size; + _maxSize = self.bounds.size; + if (_automaticallyReloadsMapImageOnOrientationChange && _mapImage.image) { + [self resetSnapshotter]; + [self takeSnapshot]; + } } + } } @end From 973593f9c4c4f9a6c5d813dc64ae8ca5e2890cc4 Mon Sep 17 00:00:00 2001 From: Aaron Schubert Date: Thu, 12 Nov 2015 13:51:23 +0000 Subject: [PATCH 03/35] Try to fix integration tests --- AsyncDisplayKit.xcodeproj/project.pbxproj | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index d6257c18a2..88ca6c6166 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -213,6 +213,9 @@ 6BDC61F61979037800E50D21 /* AsyncDisplayKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 92DD2FE31BF4B97E0074C9DD /* ASMapNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */; }; 92DD2FE41BF4B97E0074C9DD /* ASMapNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */; }; + 92DD2FE61BF4D05E0074C9DD /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92DD2FE51BF4D05E0074C9DD /* MapKit.framework */; }; + 92DD2FE71BF4D0850074C9DD /* ASMapNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */; }; + 92DD2FE81BF4D0A80074C9DD /* ASMapNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9B92C8851BC2EB6E00EE46B2 /* ASCollectionDataController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */; }; 9B92C8861BC2EB7600EE46B2 /* ASCollectionViewFlowLayoutInspector.m in Sources */ = {isa = PBXBuildFile; fileRef = 251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */; }; 9C49C36F1B853957000B0DD5 /* ASStackLayoutable.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C49C36E1B853957000B0DD5 /* ASStackLayoutable.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -603,6 +606,7 @@ 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AsyncDisplayKit.h; sourceTree = ""; }; 92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASMapNode.h; sourceTree = ""; }; 92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASMapNode.mm; sourceTree = ""; }; + 92DD2FE51BF4D05E0074C9DD /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = System/Library/Frameworks/MapKit.framework; sourceTree = SDKROOT; }; 9C49C36E1B853957000B0DD5 /* ASStackLayoutable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASStackLayoutable.h; path = AsyncDisplayKit/Layout/ASStackLayoutable.h; sourceTree = ""; }; 9C5586671BD549CB00B50E3A /* ASAsciiArtBoxCreator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASAsciiArtBoxCreator.h; path = AsyncDisplayKit/Layout/ASAsciiArtBoxCreator.h; sourceTree = ""; }; 9C5586681BD549CB00B50E3A /* ASAsciiArtBoxCreator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASAsciiArtBoxCreator.m; path = AsyncDisplayKit/Layout/ASAsciiArtBoxCreator.m; sourceTree = ""; }; @@ -719,6 +723,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 92DD2FE61BF4D05E0074C9DD /* MapKit.framework in Frameworks */, B350625F1B0111800018CF92 /* Foundation.framework in Frameworks */, B350625E1B0111780018CF92 /* AssetsLibrary.framework in Frameworks */, B350625D1B0111740018CF92 /* Photos.framework in Frameworks */, @@ -778,6 +783,7 @@ 058D09AE195D04C000B7D73C /* Frameworks */ = { isa = PBXGroup; children = ( + 92DD2FE51BF4D05E0074C9DD /* MapKit.framework */, 051943141A1575670030A7D0 /* Photos.framework */, 051943121A1575630030A7D0 /* AssetsLibrary.framework */, 058D09AF195D04C000B7D73C /* Foundation.framework */, @@ -1307,6 +1313,7 @@ CC7FD9E21BB603FF005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */, 34EFC7711B701CFF00AD841F /* ASStackLayoutSpec.h in Headers */, 2767E9411BB19BD600EA9B77 /* ASViewController.h in Headers */, + 92DD2FE81BF4D0A80074C9DD /* ASMapNode.h in Headers */, 044284FE1BAA387800D16268 /* ASStackLayoutSpecUtilities.h in Headers */, 34EFC7751B701D2400AD841F /* ASStackPositionedLayout.h in Headers */, 34EFC7771B701D2D00AD841F /* ASStackUnpositionedLayout.h in Headers */, @@ -1657,6 +1664,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 92DD2FE71BF4D0850074C9DD /* ASMapNode.mm in Sources */, 9B92C8861BC2EB7600EE46B2 /* ASCollectionViewFlowLayoutInspector.m in Sources */, 9B92C8851BC2EB6E00EE46B2 /* ASCollectionDataController.mm in Sources */, B350623D1B010EFD0018CF92 /* _ASAsyncTransaction.m in Sources */, From 476bff2bf17e306ab7fc0a57d24e1163ef4a3b8a Mon Sep 17 00:00:00 2001 From: Aaron Schubert Date: Thu, 12 Nov 2015 14:09:48 +0000 Subject: [PATCH 04/35] Link MapKit in static framework. --- AsyncDisplayKit.xcodeproj/project.pbxproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 88ca6c6166..37bdadd029 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -216,6 +216,8 @@ 92DD2FE61BF4D05E0074C9DD /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92DD2FE51BF4D05E0074C9DD /* MapKit.framework */; }; 92DD2FE71BF4D0850074C9DD /* ASMapNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */; }; 92DD2FE81BF4D0A80074C9DD /* ASMapNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 92DD2FE91BF4D4870074C9DD /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92DD2FE51BF4D05E0074C9DD /* MapKit.framework */; }; + 92DD2FEA1BF4D49B0074C9DD /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92DD2FE51BF4D05E0074C9DD /* MapKit.framework */; }; 9B92C8851BC2EB6E00EE46B2 /* ASCollectionDataController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */; }; 9B92C8861BC2EB7600EE46B2 /* ASCollectionViewFlowLayoutInspector.m in Sources */ = {isa = PBXBuildFile; fileRef = 251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */; }; 9C49C36F1B853957000B0DD5 /* ASStackLayoutable.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C49C36E1B853957000B0DD5 /* ASStackLayoutable.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -699,6 +701,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 92DD2FE91BF4D4870074C9DD /* MapKit.framework in Frameworks */, 051943151A1575670030A7D0 /* Photos.framework in Frameworks */, 051943131A1575630030A7D0 /* AssetsLibrary.framework in Frameworks */, 058D09B0195D04C000B7D73C /* Foundation.framework in Frameworks */, @@ -709,6 +712,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 92DD2FEA1BF4D49B0074C9DD /* MapKit.framework in Frameworks */, 0515EA221A1576A100BA8B9A /* AssetsLibrary.framework in Frameworks */, 0515EA211A15769900BA8B9A /* Photos.framework in Frameworks */, 058D09BE195D04C000B7D73C /* XCTest.framework in Frameworks */, From b35a9bbbb6cc04d83f58557b3de227c0680ced5e Mon Sep 17 00:00:00 2001 From: Aaron Schubert Date: Thu, 12 Nov 2015 14:25:29 +0000 Subject: [PATCH 05/35] Add MapKit to Life Without CocoaPods --- .../Life Without CocoaPods.xcodeproj/project.pbxproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/smoke-tests/Life Without CocoaPods/Life Without CocoaPods.xcodeproj/project.pbxproj b/smoke-tests/Life Without CocoaPods/Life Without CocoaPods.xcodeproj/project.pbxproj index 3a7e63f9d1..aaaa497852 100644 --- a/smoke-tests/Life Without CocoaPods/Life Without CocoaPods.xcodeproj/project.pbxproj +++ b/smoke-tests/Life Without CocoaPods/Life Without CocoaPods.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ 058969281ABCE1750059CE2A /* libAsyncDisplayKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 058969271ABCE1750059CE2A /* libAsyncDisplayKit.a */; }; 0589692A1ABCE17C0059CE2A /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 058969291ABCE17C0059CE2A /* AssetsLibrary.framework */; }; 0589692C1ABCE1820059CE2A /* Photos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0589692B1ABCE1820059CE2A /* Photos.framework */; }; + 92DD2FEC1BF4D8BB0074C9DD /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92DD2FEB1BF4D8BB0074C9DD /* MapKit.framework */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -32,6 +33,7 @@ 058969271ABCE1750059CE2A /* libAsyncDisplayKit.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libAsyncDisplayKit.a; path = "../../build/Debug-iphoneos/libAsyncDisplayKit.a"; sourceTree = ""; }; 058969291ABCE17C0059CE2A /* AssetsLibrary.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AssetsLibrary.framework; path = System/Library/Frameworks/AssetsLibrary.framework; sourceTree = SDKROOT; }; 0589692B1ABCE1820059CE2A /* Photos.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Photos.framework; path = System/Library/Frameworks/Photos.framework; sourceTree = SDKROOT; }; + 92DD2FEB1BF4D8BB0074C9DD /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = System/Library/Frameworks/MapKit.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -39,6 +41,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 92DD2FEC1BF4D8BB0074C9DD /* MapKit.framework in Frameworks */, 0589692C1ABCE1820059CE2A /* Photos.framework in Frameworks */, 0589692A1ABCE17C0059CE2A /* AssetsLibrary.framework in Frameworks */, 058969281ABCE1750059CE2A /* libAsyncDisplayKit.a in Frameworks */, @@ -51,6 +54,7 @@ 058968E61ABCE06E0059CE2A = { isa = PBXGroup; children = ( + 92DD2FEB1BF4D8BB0074C9DD /* MapKit.framework */, 0589692B1ABCE1820059CE2A /* Photos.framework */, 058969291ABCE17C0059CE2A /* AssetsLibrary.framework */, 058969271ABCE1750059CE2A /* libAsyncDisplayKit.a */, From 1b74f8d696d97a01e377e905695b5a17c72c7e48 Mon Sep 17 00:00:00 2001 From: Aaron Schubert Date: Thu, 12 Nov 2015 14:27:45 +0000 Subject: [PATCH 06/35] Update ReadMe to mention new MapKit link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2a249299a1..f7743eced1 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ pod 'AsyncDisplayKit' (ASDK can also be used as a regular static library: Copy the project to your codebase manually, adding `AsyncDisplayKit.xcodeproj` to your workspace. Add -`libAsyncDisplayKit.a`, AssetsLibrary, and Photos to the "Link Binary With +`libAsyncDisplayKit.a`, MapKit, AssetsLibrary, and Photos to the "Link Binary With Libraries" build phase. Include `-lc++ -ObjC` in your project linker flags.) Import the framework header, or create an [Objective-C bridging From 607be19ce72599bc770b7db493232d507b06f710 Mon Sep 17 00:00:00 2001 From: Aaron Schubert Date: Thu, 12 Nov 2015 14:54:29 +0000 Subject: [PATCH 07/35] Update Podspec to require MapKit framework --- AsyncDisplayKit.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncDisplayKit.podspec b/AsyncDisplayKit.podspec index 33237d7be6..8404db6beb 100644 --- a/AsyncDisplayKit.podspec +++ b/AsyncDisplayKit.podspec @@ -21,7 +21,7 @@ Pod::Spec.new do |spec| 'Base/*.{h,m}' ] - spec.frameworks = 'AssetsLibrary' + spec.frameworks = 'AssetsLibrary','MapKit' spec.weak_frameworks = 'Photos' # ASDealloc2MainObject must be compiled with MRR From 156b5866a517e30f05d594b1b689a017dc8374b7 Mon Sep 17 00:00:00 2001 From: Aaron Schubert Date: Thu, 12 Nov 2015 14:57:09 +0000 Subject: [PATCH 08/35] Add ASMapNode to main header file. --- AsyncDisplayKit/AsyncDisplayKit.h | 1 + 1 file changed, 1 insertion(+) diff --git a/AsyncDisplayKit/AsyncDisplayKit.h b/AsyncDisplayKit/AsyncDisplayKit.h index 407d08b037..afbb891de0 100644 --- a/AsyncDisplayKit/AsyncDisplayKit.h +++ b/AsyncDisplayKit/AsyncDisplayKit.h @@ -13,6 +13,7 @@ #import #import #import +#import #import From 49c476bdd139e67fa5b1e0ef383e62cca95b35b6 Mon Sep 17 00:00:00 2001 From: Aaron Schubert Date: Thu, 12 Nov 2015 15:05:45 +0000 Subject: [PATCH 09/35] Move ASMapNode header into public --- AsyncDisplayKit.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 37bdadd029..3b85e90677 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -211,7 +211,7 @@ 509E68651B3AEDC5009B9150 /* CGRect+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E1F1B376416007741D0 /* CGRect+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; 509E68661B3AEDD7009B9150 /* CGRect+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E201B376416007741D0 /* CGRect+ASConvenience.m */; }; 6BDC61F61979037800E50D21 /* AsyncDisplayKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 92DD2FE31BF4B97E0074C9DD /* ASMapNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */; }; + 92DD2FE31BF4B97E0074C9DD /* ASMapNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; 92DD2FE41BF4B97E0074C9DD /* ASMapNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */; }; 92DD2FE61BF4D05E0074C9DD /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92DD2FE51BF4D05E0074C9DD /* MapKit.framework */; }; 92DD2FE71BF4D0850074C9DD /* ASMapNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */; }; @@ -1123,6 +1123,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 92DD2FE31BF4B97E0074C9DD /* ASMapNode.h in Headers */, AC026B691BD57D6F00BBC17E /* ASChangeSetDataController.h in Headers */, 058D0A71195D05F800B7D73C /* _AS-objc-internal.h in Headers */, 058D0A68195D05EC00B7D73C /* _ASAsyncTransaction.h in Headers */, @@ -1210,7 +1211,6 @@ ACF6ED4F1B17847A00DA7C62 /* ASStackPositionedLayout.h in Headers */, ACF6ED511B17847A00DA7C62 /* ASStackUnpositionedLayout.h in Headers */, 9C6BB3B21B8CC9C200F13F52 /* ASStaticLayoutable.h in Headers */, - 92DD2FE31BF4B97E0074C9DD /* ASMapNode.h in Headers */, ACF6ED311B17843500DA7C62 /* ASStaticLayoutSpec.h in Headers */, 055F1A3419ABD3E3004DAFF1 /* ASTableView.h in Headers */, 251B8EF71BBB3D690087C538 /* ASCollectionDataController.h in Headers */, From 372acd85a42a330f49ecd74f4b975e63a7f012da Mon Sep 17 00:00:00 2001 From: Aaron Schubert Date: Sun, 15 Nov 2015 16:58:56 +0000 Subject: [PATCH 10/35] Make MapKit a weak framework --- AsyncDisplayKit.podspec | 4 ++-- AsyncDisplayKit.xcodeproj/project.pbxproj | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/AsyncDisplayKit.podspec b/AsyncDisplayKit.podspec index 8404db6beb..43123de8cc 100644 --- a/AsyncDisplayKit.podspec +++ b/AsyncDisplayKit.podspec @@ -21,8 +21,8 @@ Pod::Spec.new do |spec| 'Base/*.{h,m}' ] - spec.frameworks = 'AssetsLibrary','MapKit' - spec.weak_frameworks = 'Photos' + spec.frameworks = 'AssetsLibrary' + spec.weak_frameworks = 'Photos','MapKit' # ASDealloc2MainObject must be compiled with MRR spec.requires_arc = true diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 3b85e90677..e1935e29dd 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -216,7 +216,7 @@ 92DD2FE61BF4D05E0074C9DD /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92DD2FE51BF4D05E0074C9DD /* MapKit.framework */; }; 92DD2FE71BF4D0850074C9DD /* ASMapNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */; }; 92DD2FE81BF4D0A80074C9DD /* ASMapNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 92DD2FE91BF4D4870074C9DD /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92DD2FE51BF4D05E0074C9DD /* MapKit.framework */; }; + 92DD2FE91BF4D4870074C9DD /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92DD2FE51BF4D05E0074C9DD /* MapKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 92DD2FEA1BF4D49B0074C9DD /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92DD2FE51BF4D05E0074C9DD /* MapKit.framework */; }; 9B92C8851BC2EB6E00EE46B2 /* ASCollectionDataController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */; }; 9B92C8861BC2EB7600EE46B2 /* ASCollectionViewFlowLayoutInspector.m in Sources */ = {isa = PBXBuildFile; fileRef = 251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */; }; From 863156b2dc5cbd6e1de3b22737ae614a6a470c63 Mon Sep 17 00:00:00 2001 From: Aaron Schubert Date: Mon, 16 Nov 2015 13:48:21 +0000 Subject: [PATCH 11/35] Changed hasLiveMap to liveMap/isLiveMap. Removed internal action handler. Renamed ASDisplayNode liveMap to mapView to avoid naming confusion. --- AsyncDisplayKit/ASMapNode.h | 15 ++++++---- AsyncDisplayKit/ASMapNode.mm | 55 ++++++++++++++++++------------------ 2 files changed, 38 insertions(+), 32 deletions(-) diff --git a/AsyncDisplayKit/ASMapNode.h b/AsyncDisplayKit/ASMapNode.h index 4b5ca85873..35d63bf228 100644 --- a/AsyncDisplayKit/ASMapNode.h +++ b/AsyncDisplayKit/ASMapNode.h @@ -10,13 +10,18 @@ #import @interface ASMapNode : ASControlNode - (instancetype)initWithCoordinate:(CLLocationCoordinate2D)coordinate NS_DESIGNATED_INITIALIZER; - -@property (nonatomic, readonly) ASImageNode *mapImage; -@property (nonatomic, readonly) ASDisplayNode *liveMap; /** - Whether the map snapshot should turn into a MKMapView when tapped on. Defaults to YES. + This is the snapshot shot image node, this will be hidden (but not nil) when .liveMap = YES */ -@property (nonatomic, assign) BOOL hasLiveMap; +@property (nonatomic, readonly) ASImageNode *mapImage; +/** + 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. + */ +@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. diff --git a/AsyncDisplayKit/ASMapNode.mm b/AsyncDisplayKit/ASMapNode.mm index fdf1067f49..d60171a9e4 100644 --- a/AsyncDisplayKit/ASMapNode.mm +++ b/AsyncDisplayKit/ASMapNode.mm @@ -24,7 +24,7 @@ @implementation ASMapNode -@synthesize hasLiveMap = _hasLiveMap; +@synthesize liveMap = _liveMap; @synthesize mapSize = _mapSize; @synthesize automaticallyReloadsMapImageOnOrientationChange = _automaticallyReloadsMapImageOnOrientationChange; @synthesize mapDelegate = _mapDelegate; @@ -35,15 +35,14 @@ return nil; } self.backgroundColor = ASDisplayNodeDefaultPlaceholderColor(); - _hasLiveMap = YES; _automaticallyReloadsMapImageOnOrientationChange = YES; + _liveMap = NO; + _options = [[MKMapSnapshotOptions alloc] init]; _options.region = MKCoordinateRegionMakeWithDistance(coordinate, 1000, 1000);; - _mapImage = [[ASImageNode alloc]init]; _mapImage.clipsToBounds = YES; [self addSubnode:_mapImage]; - [self updateGesture]; _maxSize = self.bounds.size; return self; } @@ -69,20 +68,18 @@ } } -- (BOOL)hasLiveMap +- (BOOL)isLiveMap { ASDN::MutexLocker l(_propertyLock); - return _hasLiveMap; + return _liveMap; } -- (void)setHasLiveMap:(BOOL)hasLiveMap +- (void)setLiveMap:(BOOL)liveMap { ASDN::MutexLocker l(_propertyLock); - if (hasLiveMap == _hasLiveMap) - return; - - _hasLiveMap = hasLiveMap; - [self updateGesture]; + if (liveMap == _liveMap) return; + _liveMap = liveMap; + liveMap ? [self addLiveMap] : [self removeLiveMap]; } - (CGSize)mapSize @@ -119,11 +116,6 @@ } -- (void)updateGesture -{ - _hasLiveMap ? [self addTarget:self action:@selector(showLiveMap) forControlEvents:ASControlNodeEventTouchUpInside] : [self removeTarget:self action:@selector(showLiveMap) forControlEvents:ASControlNodeEventTouchUpInside]; -} - - (void)fetchData { [super fetchData]; @@ -134,9 +126,9 @@ - (void)clearFetchedData { [super clearFetchedData]; - if (_liveMap) { - [_liveMap removeFromSupernode]; - _liveMap = nil; + if (_mapView) { + [_mapView removeFromSupernode]; + _mapView = nil; } _mapImage.image = nil; } @@ -186,18 +178,27 @@ } #pragma mark - Action -- (void)showLiveMap +- (void)addLiveMap { - if (self.isNodeLoaded && !_liveMap) { - _liveMap = [[ASDisplayNode alloc]initWithViewBlock:^UIView *{ + if (self.isNodeLoaded && !_mapView) { + _mapView = [[ASDisplayNode alloc]initWithViewBlock:^UIView *{ MKMapView *mapView = [[MKMapView alloc]initWithFrame:CGRectMake(0.0f, 0.0f, self.calculatedSize.width, self.calculatedSize.height)]; mapView.delegate = _mapDelegate; [mapView setRegion:_options.region]; [mapView addAnnotations:_annotations]; return mapView; }]; - [self addSubnode:_liveMap]; - _mapImage.image = nil; + [self addSubnode:_mapView]; + _mapImage.hidden = YES; + } +} + +- (void)removeLiveMap +{ + if (_mapView) { + [_mapView removeFromSupernode]; + _mapView = nil; + _mapImage.hidden = NO; } } @@ -215,8 +216,8 @@ - (void)layout { [super layout]; - if (_liveMap) { - MKMapView *mapView = (MKMapView *)_liveMap.view; + if (_mapView) { + MKMapView *mapView = (MKMapView *)_mapView.view; mapView.frame = CGRectMake(0.0f, 0.0f, self.calculatedSize.width, self.calculatedSize.height); } else { From 42f56defbf09b55109626b1740e65768e8ff1271 Mon Sep 17 00:00:00 2001 From: Alexey Glushkov Date: Sun, 22 Nov 2015 11:56:12 +0300 Subject: [PATCH 12/35] ASTextNode wrong text layout fix --- AsyncDisplayKit/ASTextNode.mm | 38 ++++++++++++++++++++++------------- Podfile.lock | 2 +- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index 0f78ac960d..3b3c41347b 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -246,25 +246,13 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation - (void)setFrame:(CGRect)frame { [super setFrame:frame]; - if (!CGSizeEqualToSize(frame.size, _constrainedSize)) { - // Our bounds have changed to a size that is not identical to our constraining size, - // so our previous layout information is invalid, and TextKit may draw at the - // incorrect origin. - _constrainedSize = CGSizeMake(-INFINITY, -INFINITY); - [self _invalidateRenderer]; - } + [self _invalidateRendererIfNeeded:frame.size]; } - (void)setBounds:(CGRect)bounds { [super setBounds:bounds]; - if (!CGSizeEqualToSize(bounds.size, _constrainedSize)) { - // Our bounds have changed to a size that is not identical to our constraining size, - // so our previous layout information is invalid, and TextKit may draw at the - // incorrect origin. - _constrainedSize = CGSizeMake(-INFINITY, -INFINITY); - [self _invalidateRenderer]; - } + [self _invalidateRendererIfNeeded:bounds.size]; } #pragma mark - Renderer Management @@ -299,6 +287,26 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation _renderer = nil; } +- (BOOL)_needInvalidateRenderer:(CGSize)newSize +{ + return !CGSizeEqualToSize(newSize, _constrainedSize); +} + +- (void)_invalidateRendererIfNeeded { + [self _invalidateRendererIfNeeded:self.view.bounds.size]; +} + +- (void)_invalidateRendererIfNeeded:(CGSize)newSize +{ + if ([self _needInvalidateRenderer:newSize]) { + // Our bounds of frame have changed to a size that is not identical to our constraining size, + // so our previous layout information is invalid, and TextKit may draw at the + // incorrect origin. + _constrainedSize = CGSizeMake(-INFINITY, -INFINITY); + [self _invalidateRenderer]; + } +} + #pragma mark - Shadow Drawer Management - (ASTextNodeShadower *)_shadower { @@ -410,6 +418,8 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation - (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer { + [self _invalidateRendererIfNeeded]; + // Offset the text origin by any shadow padding UIEdgeInsets shadowPadding = [self shadowPadding]; CGPoint textOrigin = CGPointMake(self.bounds.origin.x - shadowPadding.left, self.bounds.origin.y - shadowPadding.top); diff --git a/Podfile.lock b/Podfile.lock index cf8e89d790..cb01b76e45 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -10,4 +10,4 @@ SPEC CHECKSUMS: FBSnapshotTestCase: 3dc3899168747a0319c5278f5b3445c13a6532dd OCMock: a6a7dc0e3997fb9f35d99f72528698ebf60d64f2 -COCOAPODS: 0.38.2 +COCOAPODS: 0.39.0 From 38ded30da9fb9298bf411078f8c1ec08b0640bdb Mon Sep 17 00:00:00 2001 From: Alexey Glushkov Date: Tue, 24 Nov 2015 10:01:02 +0300 Subject: [PATCH 13/35] add layer backed text node support for the fix --- AsyncDisplayKit/ASTextNode.mm | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index 3b3c41347b..cedc1ac112 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -287,13 +287,19 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation _renderer = nil; } -- (BOOL)_needInvalidateRenderer:(CGSize)newSize -{ - return !CGSizeEqualToSize(newSize, _constrainedSize); +- (void)_invalidateRendererIfNeeded { + [self _invalidateRendererIfNeeded:[self displaySize]]; } -- (void)_invalidateRendererIfNeeded { - [self _invalidateRendererIfNeeded:self.view.bounds.size]; +- (CGSize)displaySize { + CGSize resultSize = CGSizeZero; + if (self.isLayerBacked) { + resultSize = self.layer.bounds.size; + } else { + resultSize = self.view.bounds.size; + } + + return resultSize; } - (void)_invalidateRendererIfNeeded:(CGSize)newSize @@ -307,6 +313,11 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation } } +- (BOOL)_needInvalidateRenderer:(CGSize)newSize +{ + return !CGSizeEqualToSize(newSize, _constrainedSize); +} + #pragma mark - Shadow Drawer Management - (ASTextNodeShadower *)_shadower { From 1d986f607b1806b6daacbe868ec04889020400ad Mon Sep 17 00:00:00 2001 From: Alexey Glushkov Date: Tue, 24 Nov 2015 10:04:42 +0300 Subject: [PATCH 14/35] reverted Podfile.lock --- Podfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Podfile.lock b/Podfile.lock index cb01b76e45..cf8e89d790 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -10,4 +10,4 @@ SPEC CHECKSUMS: FBSnapshotTestCase: 3dc3899168747a0319c5278f5b3445c13a6532dd OCMock: a6a7dc0e3997fb9f35d99f72528698ebf60d64f2 -COCOAPODS: 0.39.0 +COCOAPODS: 0.38.2 From e859f9a870394e9b805586cea634cbf11c169cd8 Mon Sep 17 00:00:00 2001 From: Alexey Glushkov Date: Tue, 24 Nov 2015 10:07:32 +0300 Subject: [PATCH 15/35] fixed brace style --- AsyncDisplayKit/ASTextNode.mm | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index cedc1ac112..be5ab82e8c 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -287,18 +287,20 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation _renderer = nil; } -- (void)_invalidateRendererIfNeeded { +- (void)_invalidateRendererIfNeeded +{ [self _invalidateRendererIfNeeded:[self displaySize]]; } -- (CGSize)displaySize { +- (CGSize)displaySize +{ CGSize resultSize = CGSizeZero; if (self.isLayerBacked) { resultSize = self.layer.bounds.size; } else { resultSize = self.view.bounds.size; } - + return resultSize; } From 01a2fd90d492c9330f72e0ea2987b3c9de02cc68 Mon Sep 17 00:00:00 2001 From: Alexey Glushkov Date: Tue, 24 Nov 2015 11:12:36 +0300 Subject: [PATCH 16/35] removed displaySize method, added condition to skip _invalidateRendererIfNeeded if there are no view and no layer --- AsyncDisplayKit/ASTextNode.mm | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index be5ab82e8c..9c0bd9d79d 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -289,19 +289,7 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation - (void)_invalidateRendererIfNeeded { - [self _invalidateRendererIfNeeded:[self displaySize]]; -} - -- (CGSize)displaySize -{ - CGSize resultSize = CGSizeZero; - if (self.isLayerBacked) { - resultSize = self.layer.bounds.size; - } else { - resultSize = self.view.bounds.size; - } - - return resultSize; + [self _invalidateRendererIfNeeded:self.bounds.size]; } - (void)_invalidateRendererIfNeeded:(CGSize)newSize @@ -317,7 +305,8 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation - (BOOL)_needInvalidateRenderer:(CGSize)newSize { - return !CGSizeEqualToSize(newSize, _constrainedSize); + BOOL hasViewOrLayer = self.view || self.layer; + return hasViewOrLayer && !CGSizeEqualToSize(newSize, _constrainedSize); } #pragma mark - Shadow Drawer Management From c501e46b5de8af832c5f2e69aac988c9efa1cc9f Mon Sep 17 00:00:00 2001 From: Alexey Glushkov Date: Tue, 24 Nov 2015 11:35:20 +0300 Subject: [PATCH 17/35] removed wrong conditions in _needInvalidateRenderer --- AsyncDisplayKit/ASTextNode.mm | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index 9c0bd9d79d..4cd7cf8f2c 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -305,8 +305,7 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation - (BOOL)_needInvalidateRenderer:(CGSize)newSize { - BOOL hasViewOrLayer = self.view || self.layer; - return hasViewOrLayer && !CGSizeEqualToSize(newSize, _constrainedSize); + return !CGSizeEqualToSize(newSize, _constrainedSize); } #pragma mark - Shadow Drawer Management From 79b4c9574933c5a5c46563db2ac55d894bded7e2 Mon Sep 17 00:00:00 2001 From: Aaron Schubert Date: Thu, 3 Dec 2015 11:22:05 +0000 Subject: [PATCH 18/35] Substantially improved ASMapNode and made it a lot clearer and less complex internally. --- AsyncDisplayKit/ASMapNode.h | 32 ++-- AsyncDisplayKit/ASMapNode.mm | 162 ++++++++---------- .../Kittens/Sample.xcodeproj/project.pbxproj | 16 ++ examples/Kittens/Sample/KittenNode.mm | 30 +++- 4 files changed, 127 insertions(+), 113 deletions(-) diff --git a/AsyncDisplayKit/ASMapNode.h b/AsyncDisplayKit/ASMapNode.h index 35d63bf228..e5fcbd4921 100644 --- a/AsyncDisplayKit/ASMapNode.h +++ b/AsyncDisplayKit/ASMapNode.h @@ -8,38 +8,36 @@ #import #import -@interface ASMapNode : ASControlNode + +@interface ASMapNode : ASImageNode + - (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; -/** - 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; +@property (nonatomic, readonly) MKMapView *mapView; + /** 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; -/** - @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. @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 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 */ -- (void)addAnnotations:(NSArray *)annotations; +- (void)setAnnotations:(NSArray *)annotations; + @end diff --git a/AsyncDisplayKit/ASMapNode.mm b/AsyncDisplayKit/ASMapNode.mm index d60171a9e4..311000362e 100644 --- a/AsyncDisplayKit/ASMapNode.mm +++ b/AsyncDisplayKit/ASMapNode.mm @@ -14,19 +14,18 @@ @interface ASMapNode() { ASDN::RecursiveMutex _propertyLock; - CGSize _nodeSize; MKMapSnapshotter *_snapshotter; MKMapSnapshotOptions *_options; - CGSize _maxSize; NSArray *_annotations; + ASDisplayNode *_mapNode; + CLLocationCoordinate2D _centerCoordinateOfMap; } @end @implementation ASMapNode @synthesize liveMap = _liveMap; -@synthesize mapSize = _mapSize; -@synthesize automaticallyReloadsMapImageOnOrientationChange = _automaticallyReloadsMapImageOnOrientationChange; +@synthesize needsMapReloadOnBoundsChange = _needsMapReloadOnBoundsChange; @synthesize mapDelegate = _mapDelegate; - (instancetype)initWithCoordinate:(CLLocationCoordinate2D)coordinate @@ -35,26 +34,23 @@ return nil; } self.backgroundColor = ASDisplayNodeDefaultPlaceholderColor(); - _automaticallyReloadsMapImageOnOrientationChange = YES; + self.clipsToBounds = YES; + + _needsMapReloadOnBoundsChange = YES; _liveMap = NO; + _centerCoordinateOfMap = kCLLocationCoordinate2DInvalid; _options = [[MKMapSnapshotOptions alloc] init]; _options.region = MKCoordinateRegionMakeWithDistance(coordinate, 1000, 1000);; - _mapImage = [[ASImageNode alloc]init]; - _mapImage.clipsToBounds = YES; - [self addSubnode:_mapImage]; - _maxSize = self.bounds.size; + return self; } -- (void)addAnnotations:(NSArray *)annotations +- (void)setAnnotations:(NSArray *)annotations { ASDN::MutexLocker l(_propertyLock); - if (annotations.count == 0) { - return; - } _annotations = [annotations copy]; - if (annotations.count != _annotations.count && _mapImage.image) { + if (annotations.count != _annotations.count) { // Redraw [self setNeedsDisplay]; } @@ -63,8 +59,9 @@ - (void)setUpSnapshotter { if (!_snapshotter) { - _options.size = _nodeSize; - _snapshotter = [[MKMapSnapshotter alloc] initWithOptions:_options]; + 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."); + _options.size = self.calculatedSize; + _snapshotter = [[MKMapSnapshotter alloc] initWithOptions:_options]; } } @@ -77,60 +74,42 @@ - (void)setLiveMap:(BOOL)liveMap { ASDN::MutexLocker l(_propertyLock); - if (liveMap == _liveMap) return; + if (liveMap == _liveMap) { + return; + } _liveMap = liveMap; liveMap ? [self addLiveMap] : [self removeLiveMap]; } -- (CGSize)mapSize + +- (BOOL)needsMapReloadOnBoundsChange { ASDN::MutexLocker l(_propertyLock); - return _mapSize; + return _needsMapReloadOnBoundsChange; } -- (void)setMapSize:(CGSize)mapSize +- (void)setNeedsMapReloadOnBoundsChange:(BOOL)needsMapReloadOnBoundsChange { ASDN::MutexLocker l(_propertyLock); - if (CGSizeEqualToSize(mapSize,_mapSize)) { - 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; - + _needsMapReloadOnBoundsChange = needsMapReloadOnBoundsChange; } - (void)fetchData { [super fetchData]; - [self setUpSnapshotter]; - [self takeSnapshot]; + if (_liveMap && !_mapNode) { + [self addLiveMap]; + } + else { + [self setUpSnapshotter]; + [self takeSnapshot]; + } } - (void)clearFetchedData { [super clearFetchedData]; - if (_mapView) { - [_mapView removeFromSupernode]; - _mapView = nil; - } - _mapImage.image = nil; + [self removeLiveMap]; } - (void)takeSnapshot @@ -141,29 +120,31 @@ UIImage *image = snapshot.image; 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); [image drawAtPoint:CGPointMake(0, 0)]; - for (idannotation in _annotations) - { - CGPoint point = [snapshot pointForCoordinate:annotation.coordinate]; - if (CGRectContainsPoint(finalImageRect, point)) + if (_annotations.count > 0 ) { + // 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; + for (idannotation in _annotations) { - CGPoint pinCenterOffset = pin.centerOffset; - point.x -= pin.bounds.size.width / 2.0; - point.y -= pin.bounds.size.height / 2.0; - point.x += pinCenterOffset.x; - point.y += pinCenterOffset.y; - [pinImage drawAtPoint:point]; + CGPoint point = [snapshot pointForCoordinate:annotation.coordinate]; + if (CGRectContainsPoint(finalImageRect, point)) + { + CGPoint pinCenterOffset = pin.centerOffset; + point.x -= pin.bounds.size.width / 2.0; + point.y -= pin.bounds.size.height / 2.0; + point.x += pinCenterOffset.x; + point.y += pinCenterOffset.y; + [pinImage drawAtPoint:point]; + } } } + UIImage *finalImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); - _mapImage.image = finalImage; + self.image = finalImage; } }]; } @@ -172,7 +153,7 @@ - (void)resetSnapshotter { if (!_snapshotter.isLoading) { - _options.size = _nodeSize; + _options.size = self.calculatedSize; _snapshotter = [[MKMapSnapshotter alloc] initWithOptions:_options]; } } @@ -180,52 +161,45 @@ #pragma mark - Action - (void)addLiveMap { - if (self.isNodeLoaded && !_mapView) { - _mapView = [[ASDisplayNode alloc]initWithViewBlock:^UIView *{ - MKMapView *mapView = [[MKMapView alloc]initWithFrame:CGRectMake(0.0f, 0.0f, self.calculatedSize.width, self.calculatedSize.height)]; - mapView.delegate = _mapDelegate; - [mapView setRegion:_options.region]; - [mapView addAnnotations:_annotations]; - return mapView; + if (self.isNodeLoaded && !_mapNode) { + _mapNode = [[ASDisplayNode alloc]initWithViewBlock:^UIView *{ + _mapView = [[MKMapView alloc]initWithFrame:CGRectMake(0.0f, 0.0f, self.calculatedSize.width, self.calculatedSize.height)]; + _mapView.delegate = _mapDelegate; + [_mapView setRegion:_options.region]; + [_mapView addAnnotations:_annotations]; + return _mapView; }]; - [self addSubnode:_mapView]; - _mapImage.hidden = YES; + [self addSubnode:_mapNode]; + + if (CLLocationCoordinate2DIsValid(_centerCoordinateOfMap)) { + [_mapView setCenterCoordinate:_centerCoordinateOfMap]; + } } } - (void)removeLiveMap { - if (_mapView) { - [_mapView removeFromSupernode]; + if (_mapNode) { + _centerCoordinateOfMap = _mapView.centerCoordinate; + [_mapNode removeFromSupernode]; _mapView = nil; - _mapImage.hidden = NO; + _mapNode = nil; } + self.image = nil; } #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. - (void)layout { [super layout]; 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 { - _mapImage.frame = CGRectMake(0.0f, 0.0f, self.calculatedSize.width, self.calculatedSize.height); - if (!CGSizeEqualToSize(_maxSize, self.bounds.size)) { - _mapImage.preferredFrameSize = self.bounds.size; - _maxSize = self.bounds.size; - if (_automaticallyReloadsMapImageOnOrientationChange && _mapImage.image) { + // If our bounds.size is different from our current snapshot size, then let's request a new image from MKMapSnapshotter. + if (!CGSizeEqualToSize(_options.size, self.bounds.size)) { + if (_needsMapReloadOnBoundsChange && self.image) { [self resetSnapshotter]; [self takeSnapshot]; } diff --git a/examples/Kittens/Sample.xcodeproj/project.pbxproj b/examples/Kittens/Sample.xcodeproj/project.pbxproj index ddfd884074..e70aa77813 100644 --- a/examples/Kittens/Sample.xcodeproj/project.pbxproj +++ b/examples/Kittens/Sample.xcodeproj/project.pbxproj @@ -128,6 +128,7 @@ 05E2127E19D4DB510098F589 /* Frameworks */, 05E2127F19D4DB510098F589 /* Resources */, F012A6F39E0149F18F564F50 /* Copy Pods Resources */, + 860D1494A00C2E990C93A4D9 /* Embed Pods Frameworks */, ); buildRules = ( ); @@ -184,6 +185,21 @@ /* End PBXResourcesBuildPhase 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 */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; diff --git a/examples/Kittens/Sample/KittenNode.mm b/examples/Kittens/Sample/KittenNode.mm index adc656aa22..144313959d 100644 --- a/examples/Kittens/Sample/KittenNode.mm +++ b/examples/Kittens/Sample/KittenNode.mm @@ -29,6 +29,7 @@ static const CGFloat kInnerPadding = 10.0f; ASNetworkImageNode *_imageNode; ASTextNode *_textNode; ASDisplayNode *_divider; + ASMapNode *_map; BOOL _isImageEnlarged; BOOL _swappedTextAndImage; } @@ -88,6 +89,22 @@ static const CGFloat kInnerPadding = 10.0f; // _imageNode.contentMode = UIViewContentModeCenter; [_imageNode addTarget:self action:@selector(toggleNodesSwap) forControlEvents:ASControlNodeEventTouchUpInside]; [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 _textNode = [[ASTextNode alloc] init]; @@ -131,16 +148,25 @@ static const CGFloat kInnerPadding = 10.0f; NSParagraphStyleAttributeName: style }; } +- (void)makeMapInteractive +{ + [_map setLiveMap:YES]; +} + #if UseAutomaticLayout - (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize { _imageNode.preferredFrameSize = _isImageEnlarged ? CGSizeMake(2.0 * kImageSize, 2.0 * kImageSize) : CGSizeMake(kImageSize, kImageSize); _textNode.flexShrink = YES; + + ASRatioLayoutSpec *ratioSpec = [[ASRatioLayoutSpec alloc]init]; + ratioSpec.ratio = 0.5; + ratioSpec.child = _map; ASStackLayoutSpec *stackSpec = [[ASStackLayoutSpec alloc] init]; - stackSpec.direction = ASStackLayoutDirectionHorizontal; + stackSpec.direction = ASStackLayoutDirectionVertical; stackSpec.spacing = kInnerPadding; - [stackSpec setChildren:!_swappedTextAndImage ? @[_imageNode, _textNode] : @[_textNode, _imageNode]]; + [stackSpec setChildren:!_swappedTextAndImage ? @[ratioSpec,_imageNode, _textNode] : @[ratioSpec,_textNode, _imageNode]]; ASInsetLayoutSpec *insetSpec = [[ASInsetLayoutSpec alloc] init]; insetSpec.insets = UIEdgeInsetsMake(kOuterPadding, kOuterPadding, kOuterPadding, kOuterPadding); From 945e6fdb4454a017d5850e0da1a0ad8c2106670d Mon Sep 17 00:00:00 2001 From: Aaron Schubert Date: Thu, 3 Dec 2015 11:28:10 +0000 Subject: [PATCH 19/35] Revert accidental changes to Kittens example. Apologies @nguyenhuy ! --- .../Kittens/Sample.xcodeproj/project.pbxproj | 16 ---------- examples/Kittens/Sample/KittenNode.mm | 30 ++----------------- 2 files changed, 2 insertions(+), 44 deletions(-) diff --git a/examples/Kittens/Sample.xcodeproj/project.pbxproj b/examples/Kittens/Sample.xcodeproj/project.pbxproj index e70aa77813..ddfd884074 100644 --- a/examples/Kittens/Sample.xcodeproj/project.pbxproj +++ b/examples/Kittens/Sample.xcodeproj/project.pbxproj @@ -128,7 +128,6 @@ 05E2127E19D4DB510098F589 /* Frameworks */, 05E2127F19D4DB510098F589 /* Resources */, F012A6F39E0149F18F564F50 /* Copy Pods Resources */, - 860D1494A00C2E990C93A4D9 /* Embed Pods Frameworks */, ); buildRules = ( ); @@ -185,21 +184,6 @@ /* End PBXResourcesBuildPhase 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 */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; diff --git a/examples/Kittens/Sample/KittenNode.mm b/examples/Kittens/Sample/KittenNode.mm index 144313959d..adc656aa22 100644 --- a/examples/Kittens/Sample/KittenNode.mm +++ b/examples/Kittens/Sample/KittenNode.mm @@ -29,7 +29,6 @@ static const CGFloat kInnerPadding = 10.0f; ASNetworkImageNode *_imageNode; ASTextNode *_textNode; ASDisplayNode *_divider; - ASMapNode *_map; BOOL _isImageEnlarged; BOOL _swappedTextAndImage; } @@ -89,22 +88,6 @@ static const CGFloat kInnerPadding = 10.0f; // _imageNode.contentMode = UIViewContentModeCenter; [_imageNode addTarget:self action:@selector(toggleNodesSwap) forControlEvents:ASControlNodeEventTouchUpInside]; [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 _textNode = [[ASTextNode alloc] init]; @@ -148,25 +131,16 @@ static const CGFloat kInnerPadding = 10.0f; NSParagraphStyleAttributeName: style }; } -- (void)makeMapInteractive -{ - [_map setLiveMap:YES]; -} - #if UseAutomaticLayout - (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize { _imageNode.preferredFrameSize = _isImageEnlarged ? CGSizeMake(2.0 * kImageSize, 2.0 * kImageSize) : CGSizeMake(kImageSize, kImageSize); _textNode.flexShrink = YES; - - ASRatioLayoutSpec *ratioSpec = [[ASRatioLayoutSpec alloc]init]; - ratioSpec.ratio = 0.5; - ratioSpec.child = _map; ASStackLayoutSpec *stackSpec = [[ASStackLayoutSpec alloc] init]; - stackSpec.direction = ASStackLayoutDirectionVertical; + stackSpec.direction = ASStackLayoutDirectionHorizontal; stackSpec.spacing = kInnerPadding; - [stackSpec setChildren:!_swappedTextAndImage ? @[ratioSpec,_imageNode, _textNode] : @[ratioSpec,_textNode, _imageNode]]; + [stackSpec setChildren:!_swappedTextAndImage ? @[_imageNode, _textNode] : @[_textNode, _imageNode]]; ASInsetLayoutSpec *insetSpec = [[ASInsetLayoutSpec alloc] init]; insetSpec.insets = UIEdgeInsetsMake(kOuterPadding, kOuterPadding, kOuterPadding, kOuterPadding); From e1b50ce77ac29fcf963576800e8271a7356a7d02 Mon Sep 17 00:00:00 2001 From: Aaron Schubert Date: Thu, 3 Dec 2015 14:23:27 +0000 Subject: [PATCH 20/35] Fix merge conflicts --- AsyncDisplayKit.xcodeproj/project.pbxproj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 592ac0994a..023122035b 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -657,7 +657,7 @@ 4640521B1A3F83C40061C0BA /* ASFlowLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASFlowLayoutController.h; sourceTree = ""; }; 4640521C1A3F83C40061C0BA /* ASFlowLayoutController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASFlowLayoutController.mm; sourceTree = ""; }; 4640521D1A3F83C40061C0BA /* ASLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutController.h; sourceTree = ""; }; - 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AsyncDisplayKit.h; sourceTree = ""; }; + 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.c.h; path = AsyncDisplayKit.h; sourceTree = ""; }; 92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASMapNode.h; sourceTree = ""; }; 92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASMapNode.mm; sourceTree = ""; }; 92DD2FE51BF4D05E0074C9DD /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = System/Library/Frameworks/MapKit.framework; sourceTree = SDKROOT; }; @@ -1199,6 +1199,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 257754C21BEE458E00737CA5 /* ASTextKitCoreTextAdditions.h in Headers */, 92DD2FE31BF4B97E0074C9DD /* ASMapNode.h in Headers */, AC026B691BD57D6F00BBC17E /* ASChangeSetDataController.h in Headers */, 058D0A71195D05F800B7D73C /* _AS-objc-internal.h in Headers */, From 8f5b498381a8b7b80918a2e0791e2e64784eddae Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Fri, 4 Dec 2015 11:44:02 -0800 Subject: [PATCH 21/35] Move data controller classes into separate details group --- AsyncDisplayKit.xcodeproj/project.pbxproj | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 8708d2a031..78e2274309 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -950,10 +950,9 @@ 058D09E1195D050800B7D73C /* Details */ = { isa = PBXGroup; children = ( + 25B171EA1C12242700508A7A /* Data Controller */, CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */, CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */, - 251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */, - 251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */, 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */, 251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */, 251B8EF61BBB3D690087C538 /* ASDataController+Subclasses.h */, @@ -969,10 +968,6 @@ 299DA1A81A828D2900162D41 /* ASBatchContext.mm */, 205F0E1B1B373A2C007741D0 /* ASCollectionViewLayoutController.h */, 205F0E1C1B373A2C007741D0 /* ASCollectionViewLayoutController.mm */, - 464052191A3F83C40061C0BA /* ASDataController.h */, - 4640521A1A3F83C40061C0BA /* ASDataController.mm */, - AC026B671BD57D6F00BBC17E /* ASChangeSetDataController.h */, - AC026B681BD57D6F00BBC17E /* ASChangeSetDataController.m */, 05A6D05819D0EB64002DD95E /* ASDealloc2MainObject.h */, 05A6D05919D0EB64002DD95E /* ASDealloc2MainObject.m */, 4640521B1A3F83C40061C0BA /* ASFlowLayoutController.h */, @@ -1110,6 +1105,19 @@ name = TextKit; sourceTree = ""; }; + 25B171EA1C12242700508A7A /* Data Controller */ = { + isa = PBXGroup; + children = ( + 251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */, + 251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */, + 464052191A3F83C40061C0BA /* ASDataController.h */, + 4640521A1A3F83C40061C0BA /* ASDataController.mm */, + AC026B671BD57D6F00BBC17E /* ASChangeSetDataController.h */, + AC026B681BD57D6F00BBC17E /* ASChangeSetDataController.m */, + ); + name = "Data Controller"; + sourceTree = ""; + }; AC6456051B0A333200CF11B8 /* Layout */ = { isa = PBXGroup; children = ( From 840884272d4c448951c0186a8b832e0e7d0ffde1 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Sat, 5 Dec 2015 22:20:16 -0800 Subject: [PATCH 22/35] Introduced ASHierarchyState. Created ASDisplayNode+FrameworkPrivate.h. Fixed deadlock. --- AsyncDisplayKit.xcodeproj/project.pbxproj | 6 + AsyncDisplayKit/ASCellNode.m | 2 +- AsyncDisplayKit/ASCollectionView.mm | 19 ++- AsyncDisplayKit/ASDisplayNode+Subclasses.h | 44 ------ AsyncDisplayKit/ASDisplayNode.mm | 140 +++++++++++------- AsyncDisplayKit/ASDisplayNodeExtras.h | 6 + AsyncDisplayKit/ASDisplayNodeExtras.mm | 9 +- AsyncDisplayKit/ASMultiplexImageNode.mm | 1 + AsyncDisplayKit/ASNetworkImageNode.mm | 5 +- AsyncDisplayKit/ASTableView.mm | 12 +- AsyncDisplayKit/ASViewController.m | 4 +- .../Details/ASRangeHandlerPreload.mm | 2 +- .../Details/ASRangeHandlerRender.mm | 2 +- .../Details/ASRangeHandlerVisible.mm | 2 +- AsyncDisplayKit/Details/_ASDisplayLayer.mm | 1 + AsyncDisplayKit/Details/_ASDisplayView.mm | 1 + .../Private/ASDisplayNode+AsyncDisplay.mm | 7 +- .../Private/ASDisplayNode+FrameworkPrivate.h | 109 ++++++++++++++ .../Private/ASDisplayNode+UIViewBridge.mm | 23 ++- .../Private/ASDisplayNodeInternal.h | 37 ++--- .../TextKit/ASTextNodeWordKerner.m | 2 +- 21 files changed, 281 insertions(+), 153 deletions(-) create mode 100644 AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 8708d2a031..a769a8fc6d 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -441,6 +441,8 @@ D785F6621A74327E00291744 /* ASScrollNode.h in Headers */ = {isa = PBXBuildFile; fileRef = D785F6601A74327E00291744 /* ASScrollNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; D785F6631A74327E00291744 /* ASScrollNode.m in Sources */ = {isa = PBXBuildFile; fileRef = D785F6611A74327E00291744 /* ASScrollNode.m */; }; DB7121BCD50849C498C886FB /* libPods-AsyncDisplayKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */; }; + DE6EA3221C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */; }; + DE6EA3231C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */; }; DECBD6E71BE56E1900CF4905 /* ASButtonNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; DECBD6E81BE56E1900CF4905 /* ASButtonNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; DECBD6E91BE56E1900CF4905 /* ASButtonNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */; }; @@ -725,6 +727,7 @@ D3779BCFF841AD3EB56537ED /* Pods-AsyncDisplayKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.release.xcconfig"; sourceTree = ""; }; D785F6601A74327E00291744 /* ASScrollNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASScrollNode.h; sourceTree = ""; }; D785F6611A74327E00291744 /* ASScrollNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASScrollNode.m; sourceTree = ""; }; + DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+FrameworkPrivate.h"; sourceTree = ""; }; DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASButtonNode.h; sourceTree = ""; }; DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASButtonNode.mm; sourceTree = ""; }; EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-AsyncDisplayKitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1045,6 +1048,7 @@ 058D0A09195D050800B7D73C /* ASDisplayNode+DebugTiming.h */, 058D0A0A195D050800B7D73C /* ASDisplayNode+DebugTiming.mm */, 058D0A0B195D050800B7D73C /* ASDisplayNode+UIViewBridge.mm */, + DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */, 058D0A0C195D050800B7D73C /* ASDisplayNodeInternal.h */, 058D0A0D195D050800B7D73C /* ASImageNode+CGExtras.h */, 058D0A0E195D050800B7D73C /* ASImageNode+CGExtras.m */, @@ -1231,6 +1235,7 @@ 257754B11BEE44CD00737CA5 /* ASTextKitShadower.h in Headers */, 058D0A7B195D05F900B7D73C /* ASDisplayNodeInternal.h in Headers */, 0587F9BD1A7309ED00AFF0BA /* ASEditableTextNode.h in Headers */, + DE6EA3221C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */, 1950C4491A3BB5C1005C8279 /* ASEqualityHelpers.h in Headers */, 257754A81BEE44CD00737CA5 /* ASTextKitContext.h in Headers */, 464052221A3F83C40061C0BA /* ASFlowLayoutController.h in Headers */, @@ -1340,6 +1345,7 @@ 254C6B791BF94DF4003EC431 /* ASTextKitEntityAttribute.h in Headers */, 509E68631B3AEDB4009B9150 /* ASCollectionViewLayoutController.h in Headers */, B35061F71B010EFD0018CF92 /* ASCollectionViewProtocols.h in Headers */, + DE6EA3231C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */, B35061FA1B010EFD0018CF92 /* ASControlNode+Subclasses.h in Headers */, B35061F81B010EFD0018CF92 /* ASControlNode.h in Headers */, B35062171B010EFD0018CF92 /* ASDataController.h in Headers */, diff --git a/AsyncDisplayKit/ASCellNode.m b/AsyncDisplayKit/ASCellNode.m index 50ad035702..a4357479f9 100644 --- a/AsyncDisplayKit/ASCellNode.m +++ b/AsyncDisplayKit/ASCellNode.m @@ -25,7 +25,7 @@ if (!(self = [super init])) return nil; - // use UITableViewCell defaults + // Use UITableViewCell defaults _selectionStyle = UITableViewCellSelectionStyleDefault; self.clipsToBounds = YES; diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index aac9507693..7c5e5fabac 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -6,19 +6,16 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import "ASCollectionView.h" - #import "ASAssert.h" -#import "ASCollectionViewLayoutController.h" -#import "ASRangeController.h" -#import "ASCollectionDataController.h" #import "ASBatchFetching.h" -#import "UICollectionViewLayout+ASConvenience.h" -#import "ASInternalHelpers.h" +#import "ASCollectionView.h" +#import "ASCollectionDataController.h" +#import "ASCollectionViewLayoutController.h" #import "ASCollectionViewFlowLayoutInspector.h" - -// FIXME: Temporary nonsense import until method names are finalized and exposed -#import "ASDisplayNode+Subclasses.h" +#import "ASDisplayNode+FrameworkPrivate.h" +#import "ASInternalHelpers.h" +#import "ASRangeController.h" +#import "UICollectionViewLayout+ASConvenience.h" static const NSUInteger kASCollectionViewAnimationNone = UITableViewRowAnimationNone; static const ASSizeRange kInvalidSizeRange = {CGSizeZero, CGSizeZero}; @@ -661,6 +658,8 @@ static BOOL _isInterceptedSelector(SEL sel) - (ASCellNode *)dataController:(ASDataController *)dataController nodeAtIndexPath:(NSIndexPath *)indexPath { ASCellNode *node = [_asyncDataSource collectionView:self nodeForItemAtIndexPath:indexPath]; + [node enterHierarchyState:ASHierarchyStateRangeManaged]; + ASDisplayNodeAssert([node isKindOfClass:ASCellNode.class], @"invalid node class, expected ASCellNode"); if (node.layoutDelegate == nil) { node.layoutDelegate = self; diff --git a/AsyncDisplayKit/ASDisplayNode+Subclasses.h b/AsyncDisplayKit/ASDisplayNode+Subclasses.h index 3af5c45254..b0bb3b0e0f 100644 --- a/AsyncDisplayKit/ASDisplayNode+Subclasses.h +++ b/AsyncDisplayKit/ASDisplayNode+Subclasses.h @@ -412,10 +412,8 @@ */ - (UIImage *)placeholderImage; - /** @name Description */ - /** * @abstract Return a description of the node * @@ -425,47 +423,5 @@ @end -@interface ASDisplayNode (ASDisplayNodePrivate) -/** - * This method has proven helpful in a few rare scenarios, similar to a category extension on UIView, - * but it's considered private API for now and its use should not be encouraged. - * @param checkViewHierarchy If YES, and no supernode can be found, method will walk up from `self.view` to find a supernode. - * If YES, this method must be called on the main thread and the node must not be layer-backed. - */ -- (ASDisplayNode *)_supernodeWithClass:(Class)supernodeClass checkViewHierarchy:(BOOL)checkViewHierarchy; - -// The two methods below will eventually be exposed, but their names are subject to change. -/** - * @abstract Ensure that all rendering is complete for this node and its descendents. - * - * @discussion Calling this method on the main thread after a node is added to the view heirarchy will ensure that - * placeholder states are never visible to the user. It is used by ASTableView, ASCollectionView, and ASViewController - * to implement their respective ".neverShowPlaceholders" option. - * - * If all nodes have layer.contents set and/or their layer does not have -needsDisplay set, the method will return immediately. - * - * This method is capable of handling a mixed set of nodes, with some not having started display, some in progress on an - * asynchronous display operation, and some already finished. - * - * In order to guarantee against deadlocks, this method should only be called on the main thread. - * It may block on the private queue, [_ASDisplayLayer displayQueue] - */ -- (void)recursivelyEnsureDisplay; - -/** - * @abstract Allows a node to bypass all ensureDisplay passes. Defaults to NO. - * - * @discussion Nodes that are expensive to draw and expected to have placeholder even with - * .neverShowPlaceholders enabled should set this to YES. - * - * ASImageNode uses the default of NO, as it is often used for UI images that are expected to synchronize with ensureDisplay. - * - * ASNetworkImageNode and ASMultiplexImageNode set this to YES, because they load data from a database or server, - * and are expected to support a placeholder state given that display is often blocked on slow data fetching. - */ -@property (nonatomic, assign) BOOL shouldBypassEnsureDisplay; - -@end - #define ASDisplayNodeAssertThreadAffinity(viewNode) ASDisplayNodeAssert(!viewNode || ASDisplayNodeThreadIsMain() || !(viewNode).nodeLoaded, @"Incorrect display node thread affinity - this method should not be called off the main thread after the ASDisplayNode's view or layer have been created") #define ASDisplayNodeCAssertThreadAffinity(viewNode) ASDisplayNodeCAssert(!viewNode || ASDisplayNodeThreadIsMain() || !(viewNode).nodeLoaded, @"Incorrect display node thread affinity - this method should not be called off the main thread after the ASDisplayNode's view or layer have been created") diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 2c68b3b7e4..0edc15136a 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -7,8 +7,9 @@ */ #import "ASDisplayNode.h" -#import "ASDisplayNode+Subclasses.h" #import "ASDisplayNodeInternal.h" +#import "ASDisplayNode+Subclasses.h" +#import "ASDisplayNode+FrameworkPrivate.h" #import "ASLayoutOptionsPrivate.h" #import @@ -38,6 +39,9 @@ @end +//#define LOG(...) NSLog(__VA_ARGS__) +#define LOG(...) + // Conditionally time these scopes to our debug ivars (only exist in debug/profile builds) #if TIME_DISPLAYNODE_OPS #define TIME_SCOPED(outVar) ASDN::ScopeTimer t(outVar) @@ -323,17 +327,6 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) #pragma mark - Core -- (void)__tearDown:(BOOL)tearDown subnodesOfNode:(ASDisplayNode *)node -{ - for (ASDisplayNode *subnode in node.subnodes) { - if (tearDown) { - [subnode __unloadNode]; - } else { - [subnode __loadNode]; - } - } -} - - (void)__unloadNode { ASDisplayNodeAssertThreadAffinity(self); @@ -357,22 +350,9 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) [self layer]; } -- (ASDisplayNode *)__rasterizedContainerNode -{ - ASDisplayNode *node = self.supernode; - while (node) { - if (node.shouldRasterizeDescendants) { - return node; - } - node = node.supernode; - } - - return nil; -} - - (BOOL)__shouldLoadViewOrLayer { - return ![self __rasterizedContainerNode]; + return !(_hierarchyState & ASHierarchyStateRasterized); } - (BOOL)__shouldSize @@ -645,28 +625,42 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) { ASDisplayNodeAssertThreadAffinity(self); ASDN::MutexLocker l(_propertyLock); + ASDisplayNodeAssert(!((_hierarchyState & ASHierarchyStateRasterized) && _flags.shouldRasterizeDescendants), + @"Subnode of a rasterized node should not have redundant shouldRasterizeDescendants enabled"); return _flags.shouldRasterizeDescendants; } -- (void)setShouldRasterizeDescendants:(BOOL)flag +- (void)setShouldRasterizeDescendants:(BOOL)shouldRasterize { ASDisplayNodeAssertThreadAffinity(self); ASDN::MutexLocker l(_propertyLock); - if (_flags.shouldRasterizeDescendants == flag) + if (_flags.shouldRasterizeDescendants == shouldRasterize) return; - _flags.shouldRasterizeDescendants = flag; + _flags.shouldRasterizeDescendants = shouldRasterize; if (self.isNodeLoaded) { - //recursively tear down or build up subnodes + // Recursively tear down or build up subnodes. + // TODO: When disabling rasterization, preserve rasterized backing store as placeholderImage + // while the newly materialized subtree finishes rendering. Then destroy placeholderImage to save memory. [self recursivelyClearContents]; - [self __tearDown:flag subnodesOfNode:self]; - if (flag == NO) { - [self _addSubnodeViewsAndLayers]; - } - [self recursivelyDisplayImmediately]; + ASDisplayNodePerformBlockOnEverySubnode(self, ^(ASDisplayNode *node) { + if (shouldRasterize) { + [node enterHierarchyState:ASHierarchyStateRasterized]; + [node __unloadNode]; + } else { + [node exitHierarchyState:ASHierarchyStateRasterized]; + [node __loadNode]; + } + }); + + if (self.interfaceState & ASInterfaceStateVisible) { + // TODO: Change this to recursivelyEnsureDisplay - but need a variant that does not skip + // nodes that have shouldBypassEnsureDisplay set (such as image nodes) so they are rasterized. + [self recursivelyDisplayImmediately]; + } } } @@ -969,7 +963,7 @@ static bool disableNotificationsForMovingBetweenParents(ASDisplayNode *from, ASD [_subnodes insertObject:subnode atIndex:subnodeIndex]; // Don't bother inserting the view/layer if in a rasterized subtree, becuase there are no layers in the hierarchy and none of this could possibly work. - if (!_flags.shouldRasterizeDescendants && ![self __rasterizedContainerNode]) { + if (!_flags.shouldRasterizeDescendants && [self __shouldLoadViewOrLayer]) { if (_layer) { ASDisplayNodeCAssertMainThread(); @@ -1090,7 +1084,7 @@ static NSInteger incrementIfFound(NSInteger i) { NSInteger aboveSublayerIndex = NSNotFound; // Don't bother figuring out the sublayerIndex if in a rasterized subtree, becuase there are no layers in the hierarchy and none of this could possibly work. - if (!_flags.shouldRasterizeDescendants && ![self __rasterizedContainerNode]) { + if (!_flags.shouldRasterizeDescendants && [self __shouldLoadViewOrLayer]) { if (_layer) { aboveSublayerIndex = [_layer.sublayers indexOfObjectIdenticalTo:above.layer]; ASDisplayNodeAssert(aboveSublayerIndex != NSNotFound, @"Somehow above's supernode is self, yet we could not find it in our layers to replace"); @@ -1345,7 +1339,15 @@ static NSInteger incrementIfFound(NSInteger i) { - (void)__setSupernode:(ASDisplayNode *)supernode { ASDN::MutexLocker l(_propertyLock); - _supernode = supernode; + if (_supernode != supernode) { + ASHierarchyState oldHierarchyState = _supernode.hierarchyState; + _supernode = supernode; + if (_supernode) { + [self enterHierarchyState:_supernode.hierarchyState]; + } else { + [self exitHierarchyState:oldHierarchyState]; + } + } } // Track that a node will be displayed as part of the current node hierarchy. @@ -1653,7 +1655,8 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer) * @see https://github.com/facebook/AsyncDisplayKit/issues/900 * Possible solution is to push `isInCellNode` state downward on `addSubnode`/`removeFromSupernode`. */ -- (BOOL)supportsInterfaceState { +- (BOOL)supportsInterfaceState +{ return ([self isKindOfClass:ASCellNode.class] || [self _supernodeWithClass:ASCellNode.class checkViewHierarchy:NO] != nil); } @@ -1664,23 +1667,23 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer) return _interfaceState; } -- (void)setInterfaceState:(ASInterfaceState)interfaceState +- (void)setInterfaceState:(ASInterfaceState)newState { - ASInterfaceState oldValue; + ASInterfaceState oldState; { ASDN::MutexLocker l(_propertyLock); - oldValue = _interfaceState; - _interfaceState = interfaceState; + oldState = _interfaceState; + _interfaceState = newState; } - if (interfaceState != oldValue) { - if ((interfaceState & ASInterfaceStateMeasureLayout) != (oldValue & ASInterfaceStateMeasureLayout)) { + if (newState != oldState) { + if ((newState & ASInterfaceStateMeasureLayout) != (oldState & ASInterfaceStateMeasureLayout)) { // Trigger asynchronous measurement if it is not already cached or being calculated. } // Entered or exited data loading state. - if ((interfaceState & ASInterfaceStateFetchData) != (oldValue & ASInterfaceStateFetchData)) { - if (interfaceState & ASInterfaceStateFetchData) { + if ((newState & ASInterfaceStateFetchData) != (oldState & ASInterfaceStateFetchData)) { + if (newState & ASInterfaceStateFetchData) { [self fetchData]; } else { [self clearFetchedData]; @@ -1688,8 +1691,8 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer) } // Entered or exited contents rendering state. - if ((interfaceState & ASInterfaceStateDisplay) != (oldValue & ASInterfaceStateDisplay)) { - if (interfaceState & ASInterfaceStateDisplay) { + if ((newState & ASInterfaceStateDisplay) != (oldState & ASInterfaceStateDisplay)) { + if (newState & ASInterfaceStateDisplay) { // Once the working window is eliminated (ASRangeHandlerRender), trigger display directly here. [self setDisplaySuspended:NO]; } else { @@ -1699,14 +1702,13 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer) } // Entered or exited data loading state. - if ((interfaceState & ASInterfaceStateVisible) != (oldValue & ASInterfaceStateVisible)) { - if (interfaceState & ASInterfaceStateVisible) { + if ((newState & ASInterfaceStateVisible) != (oldState & ASInterfaceStateVisible)) { + if (newState & ASInterfaceStateVisible) { // Consider providing a -didBecomeVisible. } else { // Consider providing a -didBecomeInvisible. } } - } } @@ -1724,6 +1726,40 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer) }); } +- (ASHierarchyState)hierarchyState +{ + ASDN::MutexLocker l(_propertyLock); + return _hierarchyState; +} + +- (void)setHierarchyState:(ASHierarchyState)newState +{ + ASHierarchyState oldState; + { + ASDN::MutexLocker l(_propertyLock); + oldState = _hierarchyState; + _hierarchyState = newState; + } + + if (newState != oldState) { + LOG(@"setHierarchyState: oldState = %lu, newState = %lu", (unsigned long)oldState, (unsigned long)newState); + } +} + +- (void)enterHierarchyState:(ASHierarchyState)hierarchyState +{ + ASDisplayNodePerformBlockOnEveryNode(nil, self, ^(ASDisplayNode *node) { + node.hierarchyState |= hierarchyState; + }); +} + +- (void)exitHierarchyState:(ASHierarchyState)hierarchyState +{ + ASDisplayNodePerformBlockOnEveryNode(nil, self, ^(ASDisplayNode *node) { + node.hierarchyState &= (~hierarchyState); + }); +} + - (void)layout { ASDisplayNodeAssertMainThread(); diff --git a/AsyncDisplayKit/ASDisplayNodeExtras.h b/AsyncDisplayKit/ASDisplayNodeExtras.h index 4ccf0b48ca..22dc2f2921 100644 --- a/AsyncDisplayKit/ASDisplayNodeExtras.h +++ b/AsyncDisplayKit/ASDisplayNodeExtras.h @@ -37,6 +37,12 @@ extern ASDisplayNode *ASDisplayNodeUltimateParentOfNode(ASDisplayNode *node); */ extern void ASDisplayNodePerformBlockOnEveryNode(CALayer *layer, ASDisplayNode *node, void(^block)(ASDisplayNode *node)); +/** + Identical to ASDisplayNodePerformBlockOnEveryNode, except it does not run the block on the + node provided directly to the function call - only on all descendants. + */ +extern void ASDisplayNodePerformBlockOnEverySubnode(ASDisplayNode *node, void(^block)(ASDisplayNode *node)); + /** Given a display node, traverses up the layer tree hierarchy, returning the first display node that passes block. */ diff --git a/AsyncDisplayKit/ASDisplayNodeExtras.mm b/AsyncDisplayKit/ASDisplayNodeExtras.mm index 681640ffa3..7665bee955 100644 --- a/AsyncDisplayKit/ASDisplayNodeExtras.mm +++ b/AsyncDisplayKit/ASDisplayNodeExtras.mm @@ -7,8 +7,8 @@ */ #import "ASDisplayNodeExtras.h" - #import "ASDisplayNodeInternal.h" +#import "ASDisplayNode+FrameworkPrivate.h" extern ASDisplayNode *ASLayerToDisplayNode(CALayer *layer) { @@ -46,6 +46,13 @@ extern void ASDisplayNodePerformBlockOnEveryNode(CALayer *layer, ASDisplayNode * } } +extern void ASDisplayNodePerformBlockOnEverySubnode(ASDisplayNode *node, void(^block)(ASDisplayNode *node)) +{ + for (ASDisplayNode *subnode in node.subnodes) { + ASDisplayNodePerformBlockOnEveryNode(nil, subnode, block); + } +} + id ASDisplayNodeFind(ASDisplayNode *node, BOOL (^block)(ASDisplayNode *node)) { CALayer *layer = node.layer; diff --git a/AsyncDisplayKit/ASMultiplexImageNode.mm b/AsyncDisplayKit/ASMultiplexImageNode.mm index f72fbdce39..c51e2bb2ba 100644 --- a/AsyncDisplayKit/ASMultiplexImageNode.mm +++ b/AsyncDisplayKit/ASMultiplexImageNode.mm @@ -17,6 +17,7 @@ #import "ASAvailability.h" #import "ASBaseDefines.h" #import "ASDisplayNode+Subclasses.h" +#import "ASDisplayNode+FrameworkPrivate.h" #import "ASLog.h" #import "ASPhotosFrameworkImageRequest.h" #import "ASEqualityHelpers.h" diff --git a/AsyncDisplayKit/ASNetworkImageNode.mm b/AsyncDisplayKit/ASNetworkImageNode.mm index e7905bebf2..c1437ef814 100644 --- a/AsyncDisplayKit/ASNetworkImageNode.mm +++ b/AsyncDisplayKit/ASNetworkImageNode.mm @@ -10,8 +10,9 @@ #import "ASBasicImageDownloader.h" #import "ASDisplayNode+Subclasses.h" -#import "ASThread.h" +#import "ASDisplayNode+FrameworkPrivate.h" #import "ASEqualityHelpers.h" +#import "ASThread.h" @interface ASNetworkImageNode () { @@ -30,10 +31,8 @@ BOOL _imageLoaded; } - @end - @implementation ASNetworkImageNode - (instancetype)initWithCache:(id)cache downloader:(id)downloader diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 530a26f638..751247d7a0 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -10,16 +10,14 @@ #import "ASTableViewInternal.h" #import "ASAssert.h" +#import "ASBatchFetching.h" #import "ASChangeSetDataController.h" #import "ASCollectionViewLayoutController.h" -#import "ASLayoutController.h" -#import "ASRangeController.h" -#import "ASBatchFetching.h" +#import "ASDisplayNode+FrameworkPrivate.h" #import "ASInternalHelpers.h" #import "ASLayout.h" - -// FIXME: Temporary nonsense import until method names are finalized and exposed -#import "ASDisplayNode+Subclasses.h" +#import "ASLayoutController.h" +#import "ASRangeController.h" static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; @@ -829,6 +827,8 @@ static BOOL _isInterceptedSelector(SEL sel) - (ASCellNode *)dataController:(ASDataController *)dataController nodeAtIndexPath:(NSIndexPath *)indexPath { ASCellNode *node = [_asyncDataSource tableView:self nodeForRowAtIndexPath:indexPath]; + [node enterHierarchyState:ASHierarchyStateRangeManaged]; + ASDisplayNodeAssert([node isKindOfClass:ASCellNode.class], @"invalid node class, expected ASCellNode"); if (node.layoutDelegate == nil) { node.layoutDelegate = self; diff --git a/AsyncDisplayKit/ASViewController.m b/AsyncDisplayKit/ASViewController.m index 3509ccbe85..ab2c6741e6 100644 --- a/AsyncDisplayKit/ASViewController.m +++ b/AsyncDisplayKit/ASViewController.m @@ -9,9 +9,7 @@ #import "ASViewController.h" #import "ASAssert.h" #import "ASDimension.h" - -// FIXME: Temporary nonsense import until method names are finalized and exposed -#import "ASDisplayNode+Subclasses.h" +#import "ASDisplayNode+FrameworkPrivate.h" @implementation ASViewController { diff --git a/AsyncDisplayKit/Details/ASRangeHandlerPreload.mm b/AsyncDisplayKit/Details/ASRangeHandlerPreload.mm index adbbf5ecfb..09a3623a26 100644 --- a/AsyncDisplayKit/Details/ASRangeHandlerPreload.mm +++ b/AsyncDisplayKit/Details/ASRangeHandlerPreload.mm @@ -8,7 +8,7 @@ #import "ASRangeHandlerPreload.h" #import "ASDisplayNode.h" -#import "ASDisplayNodeInternal.h" +#import "ASDisplayNode+FrameworkPrivate.h" @implementation ASRangeHandlerPreload diff --git a/AsyncDisplayKit/Details/ASRangeHandlerRender.mm b/AsyncDisplayKit/Details/ASRangeHandlerRender.mm index b2a751a8bd..206b7d7e62 100644 --- a/AsyncDisplayKit/Details/ASRangeHandlerRender.mm +++ b/AsyncDisplayKit/Details/ASRangeHandlerRender.mm @@ -10,7 +10,7 @@ #import "ASDisplayNode.h" #import "ASDisplayNode+Subclasses.h" -#import "ASDisplayNodeInternal.h" +#import "ASDisplayNode+FrameworkPrivate.h" @interface ASRangeHandlerRender () @property (nonatomic,readonly) UIWindow *workingWindow; diff --git a/AsyncDisplayKit/Details/ASRangeHandlerVisible.mm b/AsyncDisplayKit/Details/ASRangeHandlerVisible.mm index d8daf51889..f17bc1cad3 100644 --- a/AsyncDisplayKit/Details/ASRangeHandlerVisible.mm +++ b/AsyncDisplayKit/Details/ASRangeHandlerVisible.mm @@ -8,7 +8,7 @@ #import "ASRangeHandlerVisible.h" #import "ASDisplayNode.h" -#import "ASDisplayNodeInternal.h" +#import "ASDisplayNode+FrameworkPrivate.h" @implementation ASRangeHandlerVisible diff --git a/AsyncDisplayKit/Details/_ASDisplayLayer.mm b/AsyncDisplayKit/Details/_ASDisplayLayer.mm index 99e4bf68e8..12622d3bb6 100644 --- a/AsyncDisplayKit/Details/_ASDisplayLayer.mm +++ b/AsyncDisplayKit/Details/_ASDisplayLayer.mm @@ -14,6 +14,7 @@ #import "ASAssert.h" #import "ASDisplayNode.h" #import "ASDisplayNodeInternal.h" +#import "ASDisplayNode+FrameworkPrivate.h" @implementation _ASDisplayLayer { diff --git a/AsyncDisplayKit/Details/_ASDisplayView.mm b/AsyncDisplayKit/Details/_ASDisplayView.mm index 8b5300269d..027a312733 100644 --- a/AsyncDisplayKit/Details/_ASDisplayView.mm +++ b/AsyncDisplayKit/Details/_ASDisplayView.mm @@ -15,6 +15,7 @@ #import "ASAssert.h" #import "ASDisplayNodeExtras.h" #import "ASDisplayNodeInternal.h" +#import "ASDisplayNode+FrameworkPrivate.h" #import "ASDisplayNode+Subclasses.h" @interface _ASDisplayView () diff --git a/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm b/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm index e74cd00e35..bdfb9b2729 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm @@ -10,6 +10,7 @@ #import "_ASAsyncTransaction.h" #import "ASAssert.h" #import "ASDisplayNodeInternal.h" +#import "ASDisplayNode+FrameworkPrivate.h" @implementation ASDisplayNode (AsyncDisplay) @@ -84,7 +85,7 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync, return; } - BOOL rasterizingFromAscendent = [self __rasterizedContainerNode] != nil; + BOOL rasterizingFromAscendent = (_hierarchyState & ASHierarchyStateRasterized); // if super node is rasterizing descendents, subnodes will not have had layout calls becase they don't have layers if (rasterizingFromAscendent) { @@ -178,7 +179,7 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync, asyncdisplaykit_async_transaction_operation_block_t displayBlock = nil; - ASDisplayNodeAssert(rasterizing || ![self __rasterizedContainerNode], @"Rasterized descendants should never display unless being drawn into the rasterized container."); + ASDisplayNodeAssert(rasterizing || !(_hierarchyState & ASHierarchyStateRasterized), @"Rasterized descendants should never display unless being drawn into the rasterized container."); if (!rasterizing && self.shouldRasterizeDescendants) { CGRect bounds = self.bounds; @@ -296,7 +297,7 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync, ASDN::MutexLocker l(_propertyLock); - if ([self __rasterizedContainerNode]) { + if (_hierarchyState & ASHierarchyStateRasterized) { return; } diff --git a/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h b/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h new file mode 100644 index 0000000000..58cc12c35c --- /dev/null +++ b/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h @@ -0,0 +1,109 @@ +/* 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 following methods are ONLY for use by _ASDisplayLayer, _ASDisplayView, and ASDisplayNode. +// These methods must never be called or overridden by other classes. +// + +#import "_ASDisplayLayer.h" +#import "_AS-objc-internal.h" +#import "ASDisplayNodeExtraIvars.h" +#import "ASDisplayNode.h" +#import "ASSentinel.h" +#import "ASThread.h" +#import "ASLayoutOptions.h" + +/** + Hierarchy state is propogated from nodes to all of their children when certain behaviors are required from the subtree. + Examples include rasterization and external driving of the .interfaceState property. + By passing this information explicitly, performance is optimized by avoiding iteration up the supernode chain. + Lastly, this avoidance of supernode traversal protects against the possibility of deadlocks when a supernode is + simultaneously attempting to materialize views / layers for its subtree (as many related methods require property locking) + + Note: as the hierarchy deepens, more state properties may be enabled. However, state properties may never be disabled / + cancelled below the point they are enabled. They continue to the leaves of the hierarchy. + */ + +typedef NS_OPTIONS(NSUInteger, ASHierarchyState) +{ + /** The node may or may not have a supernode, but no supernode has a special hierarchy-influencing option enabled. */ + ASHierarchyStateNormal = 0, + /** The node has a supernode with .shouldRasterizeDescendants = YES. + Note: the root node of the rasterized subtree (the one with the property set on it) will NOT have this state set. */ + ASHierarchyStateRasterized = 1 << 0, + /** The node or one of its supernodes is managed by a class like ASRangeController. Most commonly, these nodes are + ASCellNode objects or a subnode of one, and are used in ASTableView or ASCollectionView. + These nodes also recieve regular updates to the .interfaceState property with more detailed status information. */ + ASHierarchyStateRangeManaged = 1 << 1, +}; + +@interface ASDisplayNode () <_ASDisplayLayerDelegate> +{ +@protected + ASInterfaceState _interfaceState; + ASHierarchyState _hierarchyState; +} + +// These methods are recursive, and either union or remove the provided interfaceState to all sub-elements. +- (void)enterInterfaceState:(ASInterfaceState)interfaceState; +- (void)exitInterfaceState:(ASInterfaceState)interfaceState; + +// These methods are recursive, and either union or remove the provided hierarchyState to all sub-elements. +- (void)enterHierarchyState:(ASHierarchyState)hierarchyState; +- (void)exitHierarchyState:(ASHierarchyState)hierarchyState; + +/** + * @abstract Returns the Hierarchy State of the node. + * + * @return The current ASHierarchyState of the node, indicating whether it is rasterized or managed by a range controller. + * + * @see ASInterfaceState + */ +@property (nonatomic, readwrite) ASHierarchyState hierarchyState; + +// The two methods below will eventually be exposed, but their names are subject to change. +/** + * @abstract Ensure that all rendering is complete for this node and its descendents. + * + * @discussion Calling this method on the main thread after a node is added to the view heirarchy will ensure that + * placeholder states are never visible to the user. It is used by ASTableView, ASCollectionView, and ASViewController + * to implement their respective ".neverShowPlaceholders" option. + * + * If all nodes have layer.contents set and/or their layer does not have -needsDisplay set, the method will return immediately. + * + * This method is capable of handling a mixed set of nodes, with some not having started display, some in progress on an + * asynchronous display operation, and some already finished. + * + * In order to guarantee against deadlocks, this method should only be called on the main thread. + * It may block on the private queue, [_ASDisplayLayer displayQueue] + */ +- (void)recursivelyEnsureDisplay; + +/** + * @abstract Allows a node to bypass all ensureDisplay passes. Defaults to NO. + * + * @discussion Nodes that are expensive to draw and expected to have placeholder even with + * .neverShowPlaceholders enabled should set this to YES. + * + * ASImageNode uses the default of NO, as it is often used for UI images that are expected to synchronize with ensureDisplay. + * + * ASNetworkImageNode and ASMultiplexImageNode set this to YES, because they load data from a database or server, + * and are expected to support a placeholder state given that display is often blocked on slow data fetching. + */ +@property (nonatomic, assign) BOOL shouldBypassEnsureDisplay; + +@end + +@interface UIView (ASDisplayNodeInternal) +@property (nonatomic, assign, readwrite) ASDisplayNode *asyncdisplaykit_node; +@end + +@interface CALayer (ASDisplayNodeInternal) +@property (nonatomic, assign, readwrite) ASDisplayNode *asyncdisplaykit_node; +@end diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index e9ec7d72e3..b23b994f8c 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -8,9 +8,11 @@ #import "_ASCoreAnimationExtras.h" #import "_ASPendingState.h" +#import "ASInternalHelpers.h" #import "ASAssert.h" -#import "ASDisplayNode+Subclasses.h" #import "ASDisplayNodeInternal.h" +#import "ASDisplayNode+Subclasses.h" +#import "ASDisplayNode+FrameworkPrivate.h" #import "ASEqualityHelpers.h" /** @@ -219,9 +221,22 @@ - (void)setNeedsDisplay { - ASDisplayNode *rasterizedContainerNode = [self __rasterizedContainerNode]; - if (rasterizedContainerNode) { - [rasterizedContainerNode setNeedsDisplay]; + if (_hierarchyState & ASHierarchyStateRasterized) { + ASPerformBlockOnMainThread(^{ + // The below operation must be performed on the main thread to ensure against an extremely rare deadlock, where a parent node + // begins materializing the view / layer heirarchy (locking itself or a descendant) while this node walks up + // the tree and requires locking that node to access .shouldRasterizeDescendants. + // For this reason, this method should be avoided when possible. Use _hierarchyState & ASHierarchyStateRasterized. + ASDisplayNodeAssertMainThread(); + ASDisplayNode *rasterizedContainerNode = self.supernode; + while (rasterizedContainerNode) { + if (rasterizedContainerNode.shouldRasterizeDescendants) { + break; + } + rasterizedContainerNode = rasterizedContainerNode.supernode; + } + [rasterizedContainerNode setNeedsDisplay]; + }); } else { [_layer setNeedsDisplay]; } diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index 0d3ab7efe3..f30361a3ab 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -22,13 +22,14 @@ BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector); void ASDisplayNodeRespectThreadAffinityOfNode(ASDisplayNode *node, void (^block)()); -typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) { - ASDisplayNodeMethodOverrideNone = 0, - ASDisplayNodeMethodOverrideTouchesBegan = 1 << 0, - ASDisplayNodeMethodOverrideTouchesCancelled = 1 << 1, - ASDisplayNodeMethodOverrideTouchesEnded = 1 << 2, - ASDisplayNodeMethodOverrideTouchesMoved = 1 << 3, - ASDisplayNodeMethodOverrideLayoutSpecThatFits = 1 << 4 +typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) +{ + ASDisplayNodeMethodOverrideNone = 0, + ASDisplayNodeMethodOverrideTouchesBegan = 1 << 0, + ASDisplayNodeMethodOverrideTouchesCancelled = 1 << 1, + ASDisplayNodeMethodOverrideTouchesEnded = 1 << 2, + ASDisplayNodeMethodOverrideTouchesMoved = 1 << 3, + ASDisplayNodeMethodOverrideLayoutSpecThatFits = 1 << 4 }; @class _ASPendingState; @@ -73,8 +74,6 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) { _ASPendingState *_pendingViewState; - ASInterfaceState _interfaceState; - struct ASDisplayNodeFlags { // public properties unsigned synchronous:1; @@ -118,9 +117,6 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) { // Bitmask to check which methods an object overrides. @property (nonatomic, assign, readonly) ASDisplayNodeMethodOverrides methodOverrides; -// These methods are recursive, and either union or remove the provided interfaceState to all sub-elements. -- (void)enterInterfaceState:(ASInterfaceState)interfaceState; -- (void)exitInterfaceState:(ASInterfaceState)interfaceState; // Swizzle to extend the builtin functionality with custom logic - (BOOL)__shouldLoadViewOrLayer; @@ -149,9 +145,6 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) { // Display the node's view/layer immediately on the current thread, bypassing the background thread rendering. Will be deprecated. - (void)displayImmediately; -// Returns the ancestor node that rasterizes descendants, or nil if none. -- (ASDisplayNode *)__rasterizedContainerNode; - // Alternative initialiser for backing with a custom view class. Supports asynchronous display with _ASDisplayView subclasses. - (id)initWithViewClass:(Class)viewClass; @@ -160,12 +153,12 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) { @property (nonatomic, assign) CGFloat contentsScaleForDisplay; -@end +/** + * This method has proven helpful in a few rare scenarios, similar to a category extension on UIView, + * but it's considered private API for now and its use should not be encouraged. + * @param checkViewHierarchy If YES, and no supernode can be found, method will walk up from `self.view` to find a supernode. + * If YES, this method must be called on the main thread and the node must not be layer-backed. + */ +- (ASDisplayNode *)_supernodeWithClass:(Class)supernodeClass checkViewHierarchy:(BOOL)checkViewHierarchy; -@interface UIView (ASDisplayNodeInternal) -@property (nonatomic, assign, readwrite) ASDisplayNode *asyncdisplaykit_node; -@end - -@interface CALayer (ASDisplayNodeInternal) -@property (nonatomic, assign, readwrite) ASDisplayNode *asyncdisplaykit_node; @end diff --git a/AsyncDisplayKit/TextKit/ASTextNodeWordKerner.m b/AsyncDisplayKit/TextKit/ASTextNodeWordKerner.m index ffd9655a91..f0ae6eea7f 100644 --- a/AsyncDisplayKit/TextKit/ASTextNodeWordKerner.m +++ b/AsyncDisplayKit/TextKit/ASTextNodeWordKerner.m @@ -68,7 +68,7 @@ { // If it's a space character and we have custom word kerning, use the whitespace action control character. if ([layoutManager.textStorage.string characterAtIndex:characterIndex] == ' ') - return NSControlCharacterWhitespaceAction; + return NSControlCharacterActionWhitespace; return defaultAction; } From 67aec97523a62ba2d9bb1b4cc92c2d7c1927c359 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Sat, 5 Dec 2015 22:41:40 -0800 Subject: [PATCH 23/35] Adopt ASHierarchyState to replace manual checking for ASCellNode parent. --- AsyncDisplayKit/ASDisplayNode.mm | 18 ++++++------------ AsyncDisplayKit/TextKit/ASTextNodeWordKerner.m | 2 +- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 0edc15136a..e7d266836c 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -1585,7 +1585,7 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer) ASDisplayNodeAssert(_flags.isEnteringHierarchy, @"You should never call -willEnterHierarchy directly. Appearance is automatically managed by ASDisplayNode"); ASDisplayNodeAssert(!_flags.isExitingHierarchy, @"ASDisplayNode inconsistency. __enterHierarchy and __exitHierarchy are mutually exclusive"); - if (![self supportsInterfaceState]) { + if (![self supportsRangeManagedInterfaceState]) { self.interfaceState = ASInterfaceStateInHierarchy; } } @@ -1596,7 +1596,7 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer) ASDisplayNodeAssert(_flags.isExitingHierarchy, @"You should never call -didExitHierarchy directly. Appearance is automatically managed by ASDisplayNode"); ASDisplayNodeAssert(!_flags.isEnteringHierarchy, @"ASDisplayNode inconsistency. __enterHierarchy and __exitHierarchy are mutually exclusive"); - if (![self supportsInterfaceState]) { + if (![self supportsRangeManagedInterfaceState]) { self.interfaceState = ASInterfaceStateNone; } } @@ -1647,18 +1647,12 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer) } /** - * We currently only set interface state on nodes - * in table/collection views. For other nodes, if they are - * in the hierarchy we return `Unknown`, otherwise we return `None`. - * - * TODO: Avoid traversing up node hierarchy due to possible deadlock. - * @see https://github.com/facebook/AsyncDisplayKit/issues/900 - * Possible solution is to push `isInCellNode` state downward on `addSubnode`/`removeFromSupernode`. + * We currently only set interface state on nodes in table/collection views. For other nodes, if they are + * in the hierarchy we enable all ASInterfaceState types with `ASInterfaceStateInHierarchy`, otherwise `None`. */ -- (BOOL)supportsInterfaceState +- (BOOL)supportsRangeManagedInterfaceState { - return ([self isKindOfClass:ASCellNode.class] - || [self _supernodeWithClass:ASCellNode.class checkViewHierarchy:NO] != nil); + return (_hierarchyState & ASHierarchyStateRangeManaged); } - (ASInterfaceState)interfaceState diff --git a/AsyncDisplayKit/TextKit/ASTextNodeWordKerner.m b/AsyncDisplayKit/TextKit/ASTextNodeWordKerner.m index f0ae6eea7f..ffd9655a91 100644 --- a/AsyncDisplayKit/TextKit/ASTextNodeWordKerner.m +++ b/AsyncDisplayKit/TextKit/ASTextNodeWordKerner.m @@ -68,7 +68,7 @@ { // If it's a space character and we have custom word kerning, use the whitespace action control character. if ([layoutManager.textStorage.string characterAtIndex:characterIndex] == ' ') - return NSControlCharacterActionWhitespace; + return NSControlCharacterWhitespaceAction; return defaultAction; } From 271f288a199f2c3795c63a15274b5ead7c05b7c6 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Sun, 6 Dec 2015 15:04:47 -0800 Subject: [PATCH 24/35] Polish ASHierarchyState implementation, fix old comment typos, work on test breakages. --- AsyncDisplayKit/ASDisplayNode.mm | 59 ++++++++++++++----- .../Private/ASDisplayNode+AsyncDisplay.mm | 2 +- AsyncDisplayKitTests/ASDisplayNodeTests.m | 2 +- AsyncDisplayKitTests/ASSnapshotTestCase.mm | 2 + 4 files changed, 47 insertions(+), 18 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index e7d266836c..8924e2b5b5 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -633,12 +633,14 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) - (void)setShouldRasterizeDescendants:(BOOL)shouldRasterize { ASDisplayNodeAssertThreadAffinity(self); - ASDN::MutexLocker l(_propertyLock); - - if (_flags.shouldRasterizeDescendants == shouldRasterize) - return; - - _flags.shouldRasterizeDescendants = shouldRasterize; + { + ASDN::MutexLocker l(_propertyLock); + + if (_flags.shouldRasterizeDescendants == shouldRasterize) + return; + + _flags.shouldRasterizeDescendants = shouldRasterize; + } if (self.isNodeLoaded) { // Recursively tear down or build up subnodes. @@ -661,6 +663,14 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) // nodes that have shouldBypassEnsureDisplay set (such as image nodes) so they are rasterized. [self recursivelyDisplayImmediately]; } + } else { + ASDisplayNodePerformBlockOnEverySubnode(self, ^(ASDisplayNode *node) { + if (shouldRasterize) { + [node enterHierarchyState:ASHierarchyStateRasterized]; + } else { + [node exitHierarchyState:ASHierarchyStateRasterized]; + } + }); } } @@ -962,7 +972,7 @@ static bool disableNotificationsForMovingBetweenParents(ASDisplayNode *from, ASD [oldSubnode removeFromSupernode]; [_subnodes insertObject:subnode atIndex:subnodeIndex]; - // Don't bother inserting the view/layer if in a rasterized subtree, becuase there are no layers in the hierarchy and none of this could possibly work. + // Don't bother inserting the view/layer if in a rasterized subtree, because there are no layers in the hierarchy and none of this could possibly work. if (!_flags.shouldRasterizeDescendants && [self __shouldLoadViewOrLayer]) { if (_layer) { ASDisplayNodeCAssertMainThread(); @@ -1083,7 +1093,7 @@ static NSInteger incrementIfFound(NSInteger i) { NSInteger aboveSubnodeIndex = [_subnodes indexOfObjectIdenticalTo:above]; NSInteger aboveSublayerIndex = NSNotFound; - // Don't bother figuring out the sublayerIndex if in a rasterized subtree, becuase there are no layers in the hierarchy and none of this could possibly work. + // Don't bother figuring out the sublayerIndex if in a rasterized subtree, because there are no layers in the hierarchy and none of this could possibly work. if (!_flags.shouldRasterizeDescendants && [self __shouldLoadViewOrLayer]) { if (_layer) { aboveSublayerIndex = [_layer.sublayers indexOfObjectIdenticalTo:above.layer]; @@ -1336,16 +1346,33 @@ static NSInteger incrementIfFound(NSInteger i) { return _supernode; } -- (void)__setSupernode:(ASDisplayNode *)supernode +- (void)__setSupernode:(ASDisplayNode *)newSupernode { - ASDN::MutexLocker l(_propertyLock); - if (_supernode != supernode) { - ASHierarchyState oldHierarchyState = _supernode.hierarchyState; - _supernode = supernode; - if (_supernode) { - [self enterHierarchyState:_supernode.hierarchyState]; + BOOL supernodeDidChange = NO; + ASDisplayNode *oldSupernode = nil; + { + ASDN::MutexLocker l(_propertyLock); + if (_supernode != newSupernode) { + oldSupernode = _supernode; // Access supernode properties outside of lock to avoid remote chance of deadlock, + // in case supernode implementation must access one of our properties. + _supernode = newSupernode; + supernodeDidChange = YES; + } + } + + if (supernodeDidChange) { + ASHierarchyState stateToEnterOrExit = (newSupernode ? newSupernode.hierarchyState + : oldSupernode.hierarchyState); + + BOOL parentWasOrIsRasterized = (newSupernode ? newSupernode.shouldRasterizeDescendants + : oldSupernode.shouldRasterizeDescendants); + if (parentWasOrIsRasterized) { + stateToEnterOrExit |= ASHierarchyStateRasterized; + } + if (newSupernode) { + [self enterHierarchyState:stateToEnterOrExit]; } else { - [self exitHierarchyState:oldHierarchyState]; + [self exitHierarchyState:stateToEnterOrExit]; } } } diff --git a/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm b/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm index bdfb9b2729..4242881a5b 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm @@ -87,7 +87,7 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync, BOOL rasterizingFromAscendent = (_hierarchyState & ASHierarchyStateRasterized); - // if super node is rasterizing descendents, subnodes will not have had layout calls becase they don't have layers + // if super node is rasterizing descendents, subnodes will not have had layout calls because they don't have layers if (rasterizingFromAscendent) { [self __layout]; } diff --git a/AsyncDisplayKitTests/ASDisplayNodeTests.m b/AsyncDisplayKitTests/ASDisplayNodeTests.m index ae37bc8765..c49f56a660 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeTests.m +++ b/AsyncDisplayKitTests/ASDisplayNodeTests.m @@ -1719,7 +1719,7 @@ static inline BOOL _CGPointEqualToPointWithEpsilon(CGPoint point1, CGPoint point ASTestWindow *window = [ASTestWindow new]; [window addSubview:cellNode.view]; XCTAssert(node.hasFetchedData); - XCTAssert(node.interfaceState == ASInterfaceStateFetchData); + XCTAssert(node.interfaceState == ASInterfaceStateInHierarchy); } - (void)testInitWithViewClass diff --git a/AsyncDisplayKitTests/ASSnapshotTestCase.mm b/AsyncDisplayKitTests/ASSnapshotTestCase.mm index a70b21a3d2..32c96590d6 100644 --- a/AsyncDisplayKitTests/ASSnapshotTestCase.mm +++ b/AsyncDisplayKitTests/ASSnapshotTestCase.mm @@ -7,6 +7,7 @@ */ #import "ASSnapshotTestCase.h" +#import "ASDisplayNode+FrameworkPrivate.h" #import "ASDisplayNodeInternal.h" @implementation ASSnapshotTestCase @@ -46,6 +47,7 @@ + (void)hackilySynchronouslyRecursivelyRenderNode:(ASDisplayNode *)node { +// TODO: Reconfigure this to be able to use [node recursivelyEnsureDisplay]; [self _recursivelySetDisplaysAsynchronously:NO forNode:node]; [self _recursivelyLayoutAndDisplayNode:node]; } From c11d90cd3a8097498410bfe566ba6b8f36365452 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Sun, 6 Dec 2015 18:46:29 -0800 Subject: [PATCH 25/35] Fix an issue with ASHierarchyState when disabling rasterization after having enabled it. --- AsyncDisplayKit/ASDisplayNode.mm | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 8924e2b5b5..b900b70be5 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -657,6 +657,12 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) [node __loadNode]; } }); + if (!shouldRasterize) { + // At this point all of our subnodes have their layers or views recreated, but we haven't added + // them to ours yet. This is because our node is already loaded, and the above recursion + // is only performed on our subnodes -- not self. + [self _addSubnodeViewsAndLayers]; + } if (self.interfaceState & ASInterfaceStateVisible) { // TODO: Change this to recursivelyEnsureDisplay - but need a variant that does not skip @@ -1735,6 +1741,9 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer) - (void)enterInterfaceState:(ASInterfaceState)interfaceState { + if (interfaceState == ASInterfaceStateNone) { + return; // This method is a no-op with a 0-bitfield argument, so don't bother recursing. + } ASDisplayNodePerformBlockOnEveryNode(nil, self, ^(ASDisplayNode *node) { node.interfaceState |= interfaceState; }); @@ -1742,6 +1751,9 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer) - (void)exitInterfaceState:(ASInterfaceState)interfaceState { + if (interfaceState == ASInterfaceStateNone) { + return; // This method is a no-op with a 0-bitfield argument, so don't bother recursing. + } ASDisplayNodePerformBlockOnEveryNode(nil, self, ^(ASDisplayNode *node) { node.interfaceState &= (~interfaceState); }); @@ -1769,6 +1781,9 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer) - (void)enterHierarchyState:(ASHierarchyState)hierarchyState { + if (hierarchyState == ASHierarchyStateNormal) { + return; // This method is a no-op with a 0-bitfield argument, so don't bother recursing. + } ASDisplayNodePerformBlockOnEveryNode(nil, self, ^(ASDisplayNode *node) { node.hierarchyState |= hierarchyState; }); @@ -1776,6 +1791,9 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer) - (void)exitHierarchyState:(ASHierarchyState)hierarchyState { + if (hierarchyState == ASHierarchyStateNormal) { + return; // This method is a no-op with a 0-bitfield argument, so don't bother recursing. + } ASDisplayNodePerformBlockOnEveryNode(nil, self, ^(ASDisplayNode *node) { node.hierarchyState &= (~hierarchyState); }); From 1e9c4563f364b5a52d729ddb848f9d0187618aa2 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Sun, 6 Dec 2015 19:30:04 -0800 Subject: [PATCH 26/35] Improve factoring of locking and early returns for -setInterfaceState: and -setHierarchyState: --- AsyncDisplayKit/ASDisplayNode.mm | 64 +++++++++++++++++--------------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index b900b70be5..0ca022172e 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -1696,45 +1696,46 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer) - (void)setInterfaceState:(ASInterfaceState)newState { - ASInterfaceState oldState; + ASInterfaceState oldState = ASInterfaceStateNone; { ASDN::MutexLocker l(_propertyLock); + if (_interfaceState == newState) { + return; + } oldState = _interfaceState; _interfaceState = newState; } - if (newState != oldState) { - if ((newState & ASInterfaceStateMeasureLayout) != (oldState & ASInterfaceStateMeasureLayout)) { - // Trigger asynchronous measurement if it is not already cached or being calculated. - } - - // Entered or exited data loading state. - if ((newState & ASInterfaceStateFetchData) != (oldState & ASInterfaceStateFetchData)) { - if (newState & ASInterfaceStateFetchData) { - [self fetchData]; - } else { - [self clearFetchedData]; - } + if ((newState & ASInterfaceStateMeasureLayout) != (oldState & ASInterfaceStateMeasureLayout)) { + // Trigger asynchronous measurement if it is not already cached or being calculated. + } + + // Entered or exited data loading state. + if ((newState & ASInterfaceStateFetchData) != (oldState & ASInterfaceStateFetchData)) { + if (newState & ASInterfaceStateFetchData) { + [self fetchData]; + } else { + [self clearFetchedData]; } + } - // Entered or exited contents rendering state. - if ((newState & ASInterfaceStateDisplay) != (oldState & ASInterfaceStateDisplay)) { - if (newState & ASInterfaceStateDisplay) { - // Once the working window is eliminated (ASRangeHandlerRender), trigger display directly here. - [self setDisplaySuspended:NO]; - } else { - [self setDisplaySuspended:YES]; - [self clearContents]; - } + // Entered or exited contents rendering state. + if ((newState & ASInterfaceStateDisplay) != (oldState & ASInterfaceStateDisplay)) { + if (newState & ASInterfaceStateDisplay) { + // Once the working window is eliminated (ASRangeHandlerRender), trigger display directly here. + [self setDisplaySuspended:NO]; + } else { + [self setDisplaySuspended:YES]; + [self clearContents]; } + } - // Entered or exited data loading state. - if ((newState & ASInterfaceStateVisible) != (oldState & ASInterfaceStateVisible)) { - if (newState & ASInterfaceStateVisible) { - // Consider providing a -didBecomeVisible. - } else { - // Consider providing a -didBecomeInvisible. - } + // Entered or exited data loading state. + if ((newState & ASInterfaceStateVisible) != (oldState & ASInterfaceStateVisible)) { + if (newState & ASInterfaceStateVisible) { + // Consider providing a -didBecomeVisible. + } else { + // Consider providing a -didBecomeInvisible. } } } @@ -1767,9 +1768,12 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer) - (void)setHierarchyState:(ASHierarchyState)newState { - ASHierarchyState oldState; + ASHierarchyState oldState = ASHierarchyStateNormal; { ASDN::MutexLocker l(_propertyLock); + if (_hierarchyState == newState) { + return; + } oldState = _hierarchyState; _hierarchyState = newState; } From 64da9e7ece7fc3bbfc8e9b966b8ca8aa0e6341b8 Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Sun, 6 Dec 2015 20:07:29 -0800 Subject: [PATCH 27/35] Ensure that new section supplementary nodes are inserted into backing store --- .../Details/ASCollectionDataController.mm | 56 ++++++++++++------- 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.mm b/AsyncDisplayKit/Details/ASCollectionDataController.mm index 1e3f7029d4..729b998d77 100644 --- a/AsyncDisplayKit/Details/ASCollectionDataController.mm +++ b/AsyncDisplayKit/Details/ASCollectionDataController.mm @@ -28,22 +28,30 @@ NSMutableDictionary *_pendingIndexPaths; } +- (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled +{ + self = [super initWithAsyncDataFetching:asyncDataFetchingEnabled]; + if (self != nil) { + _pendingNodes = [NSMutableDictionary dictionary]; + _pendingIndexPaths = [NSMutableDictionary dictionary]; + } + return self; +} + - (void)prepareForReloadData { - _pendingNodes = [NSMutableDictionary dictionary]; - _pendingIndexPaths = [NSMutableDictionary dictionary]; - - [[self supplementaryKinds] enumerateObjectsUsingBlock:^(NSString *kind, NSUInteger idx, BOOL *stop) { + NSArray *kinds = [self supplementaryKinds]; + for (NSString *kind in kinds) { LOG(@"Populating elements of kind: %@", kind); NSMutableArray *indexPaths = [NSMutableArray array]; NSMutableArray *nodes = [NSMutableArray array]; [self _populateSupplementaryNodesOfKind:kind withMutableNodes:nodes mutableIndexPaths:indexPaths]; _pendingNodes[kind] = nodes; _pendingIndexPaths[kind] = indexPaths; - + // Measure loaded nodes before leaving the main thread [self layoutLoadedNodes:nodes ofKind:kind atIndexPaths:indexPaths]; - }]; + } } - (void)willReloadData @@ -56,7 +64,7 @@ NSArray *editingNodes = [self editingNodesOfKind:kind]; NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodes.count)]; [self deleteSectionsOfKind:kind atIndexSet:indexSet completion:nil]; - + // Insert each section NSUInteger sectionCount = [self.collectionDataSource dataController:self numberOfSectionsForSupplementaryNodeOfKind:kind]; NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount]; @@ -75,7 +83,8 @@ - (void)prepareForInsertSections:(NSIndexSet *)sections { - [[self supplementaryKinds] enumerateObjectsUsingBlock:^(NSString *kind, NSUInteger idx, BOOL *stop) { + NSArray *kinds = [self supplementaryKinds]; + for (NSString *kind in kinds) { LOG(@"Populating elements of kind: %@, for sections: %@", kind, sections); NSMutableArray *nodes = [NSMutableArray array]; NSMutableArray *indexPaths = [NSMutableArray array]; @@ -85,7 +94,7 @@ // Measure loaded nodes before leaving the main thread [self layoutLoadedNodes:nodes ofKind:kind atIndexPaths:indexPaths]; - }]; + } } - (void)willInsertSections:(NSIndexSet *)sections @@ -97,25 +106,29 @@ } [self insertSections:sectionArray ofKind:kind atIndexSet:sections completion:nil]; - [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:nil]; - _pendingNodes[kind] = nil; - _pendingIndexPaths[kind] = nil; + [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { + [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; + }]; + [_pendingNodes removeObjectForKey:kind]; + [_pendingIndexPaths removeObjectForKey:kind]; }]; } - (void)willDeleteSections:(NSIndexSet *)sections { - [[self supplementaryKinds] enumerateObjectsUsingBlock:^(NSString *kind, NSUInteger idx, BOOL *stop) { + NSArray *kinds = [self supplementaryKinds]; + for (NSString *kind in kinds) { NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], sections); [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; [self deleteSectionsOfKind:kind atIndexSet:sections completion:nil]; - }]; + } } - (void)prepareForReloadSections:(NSIndexSet *)sections { - [[self supplementaryKinds] enumerateObjectsUsingBlock:^(NSString *kind, NSUInteger idx, BOOL *stop) { + NSArray *kinds = [self supplementaryKinds]; + for (NSString *kind in kinds) { NSMutableArray *nodes = [NSMutableArray array]; NSMutableArray *indexPaths = [NSMutableArray array]; [self _populateSupplementaryNodesOfKind:kind withSections:sections mutableNodes:nodes mutableIndexPaths:indexPaths]; @@ -124,7 +137,7 @@ // Measure loaded nodes before leaving the main thread [self layoutLoadedNodes:nodes ofKind:kind atIndexPaths:indexPaths]; - }]; + } } - (void)willReloadSections:(NSIndexSet *)sections @@ -134,14 +147,15 @@ [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; // reinsert the elements [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:nil]; - _pendingNodes[kind] = nil; - _pendingIndexPaths[kind] = nil; + [_pendingNodes removeObjectForKey:kind]; + [_pendingIndexPaths removeObjectForKey:kind]; }]; } - (void)willMoveSection:(NSInteger)section toSection:(NSInteger)newSection { - [[self supplementaryKinds] enumerateObjectsUsingBlock:^(NSString *kind, NSUInteger idx, BOOL *stop) { + NSArray *kinds = [self supplementaryKinds]; + for (NSString *kind in kinds) { NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], [NSIndexSet indexSetWithIndex:section]); NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths([self editingNodesOfKind:kind], indexPaths); [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; @@ -153,7 +167,7 @@ [updatedIndexPaths addObject:[sectionIndexPath indexPathByAddingIndex:[indexPath indexAtPosition:indexPath.length - 1]]]; }]; [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; - }]; + } } - (void)_populateSupplementaryNodesOfKind:(NSString *)kind withMutableNodes:(NSMutableArray *)nodes mutableIndexPaths:(NSMutableArray *)indexPaths @@ -214,4 +228,4 @@ return (id)self.dataSource; } -@end +@end \ No newline at end of file From a47a1b8c2aed38f6846512f81b250b17317de16f Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Sun, 6 Dec 2015 20:32:21 -0800 Subject: [PATCH 28/35] Bump podspec to 1.9.3. Remove Cocoapods-generated workspace files. --- AsyncDisplayKit.podspec | 4 ++-- Podfile.lock | 13 ------------- .../Sample.xcworkspace/contents.xcworkspacedata | 10 ---------- .../Sample.xcworkspace/contents.xcworkspacedata | 1 - .../Sample.xcworkspace/contents.xcworkspacedata | 10 ---------- .../Sample.xcworkspace/contents.xcworkspacedata | 1 - .../Sample.xcworkspace/contents.xcworkspacedata | 1 - .../Sample.xcworkspace/contents.xcworkspacedata | 1 - .../Sample.xcworkspace/contents.xcworkspacedata | 1 - .../Sample.xcworkspace/contents.xcworkspacedata | 1 - .../Sample.xcworkspace/contents.xcworkspacedata | 10 ---------- .../xcshareddata/WorkspaceSettings.xcsettings | 8 -------- .../Sample.xcworkspace/contents.xcworkspacedata | 1 - .../Sample.xcworkspace/contents.xcworkspacedata | 1 - .../Sample.xcworkspace/contents.xcworkspacedata | 1 - .../Sample.xcworkspace/contents.xcworkspacedata | 1 - 16 files changed, 2 insertions(+), 63 deletions(-) delete mode 100644 Podfile.lock delete mode 100644 examples/ASCollectionView/Sample.xcworkspace/contents.xcworkspacedata delete mode 100644 examples/ASTableViewStressTest/Sample.xcworkspace/contents.xcworkspacedata delete mode 100644 examples/CustomCollectionView/Sample.xcworkspace/contents.xcworkspacedata delete mode 100644 examples/EditableText/Sample.xcworkspace/contents.xcworkspacedata delete mode 100644 examples/HorizontalWithinVerticalScrolling/Sample.xcworkspace/contents.xcworkspacedata delete mode 100644 examples/Kittens/Sample.xcworkspace/contents.xcworkspacedata delete mode 100644 examples/Multiplex/Sample.xcworkspace/contents.xcworkspacedata delete mode 100644 examples/Placeholders/Sample.xcworkspace/contents.xcworkspacedata delete mode 100644 examples/SocialAppLayout/Sample.xcworkspace/contents.xcworkspacedata delete mode 100644 examples/SocialAppLayout/Sample.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings delete mode 100644 examples/Swift/Sample.xcworkspace/contents.xcworkspacedata delete mode 100644 examples/SynchronousConcurrency/Sample.xcworkspace/contents.xcworkspacedata delete mode 100644 examples/SynchronousKittens/Sample.xcworkspace/contents.xcworkspacedata delete mode 100644 examples/VerticalWithinHorizontalScrolling/Sample.xcworkspace/contents.xcworkspacedata diff --git a/AsyncDisplayKit.podspec b/AsyncDisplayKit.podspec index e1d2962524..fb9a6ea013 100644 --- a/AsyncDisplayKit.podspec +++ b/AsyncDisplayKit.podspec @@ -1,11 +1,11 @@ Pod::Spec.new do |spec| spec.name = 'AsyncDisplayKit' - spec.version = '1.9.2' + spec.version = '1.9.3' spec.license = { :type => 'BSD' } spec.homepage = 'http://asyncdisplaykit.org' spec.authors = { 'Scott Goodson' => 'scottgoodson@gmail.com', 'Ryan Nystrom' => 'rnystrom@fb.com' } spec.summary = 'Smooth asynchronous user interfaces for iOS apps.' - spec.source = { :git => 'https://github.com/facebook/AsyncDisplayKit.git', :tag => '1.9.2' } + spec.source = { :git => 'https://github.com/facebook/AsyncDisplayKit.git', :tag => '1.9.3' } spec.documentation_url = 'http://asyncdisplaykit.org/appledoc/' diff --git a/Podfile.lock b/Podfile.lock deleted file mode 100644 index cf8e89d790..0000000000 --- a/Podfile.lock +++ /dev/null @@ -1,13 +0,0 @@ -PODS: - - FBSnapshotTestCase (1.8.1) - - OCMock (2.2.4) - -DEPENDENCIES: - - FBSnapshotTestCase (~> 1.8.1) - - OCMock (~> 2.2) - -SPEC CHECKSUMS: - FBSnapshotTestCase: 3dc3899168747a0319c5278f5b3445c13a6532dd - OCMock: a6a7dc0e3997fb9f35d99f72528698ebf60d64f2 - -COCOAPODS: 0.38.2 diff --git a/examples/ASCollectionView/Sample.xcworkspace/contents.xcworkspacedata b/examples/ASCollectionView/Sample.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 7b5a2f3050..0000000000 --- a/examples/ASCollectionView/Sample.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/examples/ASTableViewStressTest/Sample.xcworkspace/contents.xcworkspacedata b/examples/ASTableViewStressTest/Sample.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index d98549fd35..0000000000 --- a/examples/ASTableViewStressTest/Sample.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/examples/CustomCollectionView/Sample.xcworkspace/contents.xcworkspacedata b/examples/CustomCollectionView/Sample.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 7b5a2f3050..0000000000 --- a/examples/CustomCollectionView/Sample.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/examples/EditableText/Sample.xcworkspace/contents.xcworkspacedata b/examples/EditableText/Sample.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index d98549fd35..0000000000 --- a/examples/EditableText/Sample.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/examples/HorizontalWithinVerticalScrolling/Sample.xcworkspace/contents.xcworkspacedata b/examples/HorizontalWithinVerticalScrolling/Sample.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index d98549fd35..0000000000 --- a/examples/HorizontalWithinVerticalScrolling/Sample.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/examples/Kittens/Sample.xcworkspace/contents.xcworkspacedata b/examples/Kittens/Sample.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index d98549fd35..0000000000 --- a/examples/Kittens/Sample.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/examples/Multiplex/Sample.xcworkspace/contents.xcworkspacedata b/examples/Multiplex/Sample.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index d98549fd35..0000000000 --- a/examples/Multiplex/Sample.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/examples/Placeholders/Sample.xcworkspace/contents.xcworkspacedata b/examples/Placeholders/Sample.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index d98549fd35..0000000000 --- a/examples/Placeholders/Sample.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/examples/SocialAppLayout/Sample.xcworkspace/contents.xcworkspacedata b/examples/SocialAppLayout/Sample.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 7b5a2f3050..0000000000 --- a/examples/SocialAppLayout/Sample.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/examples/SocialAppLayout/Sample.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/examples/SocialAppLayout/Sample.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index 54782e32fd..0000000000 --- a/examples/SocialAppLayout/Sample.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded - - - diff --git a/examples/Swift/Sample.xcworkspace/contents.xcworkspacedata b/examples/Swift/Sample.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index d98549fd35..0000000000 --- a/examples/Swift/Sample.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/examples/SynchronousConcurrency/Sample.xcworkspace/contents.xcworkspacedata b/examples/SynchronousConcurrency/Sample.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index d98549fd35..0000000000 --- a/examples/SynchronousConcurrency/Sample.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/examples/SynchronousKittens/Sample.xcworkspace/contents.xcworkspacedata b/examples/SynchronousKittens/Sample.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index d98549fd35..0000000000 --- a/examples/SynchronousKittens/Sample.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/examples/VerticalWithinHorizontalScrolling/Sample.xcworkspace/contents.xcworkspacedata b/examples/VerticalWithinHorizontalScrolling/Sample.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index d98549fd35..0000000000 --- a/examples/VerticalWithinHorizontalScrolling/Sample.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file From 223cac1784eb317995f82ce817e775a65bc2049a Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Sun, 6 Dec 2015 21:30:55 -0800 Subject: [PATCH 29/35] Inline supplementary kind iteration in data controller --- .../Details/ASCollectionDataController.mm | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.mm b/AsyncDisplayKit/Details/ASCollectionDataController.mm index 729b998d77..e8028161d6 100644 --- a/AsyncDisplayKit/Details/ASCollectionDataController.mm +++ b/AsyncDisplayKit/Details/ASCollectionDataController.mm @@ -40,8 +40,7 @@ - (void)prepareForReloadData { - NSArray *kinds = [self supplementaryKinds]; - for (NSString *kind in kinds) { + for (NSString *kind in [self supplementaryKinds]) { LOG(@"Populating elements of kind: %@", kind); NSMutableArray *indexPaths = [NSMutableArray array]; NSMutableArray *nodes = [NSMutableArray array]; @@ -83,8 +82,7 @@ - (void)prepareForInsertSections:(NSIndexSet *)sections { - NSArray *kinds = [self supplementaryKinds]; - for (NSString *kind in kinds) { + for (NSString *kind in [self supplementaryKinds]) { LOG(@"Populating elements of kind: %@, for sections: %@", kind, sections); NSMutableArray *nodes = [NSMutableArray array]; NSMutableArray *indexPaths = [NSMutableArray array]; @@ -116,8 +114,7 @@ - (void)willDeleteSections:(NSIndexSet *)sections { - NSArray *kinds = [self supplementaryKinds]; - for (NSString *kind in kinds) { + for (NSString *kind in [self supplementaryKinds]) { NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], sections); [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; @@ -127,8 +124,7 @@ - (void)prepareForReloadSections:(NSIndexSet *)sections { - NSArray *kinds = [self supplementaryKinds]; - for (NSString *kind in kinds) { + for (NSString *kind in [self supplementaryKinds]) { NSMutableArray *nodes = [NSMutableArray array]; NSMutableArray *indexPaths = [NSMutableArray array]; [self _populateSupplementaryNodesOfKind:kind withSections:sections mutableNodes:nodes mutableIndexPaths:indexPaths]; @@ -154,8 +150,7 @@ - (void)willMoveSection:(NSInteger)section toSection:(NSInteger)newSection { - NSArray *kinds = [self supplementaryKinds]; - for (NSString *kind in kinds) { + for (NSString *kind in [self supplementaryKinds]) { NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], [NSIndexSet indexSetWithIndex:section]); NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths([self editingNodesOfKind:kind], indexPaths); [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; From 88b36f58e7e9bfa9c9c3053a9841276487c562be Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Sun, 6 Dec 2015 21:45:46 -0800 Subject: [PATCH 30/35] Ensure an empty array is returned if visibleNodes is called before any nodes are complete. --- AsyncDisplayKit/ASCollectionView.mm | 16 ++++++++++------ AsyncDisplayKit/ASTableView.mm | 13 ++++++++----- AsyncDisplayKit/Details/ASDataController.mm | 15 ++++++++++++++- .../Sample.xcodeproj/project.pbxproj | 16 ---------------- 4 files changed, 32 insertions(+), 28 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 7c5e5fabac..ffd1c66005 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -391,15 +391,19 @@ static BOOL _isInterceptedSelector(SEL sel) { NSArray *indexPaths = [self indexPathsForVisibleItems]; NSMutableArray *visibleNodes = [[NSMutableArray alloc] init]; - - [indexPaths enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { - ASCellNode *visibleNode = [self nodeForItemAtIndexPath:obj]; - [visibleNodes addObject:visibleNode]; - }]; - + + for (NSIndexPath *indexPath in indexPaths) { + ASCellNode *node = [self nodeForItemAtIndexPath:indexPath]; + if (node) { + // It is possible for UICollectionView to return indexPaths before the node is completed. + [visibleNodes addObject:node]; + } + } + return visibleNodes; } + #pragma mark Assertions. - (void)performBatchAnimated:(BOOL)animated updates:(void (^)())updates completion:(void (^)(BOOL))completion diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 751247d7a0..2c73367e96 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -379,11 +379,14 @@ static BOOL _isInterceptedSelector(SEL sel) NSArray *indexPaths = [self indexPathsForVisibleRows]; NSMutableArray *visibleNodes = [[NSMutableArray alloc] init]; - [indexPaths enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { - ASCellNode *visibleNode = [self nodeForRowAtIndexPath:obj]; - [visibleNodes addObject:visibleNode]; - }]; - + for (NSIndexPath *indexPath in indexPaths) { + ASCellNode *node = [self nodeForRowAtIndexPath:indexPath]; + if (node) { + // It is possible for UITableView to return indexPaths before the node is completed. + [visibleNodes addObject:node]; + } + } + return visibleNodes; } diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index e9a728c42e..72b676a7de 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -902,7 +902,20 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; - (ASCellNode *)nodeAtIndexPath:(NSIndexPath *)indexPath { ASDisplayNodeAssertMainThread(); - return [self completedNodes][indexPath.section][indexPath.row]; + + NSArray *completedNodes = [self completedNodes]; + NSInteger section = indexPath.section; + NSInteger row = indexPath.row; + ASCellNode *node = nil; + + if (section >= 0 && row >= 0 && section < completedNodes.count) { + NSArray *completedNodesSection = completedNodes[section]; + if (row < completedNodesSection.count) { + node = completedNodesSection[row]; + } + } + + return node; } - (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode; diff --git a/examples/ASTableViewStressTest/Sample.xcodeproj/project.pbxproj b/examples/ASTableViewStressTest/Sample.xcodeproj/project.pbxproj index 30be626c5c..e066c2fcd1 100644 --- a/examples/ASTableViewStressTest/Sample.xcodeproj/project.pbxproj +++ b/examples/ASTableViewStressTest/Sample.xcodeproj/project.pbxproj @@ -118,7 +118,6 @@ 05E2127E19D4DB510098F589 /* Frameworks */, 05E2127F19D4DB510098F589 /* Resources */, F012A6F39E0149F18F564F50 /* Copy Pods Resources */, - E671F9E92DFB9088485E493B /* Embed Pods Frameworks */, ); buildRules = ( ); @@ -190,21 +189,6 @@ shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; - E671F9E92DFB9088485E493B /* 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; - }; F012A6F39E0149F18F564F50 /* Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; From 082dc1cbcacc670ef1e2de444ec118bb99fc4f42 Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Thu, 10 Dec 2015 19:42:17 -0800 Subject: [PATCH 31/35] Expose recursivelyEnsureDisplay --- AsyncDisplayKit.xcodeproj/project.pbxproj | 8 +++++++- AsyncDisplayKit/ASDisplayNode+Beta.h | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 AsyncDisplayKit/ASDisplayNode+Beta.h diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 91e8c8b36b..d4b8ec1a8f 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -255,6 +255,8 @@ 509E68641B3AEDB7009B9150 /* ASCollectionViewLayoutController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E1C1B373A2C007741D0 /* ASCollectionViewLayoutController.mm */; }; 509E68651B3AEDC5009B9150 /* CGRect+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E1F1B376416007741D0 /* CGRect+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; 509E68661B3AEDD7009B9150 /* CGRect+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E201B376416007741D0 /* CGRect+ASConvenience.m */; }; + 68B0277A1C1A79CC0041016B /* ASDisplayNode+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = 68B027791C1A79CC0041016B /* ASDisplayNode+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 68B0277B1C1A79D60041016B /* ASDisplayNode+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = 68B027791C1A79CC0041016B /* ASDisplayNode+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; }; 6BDC61F61979037800E50D21 /* AsyncDisplayKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 92DD2FE31BF4B97E0074C9DD /* ASMapNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; 92DD2FE41BF4B97E0074C9DD /* ASMapNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */; }; @@ -531,7 +533,7 @@ 058D09D7195D050800B7D73C /* ASControlNode+Subclasses.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASControlNode+Subclasses.h"; sourceTree = ""; }; 058D09D8195D050800B7D73C /* ASDisplayNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASDisplayNode.h; sourceTree = ""; }; 058D09D9195D050800B7D73C /* ASDisplayNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASDisplayNode.mm; sourceTree = ""; }; - 058D09DA195D050800B7D73C /* ASDisplayNode+Subclasses.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = "ASDisplayNode+Subclasses.h"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 058D09DA195D050800B7D73C /* ASDisplayNode+Subclasses.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = "ASDisplayNode+Subclasses.h"; sourceTree = ""; }; 058D09DB195D050800B7D73C /* ASDisplayNodeExtras.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDisplayNodeExtras.h; sourceTree = ""; }; 058D09DC195D050800B7D73C /* ASDisplayNodeExtras.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASDisplayNodeExtras.mm; sourceTree = ""; }; 058D09DD195D050800B7D73C /* ASImageNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASImageNode.h; sourceTree = ""; }; @@ -659,6 +661,7 @@ 4640521B1A3F83C40061C0BA /* ASFlowLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASFlowLayoutController.h; sourceTree = ""; }; 4640521C1A3F83C40061C0BA /* ASFlowLayoutController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASFlowLayoutController.mm; sourceTree = ""; }; 4640521D1A3F83C40061C0BA /* ASLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutController.h; sourceTree = ""; }; + 68B027791C1A79CC0041016B /* ASDisplayNode+Beta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+Beta.h"; sourceTree = ""; }; 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.c.h; path = AsyncDisplayKit.h; sourceTree = ""; }; 92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASMapNode.h; sourceTree = ""; }; 92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASMapNode.mm; sourceTree = ""; }; @@ -872,6 +875,7 @@ 058D09D7195D050800B7D73C /* ASControlNode+Subclasses.h */, 058D09D8195D050800B7D73C /* ASDisplayNode.h */, 058D09D9195D050800B7D73C /* ASDisplayNode.mm */, + 68B027791C1A79CC0041016B /* ASDisplayNode+Beta.h */, 058D09DA195D050800B7D73C /* ASDisplayNode+Subclasses.h */, 058D09DB195D050800B7D73C /* ASDisplayNodeExtras.h */, 058D09DC195D050800B7D73C /* ASDisplayNodeExtras.mm */, @@ -1257,6 +1261,7 @@ 058D0A84195D060300B7D73C /* ASDisplayNodeExtraIvars.h in Headers */, AC7A2C171BDE11DF0093FE1A /* ASTableViewInternal.h in Headers */, 058D0A4D195D05CB00B7D73C /* ASDisplayNodeExtras.h in Headers */, + 68B0277A1C1A79CC0041016B /* ASDisplayNode+Beta.h in Headers */, 257754B11BEE44CD00737CA5 /* ASTextKitShadower.h in Headers */, 058D0A7B195D05F900B7D73C /* ASDisplayNodeInternal.h in Headers */, 0587F9BD1A7309ED00AFF0BA /* ASEditableTextNode.h in Headers */, @@ -1419,6 +1424,7 @@ 34EFC76E1B701CF400AD841F /* ASRatioLayoutSpec.h in Headers */, 34EFC7651B701CCC00AD841F /* ASRelativeSize.h in Headers */, 254C6B741BF94DF4003EC431 /* ASTextNodeWordKerner.h in Headers */, + 68B0277B1C1A79D60041016B /* ASDisplayNode+Beta.h in Headers */, B350622D1B010EFD0018CF92 /* ASScrollDirection.h in Headers */, 254C6B751BF94DF4003EC431 /* ASTextKitHelpers.h in Headers */, B35062081B010EFD0018CF92 /* ASScrollNode.h in Headers */, diff --git a/AsyncDisplayKit/ASDisplayNode+Beta.h b/AsyncDisplayKit/ASDisplayNode+Beta.h new file mode 100644 index 0000000000..608f7be71b --- /dev/null +++ b/AsyncDisplayKit/ASDisplayNode+Beta.h @@ -0,0 +1,19 @@ +/* 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. + */ + +@interface ASDisplayNode (Beta) + +/** @name Layout */ + + +/** + * @abstract Recursively ensures node and all subnodes are displayed. + */ +- (void)recursivelyEnsureDisplay; + +@end From f94229796cdb2ba0aa47df7e03bd6b4fa809e39a Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Thu, 10 Dec 2015 19:53:51 -0800 Subject: [PATCH 32/35] Fixes out of order blocks running on main queue in ASDataController MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ASDataController’s completedNodes is accessed by treating the main thread as a serial queue. The ​*problem*​ is, operations can cut in line by being added while on the main thread. I.e. it just calls the block instead of dispatch_async’ing to the main thread. This results in data inconsistency. To address this, I've added a queue of operations which get run serially (not to be confused with a serial dispatch queue). Instead of running blocks directly on the main thread, if it’s called while not on the main thread, it dispatch_asyncs to the main thread and runs any blocks in the queue. If it ​*is*​ on the main thread, it runs any blocks already in the queue and then runs the block. --- AsyncDisplayKit/Details/ASDataController.mm | 87 +++++++++++++++++---- 1 file changed, 72 insertions(+), 15 deletions(-) diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index 72b676a7de..4d8a4a8710 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -16,6 +16,7 @@ #import "ASMultidimensionalArrayUtils.h" #import "ASInternalHelpers.h" #import "ASLayout.h" +#import "ASThread.h" //#define LOG(...) NSLog(__VA_ARGS__) #define LOG(...) @@ -26,12 +27,22 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; static void *kASSizingQueueContext = &kASSizingQueueContext; +@interface ASMainQueueSerialQueue : NSObject +{ + ASDN::Mutex _serialQueueLock; + NSMutableArray *_blocks; +} + +- (void)performBlockOnMainThread:(dispatch_block_t)block; + +@end + @interface ASDataController () { NSMutableArray *_externalCompletedNodes; // Main thread only. External data access can immediately query this if available. NSMutableDictionary *_completedNodes; // Main thread only. External data access can immediately query this if _externalCompletedNodes is unavailable. NSMutableDictionary *_editingNodes; // Modified on _editingTransactionQueue only. Updates propogated to _completedNodes. - + ASMainQueueSerialQueue *_mainSerialQueue; NSMutableArray *_pendingEditCommandBlocks; // To be run on the main thread. Handles begin/endUpdates tracking. NSOperationQueue *_editingTransactionQueue; // Serial background queue. Dispatches concurrent layout and manages _editingNodes. @@ -63,6 +74,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; _completedNodes[ASDataControllerRowNodeKind] = [NSMutableArray array]; _editingNodes[ASDataControllerRowNodeKind] = [NSMutableArray array]; + _mainSerialQueue = [[ASMainQueueSerialQueue alloc] init]; + _pendingEditCommandBlocks = [NSMutableArray array]; _editingTransactionQueue = [[NSOperationQueue alloc] init]; @@ -208,12 +221,12 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // Deep copy is critical here, or future edits to the sub-arrays will pollute state between _editing and _complete on different threads. NSMutableArray *completedNodes = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(editingNodes); - ASPerformBlockOnMainThread(^{ + [_mainSerialQueue performBlockOnMainThread:^{ _completedNodes[kind] = completedNodes; if (completionBlock) { completionBlock(nodes, indexPaths); } - }); + }]; } - (void)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock @@ -227,13 +240,13 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; ASDeleteElementsInMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths); _editingNodes[kind] = editingNodes; - ASPerformBlockOnMainThread(^{ + [_mainSerialQueue performBlockOnMainThread:^{ NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_completedNodes[kind], indexPaths); ASDeleteElementsInMultidimensionalArrayAtIndexPaths(_completedNodes[kind], indexPaths); if (completionBlock) { completionBlock(nodes, indexPaths); } - }); + }]; } - (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSArray *sections, NSIndexSet *indexSet))completionBlock @@ -250,12 +263,12 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // Deep copy is critical here, or future edits to the sub-arrays will pollute state between _editing and _complete on different threads. NSArray *sectionsForCompleted = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(sections); - ASPerformBlockOnMainThread(^{ + [_mainSerialQueue performBlockOnMainThread:^{ [_completedNodes[kind] insertObjects:sectionsForCompleted atIndexes:indexSet]; if (completionBlock) { completionBlock(sections, indexSet); } - }); + }]; } - (void)deleteSectionsOfKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSIndexSet *indexSet))completionBlock @@ -263,12 +276,12 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; if (indexSet.count == 0) return; [_editingNodes[kind] removeObjectsAtIndexes:indexSet]; - ASPerformBlockOnMainThread(^{ + [_mainSerialQueue performBlockOnMainThread:^{ [_completedNodes[kind] removeObjectsAtIndexes:indexSet]; if (completionBlock) { completionBlock(indexSet); } - }); + }]; } #pragma mark - Internal Data Querying + Editing @@ -512,14 +525,14 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; LOG(@"endUpdatesWithCompletion - beginning"); [_editingTransactionQueue addOperationWithBlock:^{ - ASPerformBlockOnMainThread(^{ + [_mainSerialQueue performBlockOnMainThread:^{ // Deep copy _completedNodes to _externalCompletedNodes. // Any external queries from now on will be done on _externalCompletedNodes, to guarantee data consistency with the delegate. _externalCompletedNodes = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(_completedNodes[ASDataControllerRowNodeKind]); LOG(@"endUpdatesWithCompletion - begin updates call to delegate"); [_delegate dataControllerBeginUpdates:self]; - }); + }]; }]; // Running these commands may result in blocking on an _editingTransactionQueue operation that started even before -beginUpdates. @@ -532,13 +545,13 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_pendingEditCommandBlocks removeAllObjects]; [_editingTransactionQueue addOperationWithBlock:^{ - ASPerformBlockOnMainThread(^{ + [_mainSerialQueue performBlockOnMainThread:^{ // Now that the transaction is done, _completedNodes can be accessed externally again. _externalCompletedNodes = nil; LOG(@"endUpdatesWithCompletion - calling delegate end"); [_delegate dataController:self endUpdatesAnimated:animated completion:completion]; - }); + }]; }]; } } @@ -819,11 +832,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // i.e there might be some nodes that were measured using the old constrained size but haven't been added to _completedNodes // (see _layoutNodes:atIndexPaths:withAnimationOptions:). [_editingTransactionQueue addOperationWithBlock:^{ - ASPerformBlockOnMainThread(^{ + [_mainSerialQueue performBlockOnMainThread:^{ for (NSString *kind in [_completedNodes keyEnumerator]) { [self _relayoutNodesOfKind:kind]; } - }); + }]; }]; }]; } @@ -971,3 +984,47 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; } @end + +@implementation ASMainQueueSerialQueue + +- (instancetype)init +{ + if (!(self = [super init])) { + return nil; + } + + _blocks = [[NSMutableArray alloc] init]; + return self; +} + +- (void)performBlockOnMainThread:(dispatch_block_t)block +{ + ASDN::MutexLocker l(_serialQueueLock); + [_blocks addObject:block]; + [self runBlocks]; +} + +- (void)runBlocks +{ + dispatch_block_t mainThread = ^{ + ASDN::MutexLocker l(_serialQueueLock); + + for (NSUInteger i = 0; i < _blocks.count; i++) { + dispatch_block_t block = [_blocks objectAtIndex:i]; + block(); + } + + [_blocks removeAllObjects]; + }; + + if ([NSThread isMainThread]) { + ASDN::MutexUnlocker u(_serialQueueLock); + mainThread(); + } else { + dispatch_async(dispatch_get_main_queue(), ^{ + mainThread(); + }); + } +} + +@end From 066596314ef06e8c7bcafba0a3e488c24c53141d Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Fri, 11 Dec 2015 10:03:43 -0800 Subject: [PATCH 33/35] Fix deadlock when an editing transaction needs to perform its work on the main thread, and then elsewhere, on the main thread, we block waiting for all editing transactions to finish --- AsyncDisplayKit/Details/ASDataController.mm | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index 4d8a4a8710..c70726a52f 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -1001,24 +1001,28 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; { ASDN::MutexLocker l(_serialQueueLock); [_blocks addObject:block]; + ASDN::MutexUnlocker u(_serialQueueLock); [self runBlocks]; } - (void)runBlocks { dispatch_block_t mainThread = ^{ - ASDN::MutexLocker l(_serialQueueLock); - - for (NSUInteger i = 0; i < _blocks.count; i++) { - dispatch_block_t block = [_blocks objectAtIndex:i]; + do { + ASDN::MutexLocker l(_serialQueueLock); + dispatch_block_t block; + if (_blocks.count > 0) { + block = [_blocks objectAtIndex:0]; + [_blocks removeObjectAtIndex:0]; + } else { + break; + } + ASDN::MutexUnlocker u(_serialQueueLock); block(); - } - - [_blocks removeAllObjects]; + } while (true); }; if ([NSThread isMainThread]) { - ASDN::MutexUnlocker u(_serialQueueLock); mainThread(); } else { dispatch_async(dispatch_get_main_queue(), ^{ From 941a732fd6a7521ae6e215a6528f82ae2103c2f5 Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Fri, 11 Dec 2015 10:53:06 -0800 Subject: [PATCH 34/35] Rename and move ASMainSerialQueue to its own class --- AsyncDisplayKit.xcodeproj/project.pbxproj | 12 ++++ AsyncDisplayKit/Details/ASDataController.mm | 64 +------------------ AsyncDisplayKit/Details/ASMainSerialQueue.h | 15 +++++ AsyncDisplayKit/Details/ASMainSerialQueue.mm | 67 ++++++++++++++++++++ 4 files changed, 97 insertions(+), 61 deletions(-) create mode 100644 AsyncDisplayKit/Details/ASMainSerialQueue.h create mode 100644 AsyncDisplayKit/Details/ASMainSerialQueue.mm diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index d4b8ec1a8f..4269a60b2e 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -257,6 +257,10 @@ 509E68661B3AEDD7009B9150 /* CGRect+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E201B376416007741D0 /* CGRect+ASConvenience.m */; }; 68B0277A1C1A79CC0041016B /* ASDisplayNode+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = 68B027791C1A79CC0041016B /* ASDisplayNode+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; }; 68B0277B1C1A79D60041016B /* ASDisplayNode+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = 68B027791C1A79CC0041016B /* ASDisplayNode+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 68EE0DBD1C1B4ED300BA1B99 /* ASMainSerialQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 68EE0DBB1C1B4ED300BA1B99 /* ASMainSerialQueue.h */; }; + 68EE0DBE1C1B4ED300BA1B99 /* ASMainSerialQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 68EE0DBB1C1B4ED300BA1B99 /* ASMainSerialQueue.h */; }; + 68EE0DBF1C1B4ED300BA1B99 /* ASMainSerialQueue.mm in Sources */ = {isa = PBXBuildFile; fileRef = 68EE0DBC1C1B4ED300BA1B99 /* ASMainSerialQueue.mm */; }; + 68EE0DC01C1B4ED300BA1B99 /* ASMainSerialQueue.mm in Sources */ = {isa = PBXBuildFile; fileRef = 68EE0DBC1C1B4ED300BA1B99 /* ASMainSerialQueue.mm */; }; 6BDC61F61979037800E50D21 /* AsyncDisplayKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 92DD2FE31BF4B97E0074C9DD /* ASMapNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; 92DD2FE41BF4B97E0074C9DD /* ASMapNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */; }; @@ -662,6 +666,8 @@ 4640521C1A3F83C40061C0BA /* ASFlowLayoutController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASFlowLayoutController.mm; sourceTree = ""; }; 4640521D1A3F83C40061C0BA /* ASLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutController.h; sourceTree = ""; }; 68B027791C1A79CC0041016B /* ASDisplayNode+Beta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+Beta.h"; sourceTree = ""; }; + 68EE0DBB1C1B4ED300BA1B99 /* ASMainSerialQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASMainSerialQueue.h; sourceTree = ""; }; + 68EE0DBC1C1B4ED300BA1B99 /* ASMainSerialQueue.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASMainSerialQueue.mm; sourceTree = ""; }; 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.c.h; path = AsyncDisplayKit.h; sourceTree = ""; }; 92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASMapNode.h; sourceTree = ""; }; 92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASMapNode.mm; sourceTree = ""; }; @@ -1002,6 +1008,8 @@ 05F20AA31A15733C00DCA68A /* ASImageProtocols.h */, 4640521D1A3F83C40061C0BA /* ASLayoutController.h */, 292C59991A956527007E5DD6 /* ASLayoutRangeType.h */, + 68EE0DBB1C1B4ED300BA1B99 /* ASMainSerialQueue.h */, + 68EE0DBC1C1B4ED300BA1B99 /* ASMainSerialQueue.mm */, 058D09E8195D050800B7D73C /* ASMutableAttributedStringBuilder.h */, 058D09E9195D050800B7D73C /* ASMutableAttributedStringBuilder.m */, 055F1A3619ABD413004DAFF1 /* ASRangeController.h */, @@ -1229,6 +1237,7 @@ 058D0A74195D05F800B7D73C /* _ASPendingState.h in Headers */, 9C5586691BD549CB00B50E3A /* ASAsciiArtBoxCreator.h in Headers */, 058D0A76195D05F900B7D73C /* _ASScopeTimer.h in Headers */, + 68EE0DBD1C1B4ED300BA1B99 /* ASMainSerialQueue.h in Headers */, 257754B31BEE44CD00737CA5 /* ASTextKitTailTruncater.h in Headers */, 205F0E191B37339C007741D0 /* ASAbstractLayoutController.h in Headers */, 058D0A82195D060300B7D73C /* ASAssert.h in Headers */, @@ -1350,6 +1359,7 @@ B35062491B010EFD0018CF92 /* _ASCoreAnimationExtras.h in Headers */, B350620F1B010EFD0018CF92 /* _ASDisplayLayer.h in Headers */, B35062111B010EFD0018CF92 /* _ASDisplayView.h in Headers */, + 68EE0DBE1C1B4ED300BA1B99 /* ASMainSerialQueue.h in Headers */, B350624B1B010EFD0018CF92 /* _ASPendingState.h in Headers */, 9C55866C1BD54A3000B50E3A /* ASAsciiArtBoxCreator.h in Headers */, B350624D1B010EFD0018CF92 /* _ASScopeTimer.h in Headers */, @@ -1656,6 +1666,7 @@ AC026B711BD57DBF00BBC17E /* _ASHierarchyChangeSet.m in Sources */, 257754BF1BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m in Sources */, 058D0A18195D050800B7D73C /* _ASDisplayLayer.mm in Sources */, + 68EE0DBF1C1B4ED300BA1B99 /* ASMainSerialQueue.mm in Sources */, 058D0A19195D050800B7D73C /* _ASDisplayView.mm in Sources */, 9C55866A1BD549CB00B50E3A /* ASAsciiArtBoxCreator.m in Sources */, 058D0A27195D050800B7D73C /* _ASPendingState.m in Sources */, @@ -1783,6 +1794,7 @@ AC026B721BD57DBF00BBC17E /* _ASHierarchyChangeSet.m in Sources */, B35062421B010EFD0018CF92 /* _ASAsyncTransactionGroup.m in Sources */, B350624A1B010EFD0018CF92 /* _ASCoreAnimationExtras.mm in Sources */, + 68EE0DC01C1B4ED300BA1B99 /* ASMainSerialQueue.mm in Sources */, 2767E9421BB19BD600EA9B77 /* ASViewController.m in Sources */, B35062101B010EFD0018CF92 /* _ASDisplayLayer.mm in Sources */, 9C55866B1BD54A1900B50E3A /* ASAsciiArtBoxCreator.m in Sources */, diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index c70726a52f..aa774b5b41 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -13,10 +13,10 @@ #import "ASAssert.h" #import "ASCellNode.h" #import "ASDisplayNode.h" +#import "ASMainSerialQueue.h" #import "ASMultidimensionalArrayUtils.h" #import "ASInternalHelpers.h" #import "ASLayout.h" -#import "ASThread.h" //#define LOG(...) NSLog(__VA_ARGS__) #define LOG(...) @@ -27,22 +27,12 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; static void *kASSizingQueueContext = &kASSizingQueueContext; -@interface ASMainQueueSerialQueue : NSObject -{ - ASDN::Mutex _serialQueueLock; - NSMutableArray *_blocks; -} - -- (void)performBlockOnMainThread:(dispatch_block_t)block; - -@end - @interface ASDataController () { NSMutableArray *_externalCompletedNodes; // Main thread only. External data access can immediately query this if available. NSMutableDictionary *_completedNodes; // Main thread only. External data access can immediately query this if _externalCompletedNodes is unavailable. NSMutableDictionary *_editingNodes; // Modified on _editingTransactionQueue only. Updates propogated to _completedNodes. - ASMainQueueSerialQueue *_mainSerialQueue; + ASMainSerialQueue *_mainSerialQueue; NSMutableArray *_pendingEditCommandBlocks; // To be run on the main thread. Handles begin/endUpdates tracking. NSOperationQueue *_editingTransactionQueue; // Serial background queue. Dispatches concurrent layout and manages _editingNodes. @@ -74,7 +64,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; _completedNodes[ASDataControllerRowNodeKind] = [NSMutableArray array]; _editingNodes[ASDataControllerRowNodeKind] = [NSMutableArray array]; - _mainSerialQueue = [[ASMainQueueSerialQueue alloc] init]; + _mainSerialQueue = [[ASMainSerialQueue alloc] init]; _pendingEditCommandBlocks = [NSMutableArray array]; @@ -984,51 +974,3 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; } @end - -@implementation ASMainQueueSerialQueue - -- (instancetype)init -{ - if (!(self = [super init])) { - return nil; - } - - _blocks = [[NSMutableArray alloc] init]; - return self; -} - -- (void)performBlockOnMainThread:(dispatch_block_t)block -{ - ASDN::MutexLocker l(_serialQueueLock); - [_blocks addObject:block]; - ASDN::MutexUnlocker u(_serialQueueLock); - [self runBlocks]; -} - -- (void)runBlocks -{ - dispatch_block_t mainThread = ^{ - do { - ASDN::MutexLocker l(_serialQueueLock); - dispatch_block_t block; - if (_blocks.count > 0) { - block = [_blocks objectAtIndex:0]; - [_blocks removeObjectAtIndex:0]; - } else { - break; - } - ASDN::MutexUnlocker u(_serialQueueLock); - block(); - } while (true); - }; - - if ([NSThread isMainThread]) { - mainThread(); - } else { - dispatch_async(dispatch_get_main_queue(), ^{ - mainThread(); - }); - } -} - -@end diff --git a/AsyncDisplayKit/Details/ASMainSerialQueue.h b/AsyncDisplayKit/Details/ASMainSerialQueue.h new file mode 100644 index 0000000000..77e05d579e --- /dev/null +++ b/AsyncDisplayKit/Details/ASMainSerialQueue.h @@ -0,0 +1,15 @@ +// +// ASMainSerialQueue.h +// AsyncDisplayKit +// +// Created by Garrett Moon on 12/11/15. +// Copyright © 2015 Facebook. All rights reserved. +// + +#import + +@interface ASMainSerialQueue : NSObject + +- (void)performBlockOnMainThread:(dispatch_block_t)block; + +@end diff --git a/AsyncDisplayKit/Details/ASMainSerialQueue.mm b/AsyncDisplayKit/Details/ASMainSerialQueue.mm new file mode 100644 index 0000000000..f9ce3481c5 --- /dev/null +++ b/AsyncDisplayKit/Details/ASMainSerialQueue.mm @@ -0,0 +1,67 @@ +// +// ASMainSerialQueue.m +// AsyncDisplayKit +// +// Created by Garrett Moon on 12/11/15. +// Copyright © 2015 Facebook. All rights reserved. +// + +#import "ASMainSerialQueue.h" + +#import "ASThread.h" + +@interface ASMainSerialQueue () +{ + ASDN::Mutex _serialQueueLock; + NSMutableArray *_blocks; +} + +@end + +@implementation ASMainSerialQueue + +- (instancetype)init +{ + if (!(self = [super init])) { + return nil; + } + + _blocks = [[NSMutableArray alloc] init]; + return self; +} + +- (void)performBlockOnMainThread:(dispatch_block_t)block +{ + ASDN::MutexLocker l(_serialQueueLock); + [_blocks addObject:block]; + ASDN::MutexUnlocker u(_serialQueueLock); + [self runBlocks]; +} + +- (void)runBlocks +{ + dispatch_block_t mainThread = ^{ + do { + ASDN::MutexLocker l(_serialQueueLock); + dispatch_block_t block; + if (_blocks.count > 0) { + block = [_blocks objectAtIndex:0]; + [_blocks removeObjectAtIndex:0]; + } else { + break; + } + ASDN::MutexUnlocker u(_serialQueueLock); + block(); + } while (true); + }; + + if ([NSThread isMainThread]) { + mainThread(); + } else { + dispatch_async(dispatch_get_main_queue(), ^{ + mainThread(); + }); + } +} + +@end From e4ca0e3ccf443bf37d31de7f3d87bd5d74ef89a7 Mon Sep 17 00:00:00 2001 From: Samuel Hsiung Date: Fri, 11 Dec 2015 18:53:10 -0800 Subject: [PATCH 35/35] make ASEditableTextNode's textView clip to bounds --- AsyncDisplayKit/ASEditableTextNode.mm | 1 - 1 file changed, 1 deletion(-) diff --git a/AsyncDisplayKit/ASEditableTextNode.mm b/AsyncDisplayKit/ASEditableTextNode.mm index d8da46498b..13554d88e5 100644 --- a/AsyncDisplayKit/ASEditableTextNode.mm +++ b/AsyncDisplayKit/ASEditableTextNode.mm @@ -124,7 +124,6 @@ textView.opaque = NO; } textView.textContainerInset = self.textContainerInset; - textView.clipsToBounds = NO; // We don't want selection handles cut off. }; // Create and configure the placeholder text view.