From 5e73396cde1e173aef0ba46873b928f50f6f3d01 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Tue, 9 Jan 2018 14:34:32 -0800 Subject: [PATCH 01/40] Enable collection node interactive moves (#735) * Add support for interactive moves * Enable drag & drop in collection view example * Update changelog * Change the gating logic to match UIKit * Add a warning when we prevent interactive movement due to async layout --- CHANGELOG.md | 1 + Source/ASCollectionNode.h | 26 ++++ Source/ASCollectionView.mm | 87 ++++++++++--- Source/Details/ASDataController.mm | 2 +- Source/Layout/ASLayoutElement.h | 2 +- Source/Private/ASCollectionLayout.mm | 15 +++ .../ASCollectionView/Sample/ViewController.m | 123 ++++++++++++------ 7 files changed, 195 insertions(+), 61 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35e4f360bc..c5281189de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ - [ASScrollNode] Invalidate the node's calculated layout if its scrollable directions changed. Also add unit tests for the class. [#637](https://github.com/TextureGroup/Texture/pull/637) [Huy Nguyen](https://github.com/nguyenhuy) - Add new unit testing to the layout engine. [Adlai Holler](https://github.com/Adlai-Holler) [#424](https://github.com/TextureGroup/Texture/pull/424) - [Automatic Subnode Management] Nodes with ASM enabled now insert/delete their subnodes as soon as they enter preload state, so the subnodes can preload too. [Huy Nguyen](https://github.com/nguyenhuy) [#706](https://github.com/TextureGroup/Texture/pull/706) +- [ASCollectionNode] Added support for interactive item movement. [Adlai Holler](https://github.com/Adlai-Holler) ## 2.6 - [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) diff --git a/Source/ASCollectionNode.h b/Source/ASCollectionNode.h index 5d77404582..4a56217cf5 100644 --- a/Source/ASCollectionNode.h +++ b/Source/ASCollectionNode.h @@ -630,6 +630,32 @@ NS_ASSUME_NONNULL_BEGIN */ - (NSArray *)collectionNode:(ASCollectionNode *)collectionNode supplementaryElementKindsInSection:(NSInteger)section; +/** + * Asks the data source if it's possible to move the specified item interactively. + * + * See @p -[UICollectionViewDataSource collectionView:canMoveItemAtIndexPath:] @c. + * + * @param collectionNode The sender. + * @param node The display node for the item that may be moved. + * + * @return Whether the item represented by @p node may be moved. + */ +- (BOOL)collectionNode:(ASCollectionNode *)collectionNode canMoveItemWithNode:(ASCellNode *)node; + +/** + * Called when the user has interactively moved an item. The data source + * should update its internal data store to reflect the move. Note that you + * should not call [collectionNode moveItemAtIndexPath:toIndexPath:] – the + * collection node's internal state will be updated automatically. + * + * * See @p -[UICollectionViewDataSource collectionView:moveItemAtIndexPath:toIndexPath:] @c. + * + * @param collectionNode The sender. + * @param sourceIndexPath The original item index path. + * @param destinationIndexPath The new item index path. + */ +- (void)collectionNode:(ASCollectionNode *)collectionNode moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath; + /** * Similar to -collectionView:cellForItemAtIndexPath:. * diff --git a/Source/ASCollectionView.mm b/Source/ASCollectionView.mm index 4efe9a75d6..fda3ea8036 100644 --- a/Source/ASCollectionView.mm +++ b/Source/ASCollectionView.mm @@ -101,6 +101,10 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; NSHashTable *_cellsForLayoutUpdates; id _layoutFacilitator; CGFloat _leadingScreensForBatching; + + // When we update our data controller in response to an interactive move, + // we don't want to tell the collection view about the change (it knows!) + BOOL _updatingInResponseToInteractiveMove; BOOL _inverted; NSUInteger _superBatchUpdateCount; @@ -218,6 +222,8 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; unsigned int numberOfSectionsInCollectionNode:1; unsigned int collectionNodeNumberOfItemsInSection:1; unsigned int collectionNodeContextForSection:1; + unsigned int collectionNodeCanMoveItem:1; + unsigned int collectionNodeMoveItem:1; // Whether this data source conforms to ASCollectionDataSourceInterop unsigned int interop:1; @@ -454,6 +460,8 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; _asyncDataSourceFlags.collectionNodeNodeBlockForSupplementaryElement = [_asyncDataSource respondsToSelector:@selector(collectionNode:nodeBlockForSupplementaryElementOfKind:atIndexPath:)]; _asyncDataSourceFlags.collectionNodeSupplementaryElementKindsInSection = [_asyncDataSource respondsToSelector:@selector(collectionNode:supplementaryElementKindsInSection:)]; _asyncDataSourceFlags.nodeModelForItem = [_asyncDataSource respondsToSelector:@selector(collectionNode:nodeModelForItemAtIndexPath:)]; + _asyncDataSourceFlags.collectionNodeCanMoveItem = [_asyncDataSource respondsToSelector:@selector(collectionNode:canMoveItemWithNode:)]; + _asyncDataSourceFlags.collectionNodeMoveItem = [_asyncDataSource respondsToSelector:@selector(collectionNode:moveItemAtIndexPath:toIndexPath:)]; _asyncDataSourceFlags.interop = [_asyncDataSource conformsToProtocol:@protocol(ASCollectionDataSourceInterop)]; if (_asyncDataSourceFlags.interop) { @@ -1492,6 +1500,66 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; } } +- (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath +{ + // Mimic UIKit's gating logic. + // If the data source doesn't support moving, then all bets are off. + if (!_asyncDataSourceFlags.collectionNodeMoveItem) { + return NO; + } + + // Currently we do not support interactive moves when using async layout. The reason is, we do not have a mechanism + // to propagate the "presentation data" element map (containing the speculative in-progress moves) to the layout delegate, + // and this can cause exceptions to be thrown from UICV. For example, if you drag an item out of a section, + // the element map will still contain N items in that section, even though there's only N-1 shown, and UICV will + // throw an exception that you specified an element that doesn't exist. + // + // In iOS >= 11, this is made much easier by the UIDataSourceTranslating API. In previous versions of iOS our best bet + // would be to capture the invalidation contexts that are sent during interactive moves and make our own data source translator. + if ([self.collectionViewLayout isKindOfClass:[ASCollectionLayout class]]) { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + as_log_debug(ASCollectionLog(), "Collection node item interactive movement is not supported when using a layout delegate. This message will only be logged once. Node: %@", ASObjectDescriptionMakeTiny(self)); + }); + return NO; + } + + // If the data source implements canMoveItem, let them decide. + if (_asyncDataSourceFlags.collectionNodeCanMoveItem) { + if (auto cellNode = [self nodeForItemAtIndexPath:indexPath]) { + GET_COLLECTIONNODE_OR_RETURN(collectionNode, NO); + return [_asyncDataSource collectionNode:collectionNode canMoveItemWithNode:cellNode]; + } + } + + // Otherwise allow the move for all items. + return YES; +} + +- (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath +{ + ASDisplayNodeAssert(_asyncDataSourceFlags.collectionNodeMoveItem, @"Should not allow interactive collection item movement if data source does not support it."); + + // Inform the data source first, in case they call nodeForItemAtIndexPath:. + // We want to make sure we return them the node for the item they have in mind. + if (auto collectionNode = self.collectionNode) { + [_asyncDataSource collectionNode:collectionNode moveItemAtIndexPath:sourceIndexPath toIndexPath:destinationIndexPath]; + } + + // Now we update our data controller's store. + // Get up to date + [self waitUntilAllUpdatesAreCommitted]; + // Set our flag to suppress informing super about the change. + ASDisplayNodeAssertFalse(_updatingInResponseToInteractiveMove); + _updatingInResponseToInteractiveMove = YES; + // Submit the move + [self moveItemAtIndexPath:sourceIndexPath toIndexPath:destinationIndexPath]; + // Wait for it to finish – should be fast! + [self waitUntilAllUpdatesAreCommitted]; + // Clear the flag + _updatingInResponseToInteractiveMove = NO; +} + - (void)scrollViewDidScroll:(UIScrollView *)scrollView { // If a scroll happenes the current range mode needs to go to full @@ -2023,7 +2091,7 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; - (void)rangeController:(ASRangeController *)rangeController updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet updates:(dispatch_block_t)updates { ASDisplayNodeAssertMainThread(); - if (!self.asyncDataSource || _superIsPendingDataLoad) { + if (!self.asyncDataSource || _superIsPendingDataLoad || _updatingInResponseToInteractiveMove) { updates(); [changeSet executeCompletionHandlerWithFinished:NO]; return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes @@ -2278,21 +2346,4 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; return; } -#if ASDISPLAYNODE_ASSERTIONS_ENABLED // Remove implementations entirely for efficiency if not asserting. - -// intercepted due to not being supported by ASCollectionView (prevent bugs caused by usage) - -- (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(9_0) -{ - ASDisplayNodeAssert(![self.asyncDataSource respondsToSelector:_cmd], @"%@ is not supported by ASCollectionView - please remove or disable this data source method.", NSStringFromSelector(_cmd)); - return NO; -} - -- (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath*)destinationIndexPath NS_AVAILABLE_IOS(9_0) -{ - ASDisplayNodeAssert(![self.asyncDataSource respondsToSelector:_cmd], @"%@ is not supported by ASCollectionView - please remove or disable this data source method.", NSStringFromSelector(_cmd)); -} - -#endif - @end diff --git a/Source/Details/ASDataController.mm b/Source/Details/ASDataController.mm index 51f35d49a4..512b844fa5 100644 --- a/Source/Details/ASDataController.mm +++ b/Source/Details/ASDataController.mm @@ -526,7 +526,7 @@ typedef dispatch_block_t ASDataControllerCompletionBlock; BOOL canDelegate = (self.layoutDelegate != nil); ASElementMap *newMap; - id layoutContext; + ASCollectionLayoutContext *layoutContext; { as_activity_scope(as_activity_create("Latch new data for collection update", changeSet.rootActivity, OS_ACTIVITY_FLAG_DEFAULT)); diff --git a/Source/Layout/ASLayoutElement.h b/Source/Layout/ASLayoutElement.h index ca56c67222..e84c545695 100644 --- a/Source/Layout/ASLayoutElement.h +++ b/Source/Layout/ASLayoutElement.h @@ -192,7 +192,7 @@ extern NSString * const ASLayoutElementStyleLayoutPositionProperty; #pragma mark - Sizing /** - * @abstract The width property specifies the height of the content area of an ASLayoutElement. + * @abstract The width property specifies the width of the content area of an ASLayoutElement. * The minWidth and maxWidth properties override width. * Defaults to ASDimensionAuto */ diff --git a/Source/Private/ASCollectionLayout.mm b/Source/Private/ASCollectionLayout.mm index 59273c975b..eafc445a85 100644 --- a/Source/Private/ASCollectionLayout.mm +++ b/Source/Private/ASCollectionLayout.mm @@ -158,6 +158,21 @@ static const ASScrollDirection kASStaticScrollDirection = (ASScrollDirectionRigh } } +/** + * NOTE: It is suggested practice on the Web to override invalidationContextForInteractivelyMovingItems… and call out to the + * data source to move the item (so that if e.g. the item size depends on the data, you get the data you expect). However, as of iOS 11 this + * doesn't work, because UICV machinery will also call out to the data source to move the item after the interaction is done. The result is + * that your data source state will be incorrect due to this last move call. Plus it's just an API violation. + * + * Things tried: + * - Doing the speculative data source moves, and then UNDOING the last one in invalidationContextForEndingInteractiveMovementOfItems… + * but this does not work because the UICV machinery informs its data source before it calls that method on us, so we are too late. + * + * The correct practice is to use the UIDataSourceTranslating API introduced in iOS 11. Currently Texture does not support this API but we can + * build it if there is demand. We could add an id field onto the layout context object, and the layout client can + * use data source index paths when it reads nodes or other data source data. + */ + - (CGSize)collectionViewContentSize { ASDisplayNodeAssertMainThread(); diff --git a/examples/ASCollectionView/Sample/ViewController.m b/examples/ASCollectionView/Sample/ViewController.m index 3755b01d69..10c974776f 100644 --- a/examples/ASCollectionView/Sample/ViewController.m +++ b/examples/ASCollectionView/Sample/ViewController.m @@ -23,10 +23,13 @@ #define ASYNC_COLLECTION_LAYOUT 0 +static CGSize const kItemSize = (CGSize){180, 90}; + @interface ViewController () @property (nonatomic, strong) ASCollectionNode *collectionNode; -@property (nonatomic, strong) NSArray *data; +@property (nonatomic, strong) NSMutableArray *> *data; +@property (nonatomic, strong) UILongPressGestureRecognizer *moveRecognizer; @end @@ -34,18 +37,13 @@ #pragma mark - Lifecycle -- (void)dealloc -{ - self.collectionNode.dataSource = nil; - self.collectionNode.delegate = nil; - - NSLog(@"ViewController is deallocing"); -} - - (void)viewDidLoad { [super viewDidLoad]; + self.moveRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress)]; + [self.view addGestureRecognizer:self.moveRecognizer]; + #if ASYNC_COLLECTION_LAYOUT ASCollectionGalleryLayoutDelegate *layoutDelegate = [[ASCollectionGalleryLayoutDelegate alloc] initWithScrollableDirections:ASScrollDirectionVerticalDirections]; layoutDelegate.propertiesProvider = self; @@ -54,6 +52,7 @@ UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; layout.headerReferenceSize = CGSizeMake(50.0, 50.0); layout.footerReferenceSize = CGSizeMake(50.0, 50.0); + layout.itemSize = kItemSize; self.collectionNode = [[ASCollectionNode alloc] initWithFrame:self.view.bounds collectionViewLayout:layout]; [self.collectionNode registerSupplementaryNodeOfKind:UICollectionElementKindSectionHeader]; [self.collectionNode registerSupplementaryNodeOfKind:UICollectionElementKindSectionFooter]; @@ -73,34 +72,37 @@ self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh target:self action:@selector(reloadTapped)]; -#endif - -#if SIMULATE_WEB_RESPONSE + [self loadData]; +#else __weak typeof(self) weakSelf = self; - void(^mockWebService)() = ^{ - NSLog(@"ViewController \"got data from a web service\""); - ViewController *strongSelf = weakSelf; - if (strongSelf != nil) - { - NSLog(@"ViewController is not nil"); - strongSelf->_data = [[NSArray alloc] init]; - [strongSelf->_collectionNode performBatchUpdates:^{ - [strongSelf->_collectionNode insertSections:[[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, 100)]]; - } completion:nil]; - NSLog(@"ViewController finished updating collectionNode"); - } - else { - NSLog(@"ViewController is nil - won't update collectionNode"); - } - }; - - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), mockWebService); - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - [self.navigationController popViewControllerAnimated:YES]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [weakSelf handleSimulatedWebResponse]; }); #endif } +- (void)handleSimulatedWebResponse +{ + [self.collectionNode performBatchUpdates:^{ + [self loadData]; + [self.collectionNode insertSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, self.data.count)]]; + } completion:nil]; +} + +- (void)loadData +{ + // Form our data array + typeof(self.data) data = [NSMutableArray array]; + for (NSInteger s = 0; s < 100; s++) { + NSMutableArray *items = [NSMutableArray array]; + for (NSInteger i = 0; i < 10; i++) { + items[i] = [NSString stringWithFormat:@"[%zd.%zd] says hi", s, i]; + } + data[s] = items; + } + self.data = data; +} + #pragma mark - Button Actions - (void)reloadTapped @@ -115,14 +117,42 @@ - (CGSize)galleryLayoutDelegate:(ASCollectionGalleryLayoutDelegate *)delegate sizeForElements:(ASElementMap *)elements { ASDisplayNodeAssertMainThread(); - return CGSizeMake(180, 90); + return kItemSize; } -#pragma mark - ASCollectionView Data Source +- (void)handleLongPress +{ + UICollectionView *collectionView = self.collectionNode.view; + CGPoint location = [self.moveRecognizer locationInView:collectionView]; + switch (self.moveRecognizer.state) { + case UIGestureRecognizerStateBegan: { + NSIndexPath *indexPath = [collectionView indexPathForItemAtPoint:location]; + if (indexPath) { + [collectionView beginInteractiveMovementForItemAtIndexPath:indexPath]; + } + break; + } + case UIGestureRecognizerStateChanged: + [collectionView updateInteractiveMovementTargetPosition:location]; + break; + case UIGestureRecognizerStateEnded: + [collectionView endInteractiveMovement]; + break; + case UIGestureRecognizerStateFailed: + case UIGestureRecognizerStateCancelled: + [collectionView cancelInteractiveMovement]; + break; + case UIGestureRecognizerStatePossible: + // nop + break; + } +} + +#pragma mark - ASCollectionDataSource - (ASCellNodeBlock)collectionNode:(ASCollectionNode *)collectionNode nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath; { - NSString *text = [NSString stringWithFormat:@"[%zd.%zd] says hi", indexPath.section, indexPath.item]; + NSString *text = self.data[indexPath.section][indexPath.item]; return ^{ return [[ItemNode alloc] initWithString:text]; }; @@ -139,18 +169,29 @@ - (NSInteger)collectionNode:(ASCollectionNode *)collectionNode numberOfItemsInSection:(NSInteger)section { - return 10; + return self.data[section].count; } - (NSInteger)numberOfSectionsInCollectionNode:(ASCollectionNode *)collectionNode { -#if SIMULATE_WEB_RESPONSE - return _data == nil ? 0 : 100; -#else - return 100; -#endif + return self.data.count; } +- (BOOL)collectionNode:(ASCollectionNode *)collectionNode canMoveItemWithNode:(ASCellNode *)node +{ + return YES; +} + +- (void)collectionNode:(ASCollectionNode *)collectionNode moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath +{ + __auto_type sectionArray = self.data[sourceIndexPath.section]; + __auto_type object = sectionArray[sourceIndexPath.item]; + [sectionArray removeObjectAtIndex:sourceIndexPath.item]; + [self.data[destinationIndexPath.section] insertObject:object atIndex:destinationIndexPath.item]; +} + +#pragma mark - ASCollectionDelegate + - (void)collectionNode:(ASCollectionNode *)collectionNode willBeginBatchFetchWithContext:(ASBatchContext *)context { NSLog(@"fetch additional content"); From 1dfdb484971dd268f0c740cb3788ba61863d86b1 Mon Sep 17 00:00:00 2001 From: Sudhanshu Date: Wed, 10 Jan 2018 23:45:53 +0530 Subject: [PATCH 02/40] Add MensXP to Showcase (#739) This change is for adding "MensXP: Fashion, Grooming tips" iOS App to Texture showcase. --- docs/showcase.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/showcase.md b/docs/showcase.md index 4b3e68233e..95b72f3317 100755 --- a/docs/showcase.md +++ b/docs/showcase.md @@ -229,6 +229,16 @@ permalink: /showcase.html + + + + +
+ MensXP + + + +
From 3708f2e44848b71f6986b5bc034825d09832b062 Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Fri, 12 Jan 2018 12:56:19 -0800 Subject: [PATCH 03/40] Update CHANGELOG for 2.6 --- CHANGELOG.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5281189de..4b3196be30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,15 +4,6 @@ - [ASCollectionView] Add missing flags for ASCollectionDelegate [Ilya Zheleznikov](https://github.com/ilyailya) [#718](https://github.com/TextureGroup/Texture/pull/718) - [ASNetworkImageNode] Deprecates .URLs in favor of .URL [Garrett Moon](https://github.com/garrettmoon) [#699](https://github.com/TextureGroup/Texture/pull/699) - [iOS11] Update project settings and fix errors [Eke](https://github.com/Eke) [#676](https://github.com/TextureGroup/Texture/pull/676) -- [ASCollectionView] Improve performance and behavior of rotation / bounds changes. [Scott Goodson](https://github.com/appleguy) [#431](https://github.com/TextureGroup/Texture/pull/431) -- [ASCollectionView] Improve index space translation of Flow Layout Delegate methods. [Scott Goodson](https://github.com/appleguy) -- [Animated Image] Adds support for animated WebP as well as improves GIF handling. [#605](https://github.com/TextureGroup/Texture/pull/605) [Garrett Moon](https://github.com/garrettmoon) -- [ASCollectionView] Check if batch fetching is needed if batch fetching parameter has been changed. [#624](https://github.com/TextureGroup/Texture/pull/624) [Garrett Moon](https://github.com/garrettmoon) -- [ASNetworkImageNode] New delegate callback to tell the consumer whether the image was loaded from cache or download. [Adlai Holler](https://github.com/Adlai-Holler) -- [Layout] Fixes a deadlock in layout. [#638](https://github.com/TextureGroup/Texture/pull/638) [Garrett Moon](https://github.com/garrettmoon) -- Updated to be backwards compatible with Xcode 8. [Adlai Holler](https://github.com/Adlai-Holler) -- [API CHANGES] `ASPerformMainThreadDeallocation` and `ASPerformBackgroundDeallocation` functions take `id *` instead of `id` and they're now more reliable. Also, in Swift, `ASDeallocQueue.sharedDeallocationQueue() -> ASDeallocQueue.sharedDeallocationQueue`. [Adlai Holler](https://github.com/Adlai-Holler) [#651](https://github.com/TextureGroup/Texture/pull/651) -- [Collection/Table] Added direct support for mapping section indexes between data spaces. [Adlai Holler](https://github.com/Adlai-Holler) [#651](https://github.com/TextureGroup/Texture/pull/660) - [ASCornerLayoutSpec] New layout spec class for declarative corner element layout. [#657](https://github.com/TextureGroup/Texture/pull/657) [huangkun](https://github.com/huang-kun) - [Layout] Fix an issue that causes a pending layout to be applied multiple times. [Huy Nguyen](https://github.com/nguyenhuy) [#695](https://github.com/TextureGroup/Texture/pull/695) - [ASScrollNode] Ensure the node respects the given size range while calculating its layout. [#637](https://github.com/TextureGroup/Texture/pull/637) [Huy Nguyen](https://github.com/nguyenhuy) @@ -23,6 +14,15 @@ ## 2.6 - [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) +- [ASCollectionView] Improve performance and behavior of rotation / bounds changes. [Scott Goodson](https://github.com/appleguy) [#431](https://github.com/TextureGroup/Texture/pull/431) +- [ASCollectionView] Improve index space translation of Flow Layout Delegate methods. [Scott Goodson](https://github.com/appleguy) +- [Animated Image] Adds support for animated WebP as well as improves GIF handling. [#605](https://github.com/TextureGroup/Texture/pull/605) [Garrett Moon](https://github.com/garrettmoon) +- [ASCollectionView] Check if batch fetching is needed if batch fetching parameter has been changed. [#624](https://github.com/TextureGroup/Texture/pull/624) [Garrett Moon](https://github.com/garrettmoon) +- [ASNetworkImageNode] New delegate callback to tell the consumer whether the image was loaded from cache or download. [Adlai Holler](https://github.com/Adlai-Holler) +- [Layout] Fixes a deadlock in layout. [#638](https://github.com/TextureGroup/Texture/pull/638) [Garrett Moon](https://github.com/garrettmoon) +- Updated to be backwards compatible with Xcode 8. [Adlai Holler](https://github.com/Adlai-Holler) +- [API CHANGES] `ASPerformMainThreadDeallocation` and `ASPerformBackgroundDeallocation` functions take `id *` instead of `id` and they're now more reliable. Also, in Swift, `ASDeallocQueue.sharedDeallocationQueue() -> ASDeallocQueue.sharedDeallocationQueue`. [Adlai Holler](https://github.com/Adlai-Holler) [#651](https://github.com/TextureGroup/Texture/pull/651) +- [Collection/Table] Added direct support for mapping section indexes between data spaces. [Adlai Holler](https://github.com/Adlai-Holler) [#651](https://github.com/TextureGroup/Texture/pull/660) ## 2.5.1 - [ASVideoNode] Fix unreleased time observer. [Flo Vouin](https://github.com/flovouin) From 1d105c205623a53888743fdb7f8575cf03a225dc Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Sat, 13 Jan 2018 19:19:08 -0800 Subject: [PATCH 04/40] Add an experimental "no-copy" renderer (#741) * Add "ASGraphicsContext" to skip copying our rendered images * Zero the buffer before making a context * Update license header * Update dangerfile * Make it a runtime flag * Restore GState for good measure * Free buffer if end without image * Enable the experiment, and cut out the middle-man * Fix typo --- AsyncDisplayKit.xcodeproj/project.pbxproj | 8 + CHANGELOG.md | 1 + Dangerfile | 2 +- Source/ASDisplayNode.mm | 7 +- Source/ASImageNode.mm | 25 ++- Source/ASMapNode.mm | 6 +- Source/ASTextNode.mm | 6 +- Source/AsyncDisplayKit.h | 1 + Source/Debug/AsyncDisplayKit+Debug.m | 6 +- Source/Details/ASGraphicsContext.h | 62 +++++++ Source/Details/ASGraphicsContext.m | 160 +++++++++++++++++++ Source/Private/ASDisplayNode+AsyncDisplay.mm | 22 ++- Source/UIImage+ASConvenience.m | 6 +- examples/ASDKgram/Sample/AppDelegate.m | 4 + 14 files changed, 272 insertions(+), 44 deletions(-) create mode 100644 Source/Details/ASGraphicsContext.h create mode 100644 Source/Details/ASGraphicsContext.m diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 3a4b1c5342..f3ed289f17 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -402,6 +402,8 @@ CCCCCCE41EC3EF060087FE10 /* NSParagraphStyle+ASText.m in Sources */ = {isa = PBXBuildFile; fileRef = CCCCCCD41EC3EF060087FE10 /* NSParagraphStyle+ASText.m */; }; CCCCCCE71EC3F0FC0087FE10 /* NSAttributedString+ASText.h in Headers */ = {isa = PBXBuildFile; fileRef = CCCCCCE51EC3F0FC0087FE10 /* NSAttributedString+ASText.h */; }; CCCCCCE81EC3F0FC0087FE10 /* NSAttributedString+ASText.m in Sources */ = {isa = PBXBuildFile; fileRef = CCCCCCE61EC3F0FC0087FE10 /* NSAttributedString+ASText.m */; }; + CCDC9B4D200991D10063C1F8 /* ASGraphicsContext.h in Headers */ = {isa = PBXBuildFile; fileRef = CCDC9B4B200991D10063C1F8 /* ASGraphicsContext.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CCDC9B4E200991D10063C1F8 /* ASGraphicsContext.m in Sources */ = {isa = PBXBuildFile; fileRef = CCDC9B4C200991D10063C1F8 /* ASGraphicsContext.m */; }; CCDD148B1EEDCD9D0020834E /* ASCollectionModernDataSourceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CCDD148A1EEDCD9D0020834E /* ASCollectionModernDataSourceTests.m */; }; CCE4F9B31F0D60AC00062E4E /* ASIntegerMapTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CCE4F9B21F0D60AC00062E4E /* ASIntegerMapTests.m */; }; CCE4F9B51F0DA4F300062E4E /* ASLayoutEngineTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCE4F9B41F0DA4F300062E4E /* ASLayoutEngineTests.mm */; }; @@ -895,6 +897,8 @@ CCCCCCD41EC3EF060087FE10 /* NSParagraphStyle+ASText.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSParagraphStyle+ASText.m"; sourceTree = ""; }; CCCCCCE51EC3F0FC0087FE10 /* NSAttributedString+ASText.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSAttributedString+ASText.h"; sourceTree = ""; }; CCCCCCE61EC3F0FC0087FE10 /* NSAttributedString+ASText.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSAttributedString+ASText.m"; sourceTree = ""; }; + CCDC9B4B200991D10063C1F8 /* ASGraphicsContext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASGraphicsContext.h; sourceTree = ""; }; + CCDC9B4C200991D10063C1F8 /* ASGraphicsContext.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASGraphicsContext.m; sourceTree = ""; }; CCDD148A1EEDCD9D0020834E /* ASCollectionModernDataSourceTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionModernDataSourceTests.m; sourceTree = ""; }; CCE04B1E1E313EA7006AEBBB /* ASSectionController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASSectionController.h; sourceTree = ""; }; CCE04B201E313EB9006AEBBB /* IGListAdapter+AsyncDisplayKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "IGListAdapter+AsyncDisplayKit.h"; sourceTree = ""; }; @@ -1270,6 +1274,8 @@ 058D09E1195D050800B7D73C /* Details */ = { isa = PBXGroup; children = ( + CCDC9B4B200991D10063C1F8 /* ASGraphicsContext.h */, + CCDC9B4C200991D10063C1F8 /* ASGraphicsContext.m */, CC5601391F06E9A700DC4FBE /* ASIntegerMap.h */, CC56013A1F06E9A700DC4FBE /* ASIntegerMap.mm */, CC0F885E1E4280B800576FED /* _ASCollectionViewCell.h */, @@ -1836,6 +1842,7 @@ 68EE0DBE1C1B4ED300BA1B99 /* ASMainSerialQueue.h in Headers */, CCCCCCE11EC3EF060087FE10 /* ASTextUtilities.h in Headers */, B350624B1B010EFD0018CF92 /* _ASPendingState.h in Headers */, + CCDC9B4D200991D10063C1F8 /* ASGraphicsContext.h in Headers */, E5C347B11ECB3D9200EC4BE4 /* ASBatchFetchingDelegate.h in Headers */, CC54A81C1D70079800296A24 /* ASDispatch.h in Headers */, B350624D1B010EFD0018CF92 /* _ASScopeTimer.h in Headers */, @@ -2268,6 +2275,7 @@ E5B078001E69F4EB00C24B5B /* ASElementMap.m in Sources */, 9C8898BC1C738BA800D6B02E /* ASTextKitFontSizeAdjuster.mm in Sources */, 690ED59B1E36D118000627C0 /* ASImageNode+tvOS.m in Sources */, + CCDC9B4E200991D10063C1F8 /* ASGraphicsContext.m in Sources */, CCCCCCD81EC3EF060087FE10 /* ASTextInput.m in Sources */, 34EFC7621B701CA400AD841F /* ASBackgroundLayoutSpec.mm in Sources */, DE8BEAC41C2DF3FC00D57C12 /* ASDelegateProxy.m in Sources */, diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b3196be30..02331367f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - Add new unit testing to the layout engine. [Adlai Holler](https://github.com/Adlai-Holler) [#424](https://github.com/TextureGroup/Texture/pull/424) - [Automatic Subnode Management] Nodes with ASM enabled now insert/delete their subnodes as soon as they enter preload state, so the subnodes can preload too. [Huy Nguyen](https://github.com/nguyenhuy) [#706](https://github.com/TextureGroup/Texture/pull/706) - [ASCollectionNode] Added support for interactive item movement. [Adlai Holler](https://github.com/Adlai-Holler) +- Added an experimental "no-copy" rendering API. See ASGraphicsContext.h for info. [Adlai Holler](https://github.com/Adlai-Holler) ## 2.6 - [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) diff --git a/Dangerfile b/Dangerfile index edd455d53d..3b81695827 100644 --- a/Dangerfile +++ b/Dangerfile @@ -67,7 +67,7 @@ end # Ensure new files have proper header new_source_license_header = <<-HEREDOC -// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. +// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index 3a537ca4bb..0f2bf77194 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -36,6 +36,7 @@ #import #import #import +#import #import #import #import @@ -1507,7 +1508,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) BOOL isRight = (idx == 1 || idx == 2); CGSize size = CGSizeMake(radius + 1, radius + 1); - UIGraphicsBeginImageContextWithOptions(size, NO, self.contentsScaleForDisplay); + ASGraphicsBeginImageContextWithOptions(size, NO, self.contentsScaleForDisplay); CGContextRef ctx = UIGraphicsGetCurrentContext(); if (isRight == YES) { @@ -1524,11 +1525,9 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) // No lock needed, as _clipCornerLayers is only modified on the main thread. CALayer *clipCornerLayer = _clipCornerLayers[idx]; - clipCornerLayer.contents = (id)(UIGraphicsGetImageFromCurrentImageContext().CGImage); + clipCornerLayer.contents = (id)(ASGraphicsGetImageAndEndCurrentContext().CGImage); clipCornerLayer.bounds = CGRectMake(0.0, 0.0, size.width, size.height); clipCornerLayer.anchorPoint = CGPointMake(isRight ? 1.0 : 0.0, isTop ? 1.0 : 0.0); - - UIGraphicsEndImageContext(); } [self _layoutClipCornersIfNeeded]; }); diff --git a/Source/ASImageNode.mm b/Source/ASImageNode.mm index 5e2383081f..ed56c9ceb8 100644 --- a/Source/ASImageNode.mm +++ b/Source/ASImageNode.mm @@ -25,6 +25,7 @@ #import #import #import +#import #import #import #import @@ -213,11 +214,10 @@ typedef void (^ASImageNodeDrawParametersBlock)(ASWeakMapEntry *entry); ASDN::MutexLocker l(__instanceLock__); - UIGraphicsBeginImageContext(size); + ASGraphicsBeginImageContextWithOptions(size, NO, 1); [self.placeholderColor setFill]; UIRectFill(CGRectMake(0, 0, size.width, size.height)); - UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); + UIImage *image = ASGraphicsGetImageAndEndCurrentContext(); return image; } @@ -472,7 +472,7 @@ static ASDN::StaticMutex& cacheLock = *new ASDN::StaticMutex; + (UIImage *)createContentsForkey:(ASImageNodeContentsKey *)key drawParameters:(id)drawParameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled { - // The following `UIGraphicsBeginImageContextWithOptions` call will sometimes take take longer than 5ms on an + // The following `ASGraphicsBeginImageContextWithOptions` call will sometimes take take longer than 5ms on an // A5 processor for a 400x800 backingSize. // Check for cancellation before we call it. if (isCancelled()) { @@ -481,7 +481,7 @@ static ASDN::StaticMutex& cacheLock = *new ASDN::StaticMutex; // Use contentsScale of 1.0 and do the contentsScale handling in boundsSizeInPixels so ASCroppedImageBackingSizeAndDrawRectInBounds // will do its rounding on pixel instead of point boundaries - UIGraphicsBeginImageContextWithOptions(key.backingSize, key.isOpaque, 1.0); + ASGraphicsBeginImageContextWithOptions(key.backingSize, key.isOpaque, 1.0); BOOL contextIsClean = YES; @@ -529,9 +529,7 @@ static ASDN::StaticMutex& cacheLock = *new ASDN::StaticMutex; return nil; } - UIImage *result = UIGraphicsGetImageFromCurrentImageContext(); - - UIGraphicsEndImageContext(); + UIImage *result = ASGraphicsGetImageAndEndCurrentContext(); if (key.imageModificationBlock) { result = key.imageModificationBlock(result); @@ -742,7 +740,7 @@ static ASDN::StaticMutex& cacheLock = *new ASDN::StaticMutex; extern asimagenode_modification_block_t ASImageNodeRoundBorderModificationBlock(CGFloat borderWidth, UIColor *borderColor) { return ^(UIImage *originalImage) { - UIGraphicsBeginImageContextWithOptions(originalImage.size, NO, originalImage.scale); + ASGraphicsBeginImageContextWithOptions(originalImage.size, NO, originalImage.scale); UIBezierPath *roundOutline = [UIBezierPath bezierPathWithOvalInRect:(CGRect){CGPointZero, originalImage.size}]; // Make the image round @@ -758,24 +756,21 @@ extern asimagenode_modification_block_t ASImageNodeRoundBorderModificationBlock( [roundOutline stroke]; } - UIImage *modifiedImage = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - return modifiedImage; + return ASGraphicsGetImageAndEndCurrentContext(); }; } extern asimagenode_modification_block_t ASImageNodeTintColorModificationBlock(UIColor *color) { return ^(UIImage *originalImage) { - UIGraphicsBeginImageContextWithOptions(originalImage.size, NO, originalImage.scale); + ASGraphicsBeginImageContextWithOptions(originalImage.size, NO, originalImage.scale); // Set color and render template [color setFill]; UIImage *templateImage = [originalImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; [templateImage drawAtPoint:CGPointZero blendMode:kCGBlendModeCopy alpha:1]; - UIImage *modifiedImage = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); + UIImage *modifiedImage = ASGraphicsGetImageAndEndCurrentContext(); // if the original image was stretchy, keep it stretchy if (!UIEdgeInsetsEqualToEdgeInsets(originalImage.capInsets, UIEdgeInsetsZero)) { diff --git a/Source/ASMapNode.mm b/Source/ASMapNode.mm index a9394a4d70..4697a2c057 100644 --- a/Source/ASMapNode.mm +++ b/Source/ASMapNode.mm @@ -24,6 +24,7 @@ #import #import +#import #import #import #import @@ -222,7 +223,7 @@ CGRect finalImageRect = CGRectMake(0, 0, image.size.width, image.size.height); - UIGraphicsBeginImageContextWithOptions(image.size, YES, image.scale); + ASGraphicsBeginImageContextWithOptions(image.size, YES, image.scale); [image drawAtPoint:CGPointZero]; UIImage *pinImage; @@ -254,8 +255,7 @@ } } - image = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); + image = ASGraphicsGetImageAndEndCurrentContext(); } strongSelf.image = image; diff --git a/Source/ASTextNode.mm b/Source/ASTextNode.mm index e5338f5217..c3343528a3 100644 --- a/Source/ASTextNode.mm +++ b/Source/ASTextNode.mm @@ -28,6 +28,7 @@ #import #import #import +#import #import #import @@ -907,7 +908,7 @@ static CGRect ASTextNodeAdjustRenderRectForShadowPadding(CGRect rendererRect, UI ASDN::MutexLocker l(__instanceLock__); - UIGraphicsBeginImageContext(size); + ASGraphicsBeginImageContextWithOptions(size, NO, 1.0); [self.placeholderColor setFill]; ASTextKitRenderer *renderer = [self _locked_renderer]; @@ -926,8 +927,7 @@ static CGRect ASTextNodeAdjustRenderRectForShadowPadding(CGRect rendererRect, UI } } - UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); + UIImage *image = ASGraphicsGetImageAndEndCurrentContext(); return image; } diff --git a/Source/AsyncDisplayKit.h b/Source/AsyncDisplayKit.h index c20f5077a1..a3935ec21e 100644 --- a/Source/AsyncDisplayKit.h +++ b/Source/AsyncDisplayKit.h @@ -119,6 +119,7 @@ #import #import #import +#import #import #import #import diff --git a/Source/Debug/AsyncDisplayKit+Debug.m b/Source/Debug/AsyncDisplayKit+Debug.m index 20d67cd703..3225700e06 100644 --- a/Source/Debug/AsyncDisplayKit+Debug.m +++ b/Source/Debug/AsyncDisplayKit+Debug.m @@ -17,6 +17,7 @@ #import #import +#import #import #import #import @@ -148,7 +149,7 @@ static BOOL __enableHitTestDebug = NO; UIColor *clipsBorderColor = [UIColor colorWithRed:30/255.0 green:90/255.0 blue:50/255.0 alpha:0.7]; CGRect imgRect = CGRectMake(0, 0, 2.0 * borderWidth + 1.0, 2.0 * borderWidth + 1.0); - UIGraphicsBeginImageContext(imgRect.size); + ASGraphicsBeginImageContextWithOptions(imgRect.size, NO, 1); [fillColor setFill]; UIRectFill(imgRect); @@ -156,8 +157,7 @@ static BOOL __enableHitTestDebug = NO; [self drawEdgeIfClippedWithEdges:clippedEdges color:clipsBorderColor borderWidth:borderWidth imgRect:imgRect]; [self drawEdgeIfClippedWithEdges:clipsToBoundsClippedEdges color:borderColor borderWidth:borderWidth imgRect:imgRect]; - UIImage *debugHighlightImage = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); + UIImage *debugHighlightImage = ASGraphicsGetImageAndEndCurrentContext(); UIEdgeInsets edgeInsets = UIEdgeInsetsMake(borderWidth, borderWidth, borderWidth, borderWidth); debugOverlay.image = [debugHighlightImage resizableImageWithCapInsets:edgeInsets resizingMode:UIImageResizingModeStretch]; diff --git a/Source/Details/ASGraphicsContext.h b/Source/Details/ASGraphicsContext.h new file mode 100644 index 0000000000..0a1c80fc4b --- /dev/null +++ b/Source/Details/ASGraphicsContext.h @@ -0,0 +1,62 @@ +// +// ASGraphicsContext.h +// Texture +// +// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import +#import +#import + +@class UIImage; + +/** + * Functions for creating one-shot graphics contexts that do not have to copy + * their contents when an image is generated from them. This is efficient + * for our use, since we do not reuse graphics contexts. + * + * The API mirrors the UIGraphics API, with the exception that forming an image + * ends the context as well. + */ + +NS_ASSUME_NONNULL_BEGIN +ASDISPLAYNODE_EXTERN_C_BEGIN + +/** + * Call this to enable the experimental no-copy rendering. + * + * Returns YES if it was enabled, or NO + assert if it's too late because + * rendering has already started. In practice it's fine to call this + * during -didFinishLaunchingWithOptions:. + */ +extern BOOL ASEnableNoCopyRendering(void); + +/** + * Creates a one-shot context. + * + * Behavior is the same as UIGraphicsBeginImageContextWithOptions. + */ +extern void ASGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale); + +/** + * Generates and image and ends the current one-shot context. + * + * Behavior is the same as UIGraphicsGetImageFromCurrentImageContext followed by UIGraphicsEndImageContext. + */ +extern UIImage * _Nullable ASGraphicsGetImageAndEndCurrentContext(void); + +/** + * Call this if you want to end the current context without making an image. + * + * Behavior is the same as UIGraphicsEndImageContext. + */ +extern void ASGraphicsEndImageContext(void); + +ASDISPLAYNODE_EXTERN_C_END +NS_ASSUME_NONNULL_END diff --git a/Source/Details/ASGraphicsContext.m b/Source/Details/ASGraphicsContext.m new file mode 100644 index 0000000000..1c3092598a --- /dev/null +++ b/Source/Details/ASGraphicsContext.m @@ -0,0 +1,160 @@ +// +// ASGraphicsContext.m +// Texture +// +// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import "ASGraphicsContext.h" +#import +#import +#import +#import + +#pragma mark - Feature Gating + +// Two flags that we atomically manipulate to control the feature. +typedef NS_OPTIONS(uint, ASNoCopyFlags) { + ASNoCopyEnabled = 1 << 0, + ASNoCopyBlocked = 1 << 1 +}; +static atomic_uint __noCopyFlags; + +// Check if it's blocked, and set the enabled flag if not. +extern BOOL ASEnableNoCopyRendering() +{ + ASNoCopyFlags expectedFlags = 0; + BOOL enabled = atomic_compare_exchange_strong(&__noCopyFlags, &expectedFlags, ASNoCopyEnabled); + ASDisplayNodeCAssert(enabled, @"Can't enable no-copy rendering after first render started."); + return enabled; +} + +// Check if it's enabled and set the "blocked" flag either way. +static BOOL ASNoCopyRenderingBlockAndCheckEnabled() { + ASNoCopyFlags oldFlags = atomic_fetch_or(&__noCopyFlags, ASNoCopyBlocked); + return (oldFlags & ASNoCopyEnabled) != 0; +} + +#pragma mark - Callbacks + +void _ASReleaseCGDataProviderData(__unused void *info, const void *data, __unused size_t size) +{ + free((void *)data); +} + +#pragma mark - Graphics Contexts + +extern void ASGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale) +{ + if (!ASNoCopyRenderingBlockAndCheckEnabled()) { + UIGraphicsBeginImageContextWithOptions(size, opaque, scale); + return; + } + + // Only create device RGB color space once. UIGraphics actually doesn't do this but it's safe. + static dispatch_once_t onceToken; + static CGFloat defaultScale; + static CGColorSpaceRef deviceRGB; + dispatch_once(&onceToken, ^{ + deviceRGB = CGColorSpaceCreateDeviceRGB(); + UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), NO, 0); + CGContextRef uikitContext = UIGraphicsGetCurrentContext(); + defaultScale = CGContextGetCTM(uikitContext).a; + UIGraphicsEndImageContext(); + }); + + // These options are taken from UIGraphicsBeginImageContext. + CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host | (opaque ? kCGImageAlphaNoneSkipFirst : kCGImageAlphaPremultipliedFirst); + + if (scale == 0) { + scale = defaultScale; + } + size_t intWidth = (size_t)ceil(size.width * scale); + size_t intHeight = (size_t)ceil(size.height * scale); + size_t bytesPerPixel = 4; + size_t bytesPerRow = bytesPerPixel * intWidth; + size_t bufferSize = bytesPerRow * intHeight; + + // We create our own buffer, and wrap the context around that. This way we can prevent + // the copy that usually gets made when you form a CGImage from the context. + void *buf = calloc(bufferSize, 1); + CGContextRef context = CGBitmapContextCreate(buf, intWidth, intHeight, 8, bytesPerRow, deviceRGB, bitmapInfo); + + // Set the CTM to account for iOS orientation & specified scale. + // If only we could use CGContextSetBaseCTM. It doesn't + // seem like there are any consequences for our use case + // but we'll be on the look out. The internet hinted that it + // affects shadowing but I tested and shadowing works. + CGContextTranslateCTM(context, 0, intHeight); + CGContextScaleCTM(context, scale, -scale); + + // Save the state so we can restore it and recover our scale in GetImageAndEnd + CGContextSaveGState(context); + + UIGraphicsPushContext(context); +} + +extern UIImage * _Nullable ASGraphicsGetImageAndEndCurrentContext() +{ + if (!ASNoCopyRenderingBlockAndCheckEnabled()) { + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return image; + } + + // Pop the context and make sure we have one. + CGContextRef context = UIGraphicsGetCurrentContext(); + if (context == NULL) { + ASDisplayNodeCFailAssert(@"Can't end image context without having begun one."); + return nil; + } + UIGraphicsPopContext(); + + // Do some math to get the image properties. + size_t width = CGBitmapContextGetWidth(context); + size_t height = CGBitmapContextGetHeight(context); + size_t bitsPerPixel = CGBitmapContextGetBitsPerPixel(context); + size_t bytesPerRow = CGBitmapContextGetBytesPerRow(context); + size_t bufferSize = bytesPerRow * height; + + // This is the buf that we malloc'd above. + void *buf = CGBitmapContextGetData(context); + + // Wrap it in a CGDataProvider, passing along our release callback for when the CGImage dies. + CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, buf, bufferSize, _ASReleaseCGDataProviderData); + + // Create the CGImage. Options taken from CGBitmapContextCreateImage. + CGImageRef cgImg = CGImageCreate(width, height, CGBitmapContextGetBitsPerComponent(context), bitsPerPixel, bytesPerRow, CGBitmapContextGetColorSpace(context), CGBitmapContextGetBitmapInfo(context), provider, NULL, true, kCGRenderingIntentDefault); + CGDataProviderRelease(provider); + + // We saved our GState right after setting the CTM so that we could restore it + // here and get the original scale back. + CGContextRestoreGState(context); + CGFloat scale = CGContextGetCTM(context).a; + CGContextRelease(context); + + UIImage *result = [[UIImage alloc] initWithCGImage:cgImg scale:scale orientation:UIImageOrientationUp]; + CGImageRelease(cgImg); + return result; +} + +extern void ASGraphicsEndImageContext() +{ + if (!ASNoCopyRenderingBlockAndCheckEnabled()) { + UIGraphicsEndImageContext(); + return; + } + + CGContextRef context = UIGraphicsGetCurrentContext(); + if (context) { + // We manually allocated this buffer so we need to free it. + free(CGBitmapContextGetData(context)); + CGContextRelease(context); + UIGraphicsPopContext(); + } +} diff --git a/Source/Private/ASDisplayNode+AsyncDisplay.mm b/Source/Private/ASDisplayNode+AsyncDisplay.mm index 986f3acf99..96426d479a 100644 --- a/Source/Private/ASDisplayNode+AsyncDisplay.mm +++ b/Source/Private/ASDisplayNode+AsyncDisplay.mm @@ -21,6 +21,7 @@ #import #import #import +#import #import #import #import @@ -218,15 +219,14 @@ displayBlock = ^id{ CHECK_CANCELLED_AND_RETURN_NIL(); - UIGraphicsBeginImageContextWithOptions(bounds.size, opaque, contentsScaleForDisplay); + ASGraphicsBeginImageContextWithOptions(bounds.size, opaque, contentsScaleForDisplay); for (dispatch_block_t block in displayBlocks) { - CHECK_CANCELLED_AND_RETURN_NIL(UIGraphicsEndImageContext()); + CHECK_CANCELLED_AND_RETURN_NIL(ASGraphicsEndImageContext()); block(); } - UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); + UIImage *image = ASGraphicsGetImageAndEndCurrentContext(); ASDN_DELAY_FOR_DISPLAY(); return image; @@ -236,8 +236,8 @@ CHECK_CANCELLED_AND_RETURN_NIL(); if (shouldCreateGraphicsContext) { - UIGraphicsBeginImageContextWithOptions(bounds.size, opaque, contentsScaleForDisplay); - CHECK_CANCELLED_AND_RETURN_NIL( UIGraphicsEndImageContext(); ); + ASGraphicsBeginImageContextWithOptions(bounds.size, opaque, contentsScaleForDisplay); + CHECK_CANCELLED_AND_RETURN_NIL( ASGraphicsEndImageContext(); ); } CGContextRef currentContext = UIGraphicsGetCurrentContext(); @@ -256,9 +256,8 @@ [self __didDisplayNodeContentWithRenderingContext:currentContext image:&image drawParameters:drawParameters backgroundColor:backgroundColor borderWidth:borderWidth borderColor:borderColor]; if (shouldCreateGraphicsContext) { - CHECK_CANCELLED_AND_RETURN_NIL( UIGraphicsEndImageContext(); ); - image = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); + CHECK_CANCELLED_AND_RETURN_NIL( ASGraphicsEndImageContext(); ); + image = ASGraphicsGetImageAndEndCurrentContext(); } ASDN_DELAY_FOR_DISPLAY(); @@ -332,7 +331,7 @@ bounds.size.height *= contentsScale; CGFloat white = 0.0f, alpha = 0.0f; [backgroundColor getWhite:&white alpha:&alpha]; - UIGraphicsBeginImageContextWithOptions(bounds.size, (alpha == 1.0f), contentsScale); + ASGraphicsBeginImageContextWithOptions(bounds.size, (alpha == 1.0f), contentsScale); [*image drawInRect:bounds]; } else { bounds = CGContextGetClipBoundingBox(context); @@ -362,8 +361,7 @@ [roundedPath stroke]; // Won't do anything if borderWidth is 0 and roundedPath is nil. if (*image) { - *image = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); + *image = ASGraphicsGetImageAndEndCurrentContext(); } } } diff --git a/Source/UIImage+ASConvenience.m b/Source/UIImage+ASConvenience.m index efc1e35360..35e6f0d563 100644 --- a/Source/UIImage+ASConvenience.m +++ b/Source/UIImage+ASConvenience.m @@ -16,6 +16,7 @@ // #import +#import #import #import @@ -138,7 +139,7 @@ UIImage *cachedImageNamed(NSString *imageName, UITraitCollection *traitCollectio // We should probably check if the background color has any alpha component but that // might be expensive due to needing to check mulitple color spaces. - UIGraphicsBeginImageContextWithOptions(bounds.size, cornerColor != nil, scale); + ASGraphicsBeginImageContextWithOptions(bounds.size, cornerColor != nil, scale); BOOL contextIsClean = YES; if (cornerColor) { @@ -168,8 +169,7 @@ UIImage *cachedImageNamed(NSString *imageName, UITraitCollection *traitCollectio [strokePath strokeWithBlendMode:(canUseCopy ? kCGBlendModeCopy : kCGBlendModeNormal) alpha:1]; } - UIImage *result = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); + UIImage *result = ASGraphicsGetImageAndEndCurrentContext(); UIEdgeInsets capInsets = UIEdgeInsetsMake(cornerRadius, cornerRadius, cornerRadius, cornerRadius); result = [result resizableImageWithCapInsets:capInsets resizingMode:UIImageResizingModeStretch]; diff --git a/examples/ASDKgram/Sample/AppDelegate.m b/examples/ASDKgram/Sample/AppDelegate.m index 16484467f3..21c47a2737 100644 --- a/examples/ASDKgram/Sample/AppDelegate.m +++ b/examples/ASDKgram/Sample/AppDelegate.m @@ -22,6 +22,8 @@ #import "WindowWithStatusBarUnderlay.h" #import "Utilities.h" +#import + #define WEAVER 0 #if WEAVER @@ -38,6 +40,8 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + ASEnableNoCopyRendering(); + // this UIWindow subclass is neccessary to make the status bar opaque _window = [[WindowWithStatusBarUnderlay alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; _window.backgroundColor = [UIColor whiteColor]; From 61dade6bda20ff1ad0003fbd79159046705fcb55 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Mon, 15 Jan 2018 15:13:05 -0800 Subject: [PATCH 05/40] Raise deployment target to iOS 9 (#743) https://github.com/TextureGroup/Texture/pull/743 Manually merged since I forgot to retarget that diff onto master before merge --- AsyncDisplayKit.xcodeproj/project.pbxproj | 6 +-- CHANGELOG.md | 1 + Podfile | 2 +- Source/ASCellNode.mm | 2 +- Source/ASCollectionView.mm | 35 ------------- Source/ASDisplayNode+Yoga.mm | 10 ++-- Source/ASMultiplexImageNode.mm | 9 +++- Source/ASRunLoopQueue.mm | 5 -- Source/ASTableView.mm | 13 ----- Source/ASTextNode.mm | 2 + Source/Base/ASAvailability.h | 5 -- Source/Details/ASTraitCollection.m | 39 +++++--------- Source/Private/ASDisplayNode+UIViewBridge.mm | 11 ++-- .../TextExperiment/Component/ASTextLayout.m | 18 +------ .../TextExperiment/String/ASTextAttribute.m | 3 ++ .../Utility/NSAttributedString+ASText.h | 15 ------ .../Utility/NSAttributedString+ASText.m | 51 +++---------------- Source/Private/_ASPendingState.mm | 14 ++--- .../ASDKListKit.xcodeproj/project.pbxproj | 8 +-- SubspecWorkspaces/ASDKListKit/Podfile | 2 +- Texture.podspec | 2 +- examples/ASCollectionView/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 11 ++-- examples/ASDKLayoutTransition/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 9 ++-- examples/ASDKTube/Podfile | 2 +- .../ASDKTube/Sample.xcodeproj/project.pbxproj | 9 ++-- examples/ASDKgram/Podfile | 2 +- .../ASDKgram/Sample.xcodeproj/project.pbxproj | 9 ++-- examples/ASMapNode/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 9 ++-- examples/ASViewController/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 9 ++-- examples/AnimatedGIF/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 5 +- examples/AsyncDisplayKitOverview/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 9 ++-- examples/CatDealsCollectionView/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 9 ++-- examples/CustomCollectionView-Swift/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 4 +- examples/CustomCollectionView/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 11 ++-- .../CustomCollectionView/Sample/AppDelegate.m | 18 +++---- .../HorizontalWithinVerticalScrolling/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 27 +++++----- examples/Kittens/Podfile | 2 +- .../Kittens/Sample.xcodeproj/project.pbxproj | 27 +++++----- examples/LayoutSpecExamples-Swift/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 4 +- examples/LayoutSpecExamples/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 4 +- examples/PagerNode/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 27 +++++----- examples/SocialAppLayout-Inverted/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 9 ++-- examples/SocialAppLayout/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 27 +++++----- examples/Swift/Podfile | 2 +- .../Swift/Sample.xcodeproj/project.pbxproj | 4 +- .../VerticalWithinHorizontalScrolling/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 27 +++++----- examples/Videos/Podfile | 2 +- .../Videos/Sample.xcodeproj/project.pbxproj | 27 +++++----- .../ASLayoutSpecPlayground-Swift/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 11 ++-- .../contents.xcworkspacedata | 10 ++++ examples_extra/ASTableViewStressTest/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 29 ++++++----- .../contents.xcworkspacedata | 10 ++++ examples_extra/ASTraitCollection/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 4 +- .../BackgroundPropertySetting/Podfile | 2 +- .../Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 4 +- examples_extra/EditableText/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 4 +- examples_extra/Multiplex/Podfile | 2 +- examples_extra/Placeholders/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 4 +- examples_extra/RepoSearcher/Podfile | 3 +- .../RepoSearcher.xcodeproj/project.pbxproj | 4 +- .../Shop/Shop.xcodeproj/project.pbxproj | 4 +- examples_extra/SynchronousConcurrency/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 27 +++++----- .../contents.xcworkspacedata | 10 ++++ .../Sample/AsyncTableViewController.m | 4 +- .../Sample/AsyncViewController.h | 2 +- .../Sample/RandomCoreGraphicsNode.m | 2 +- examples_extra/SynchronousKittens/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 4 +- examples_extra/TextStressTest/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 4 +- .../contents.xcworkspacedata | 10 ++++ examples_extra/VideoTableView/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 4 +- .../Sample.xcodeproj/project.pbxproj | 4 +- .../project.pbxproj | 4 +- 98 files changed, 361 insertions(+), 415 deletions(-) create mode 100644 examples_extra/ASLayoutSpecPlayground-Swift/Sample.xcworkspace/contents.xcworkspacedata create mode 100644 examples_extra/ASTableViewStressTest/Sample.xcworkspace/contents.xcworkspacedata create mode 100644 examples_extra/SynchronousConcurrency/Sample.xcworkspace/contents.xcworkspacedata create mode 100644 examples_extra/TextStressTest/Sample.xcworkspace/contents.xcworkspacedata diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index f3ed289f17..a04b064214 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -2509,7 +2509,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; @@ -2553,7 +2553,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; @@ -2702,7 +2702,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; diff --git a/CHANGELOG.md b/CHANGELOG.md index 02331367f8..c884b80232 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - [Automatic Subnode Management] Nodes with ASM enabled now insert/delete their subnodes as soon as they enter preload state, so the subnodes can preload too. [Huy Nguyen](https://github.com/nguyenhuy) [#706](https://github.com/TextureGroup/Texture/pull/706) - [ASCollectionNode] Added support for interactive item movement. [Adlai Holler](https://github.com/Adlai-Holler) - Added an experimental "no-copy" rendering API. See ASGraphicsContext.h for info. [Adlai Holler](https://github.com/Adlai-Holler) +- Dropped support for iOS 8. [Adlai Holler](https://github.com/Adlai-Holler) ## 2.6 - [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) diff --git a/Podfile b/Podfile index 0ebd8c98ca..21e72482c1 100644 --- a/Podfile +++ b/Podfile @@ -1,6 +1,6 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target :'AsyncDisplayKitTests' do pod 'OCMock', '~> 3.4' diff --git a/Source/ASCellNode.mm b/Source/ASCellNode.mm index 400be07968..a3816df182 100644 --- a/Source/ASCellNode.mm +++ b/Source/ASCellNode.mm @@ -87,7 +87,7 @@ if ([_viewController isKindOfClass:[ASViewController class]]) { ASViewController *asViewController = (ASViewController *)_viewController; _viewControllerNode = asViewController.node; - [_viewController view]; + [_viewController loadViewIfNeeded]; } else { // Careful to avoid retain cycle UIViewController *viewController = _viewController; diff --git a/Source/ASCollectionView.mm b/Source/ASCollectionView.mm index fda3ea8036..1b06d6befd 100644 --- a/Source/ASCollectionView.mm +++ b/Source/ASCollectionView.mm @@ -125,15 +125,6 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; ASCollectionViewInvalidationStyle _nextLayoutInvalidationStyle; - /** - * Our layer, retained. Under iOS < 9, when collection views are removed from the hierarchy, - * their layers may be deallocated and become dangling pointers. This puts the collection view - * into a very dangerous state where pretty much any call will crash it. So we manually retain our layer. - * - * You should never access this, and it will be nil under iOS >= 9. - */ - CALayer *_retainedLayer; - /** * If YES, the `UICollectionView` will reload its data on next layout pass so we should not forward any updates to it. @@ -316,10 +307,6 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; [self registerClass:[_ASCollectionViewCell class] forCellWithReuseIdentifier:kReuseIdentifier]; - if (!AS_AT_LEAST_IOS9) { - _retainedLayer = self.layer; - } - [self _configureCollectionViewLayout:layout]; return self; @@ -2039,28 +2026,6 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; return _rangeController; } -/// The UIKit version of this method is only available on iOS >= 9 -- (NSArray *)asdk_indexPathsForVisibleSupplementaryElementsOfKind:(NSString *)kind -{ - if (AS_AVAILABLE_IOS(9)) { - return [self indexPathsForVisibleSupplementaryElementsOfKind:kind]; - } - - // iOS 8 workaround - // We cannot use willDisplaySupplementaryView/didEndDisplayingSupplementaryView - // because those methods send index paths for _deleted items_ (invalid index paths) - [self layoutIfNeeded]; - NSArray *visibleAttributes = [self.collectionViewLayout layoutAttributesForElementsInRect:self.bounds]; - NSMutableArray *result = [NSMutableArray array]; - for (UICollectionViewLayoutAttributes *attributes in visibleAttributes) { - if (attributes.representedElementCategory == UICollectionElementCategorySupplementaryView - && [attributes.representedElementKind isEqualToString:kind]) { - [result addObject:attributes.indexPath]; - } - } - return result; -} - - (NSHashTable *)visibleElementsForRangeController:(ASRangeController *)rangeController { return ASPointerTableByFlatMapping(_visibleElements, id element, element); diff --git a/Source/ASDisplayNode+Yoga.mm b/Source/ASDisplayNode+Yoga.mm index 70cdd953cc..b52f948212 100644 --- a/Source/ASDisplayNode+Yoga.mm +++ b/Source/ASDisplayNode+Yoga.mm @@ -94,12 +94,10 @@ - (void)semanticContentAttributeDidChange:(UISemanticContentAttribute)attribute { - if (AS_AT_LEAST_IOS9) { - UIUserInterfaceLayoutDirection layoutDirection = - [UIView userInterfaceLayoutDirectionForSemanticContentAttribute:attribute]; - self.style.direction = (layoutDirection == UIUserInterfaceLayoutDirectionLeftToRight - ? YGDirectionLTR : YGDirectionRTL); - } + UIUserInterfaceLayoutDirection layoutDirection = + [UIView userInterfaceLayoutDirectionForSemanticContentAttribute:attribute]; + self.style.direction = (layoutDirection == UIUserInterfaceLayoutDirectionLeftToRight + ? YGDirectionLTR : YGDirectionRTL); } - (void)setYogaParent:(ASDisplayNode *)yogaParent diff --git a/Source/ASMultiplexImageNode.mm b/Source/ASMultiplexImageNode.mm index dc689d8299..a2bed044af 100644 --- a/Source/ASMultiplexImageNode.mm +++ b/Source/ASMultiplexImageNode.mm @@ -625,7 +625,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent finishedLoadingBlock(downloadedImage, nextImageIdentifier, error); }]; } - // Likewise, if it's a iOS 8 Photo asset, we need to fetch it accordingly. + // Likewise, if it's a Photos asset, we need to fetch it accordingly. else if (ASPhotosFrameworkImageRequest *request = [ASPhotosFrameworkImageRequest requestWithURL:nextImageURL]) { [self _loadPHAssetWithRequest:request identifier:nextImageIdentifier completion:^(UIImage *image, NSError *error) { as_log_verbose(ASImageLoadingLog(), "Acquired image from Photos for %@ %@", weakSelf, nextImageIdentifier); @@ -673,7 +673,11 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent ASDisplayNodeAssertNotNil(imageIdentifier, @"imageIdentifier is required"); ASDisplayNodeAssertNotNil(assetURL, @"assetURL is required"); ASDisplayNodeAssertNotNil(completionBlock, @"completionBlock is required"); - + + // ALAssetsLibrary was replaced in iOS 8 and deprecated in iOS 9. + // We'll drop support very soon. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" ALAssetsLibrary *assetLibrary = [[ALAssetsLibrary alloc] init]; [assetLibrary assetForURL:assetURL resultBlock:^(ALAsset *asset) { @@ -685,6 +689,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent } failureBlock:^(NSError *error) { completionBlock(nil, error); }]; +#pragma clang diagnostic pop } - (void)_loadPHAssetWithRequest:(ASPhotosFrameworkImageRequest *)request identifier:(id)imageIdentifier completion:(void (^)(UIImage *image, NSError *error))completionBlock diff --git a/Source/ASRunLoopQueue.mm b/Source/ASRunLoopQueue.mm index ced7982529..974e348142 100644 --- a/Source/ASRunLoopQueue.mm +++ b/Source/ASRunLoopQueue.mm @@ -57,11 +57,6 @@ static void runLoopSourceCallback(void *info) { - (void)releaseObjectInBackground:(id _Nullable __strong *)objectPtr { - // Disable background deallocation on iOS 8 and below to avoid crashes related to UIAXDelegateClearer (#2767). - if (!AS_AT_LEAST_IOS9) { - return; - } - if (objectPtr != NULL && *objectPtr != nil) { ASDN::MutexLocker l(_queueLock); _queue.push_back(*objectPtr); diff --git a/Source/ASTableView.mm b/Source/ASTableView.mm index 1cc369b072..f78a688ea8 100644 --- a/Source/ASTableView.mm +++ b/Source/ASTableView.mm @@ -187,15 +187,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; BOOL _automaticallyAdjustsContentOffset; CGPoint _deceleratingVelocity; - - /** - * Our layer, retained. Under iOS < 9, when table views are removed from the hierarchy, - * their layers may be deallocated and become dangling pointers. This puts the table view - * into a very dangerous state where pretty much any call will crash it. So we manually retain our layer. - * - * You should never access this, and it will be nil under iOS >= 9. - */ - CALayer *_retainedLayer; CGFloat _nodesConstrainedWidth; BOOL _queuedNodeHeightUpdate; @@ -350,10 +341,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; [self registerClass:_ASTableViewCell.class forCellReuseIdentifier:kCellReuseIdentifier]; - if (!AS_AT_LEAST_IOS9) { - _retainedLayer = self.layer; - } - // iOS 11 automatically uses estimated heights, so disable those (see PR #485) if (AS_AT_LEAST_IOS11) { super.estimatedRowHeight = 0.0; diff --git a/Source/ASTextNode.mm b/Source/ASTextNode.mm index c3343528a3..e2a594b05f 100644 --- a/Source/ASTextNode.mm +++ b/Source/ASTextNode.mm @@ -230,6 +230,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; { CGColorRelease(_shadowColor); + // TODO: This may not be necessary post-iOS-9 when most UIKit assign APIs + // were changed to weak. if (_longPressGestureRecognizer) { _longPressGestureRecognizer.delegate = nil; [_longPressGestureRecognizer removeTarget:nil action:NULL]; diff --git a/Source/Base/ASAvailability.h b/Source/Base/ASAvailability.h index 6a80e274a4..f78ed9aaf6 100644 --- a/Source/Base/ASAvailability.h +++ b/Source/Base/ASAvailability.h @@ -19,10 +19,6 @@ #pragma once -#ifndef kCFCoreFoundationVersionNumber_iOS_9_0 - #define kCFCoreFoundationVersionNumber_iOS_9_0 1240.10 -#endif - #ifndef kCFCoreFoundationVersionNumber_iOS_10_0 #define kCFCoreFoundationVersionNumber_iOS_10_0 1348.00 #endif @@ -35,7 +31,6 @@ #define __IPHONE_11_0 110000 #endif -#define AS_AT_LEAST_IOS9 (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_9_0) #define AS_AT_LEAST_IOS10 (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_10_0) #define AS_AT_LEAST_IOS11 (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_11_0) diff --git a/Source/Details/ASTraitCollection.m b/Source/Details/ASTraitCollection.m index 04eaea608e..570095ec41 100644 --- a/Source/Details/ASTraitCollection.m +++ b/Source/Details/ASTraitCollection.m @@ -67,28 +67,17 @@ BOOL ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(ASPrimitiveTr // Named so as not to conflict with a hidden Apple function, in case compiler decides not to inline ASDISPLAYNODE_INLINE NSString *AS_NSStringFromUIUserInterfaceIdiom(UIUserInterfaceIdiom idiom) { - if (AS_AVAILABLE_IOS(9)) { - switch (idiom) { - case UIUserInterfaceIdiomTV: - return @"TV"; - case UIUserInterfaceIdiomPad: - return @"Pad"; - case UIUserInterfaceIdiomPhone: - return @"Phone"; - case UIUserInterfaceIdiomCarPlay: - return @"CarPlay"; - default: - return @"Unspecified"; - } - } else { - switch (idiom) { - case UIUserInterfaceIdiomPad: - return @"Pad"; - case UIUserInterfaceIdiomPhone: - return @"Phone"; - default: - return @"Unspecified"; - } + switch (idiom) { + case UIUserInterfaceIdiomTV: + return @"TV"; + case UIUserInterfaceIdiomPad: + return @"Pad"; + case UIUserInterfaceIdiomPhone: + return @"Phone"; + case UIUserInterfaceIdiomCarPlay: + return @"CarPlay"; + default: + return @"Unspecified"; } } @@ -178,15 +167,11 @@ NSString *NSStringFromASPrimitiveTraitCollection(ASPrimitiveTraitCollection trai + (instancetype)traitCollectionWithUITraitCollection:(UITraitCollection *)traitCollection containerSize:(CGSize)windowSize { - UIForceTouchCapability forceTouch = UIForceTouchCapabilityUnknown; - if(AS_AVAILABLE_IOS(9)) { - forceTouch = traitCollection.forceTouchCapability; - } return [self traitCollectionWithDisplayScale:traitCollection.displayScale userInterfaceIdiom:traitCollection.userInterfaceIdiom horizontalSizeClass:traitCollection.horizontalSizeClass verticalSizeClass:traitCollection.verticalSizeClass - forceTouchCapability:forceTouch + forceTouchCapability:traitCollection.forceTouchCapability containerSize:windowSize]; } diff --git a/Source/Private/ASDisplayNode+UIViewBridge.mm b/Source/Private/ASDisplayNode+UIViewBridge.mm index cf07bd7000..a3eceb7412 100644 --- a/Source/Private/ASDisplayNode+UIViewBridge.mm +++ b/Source/Private/ASDisplayNode+UIViewBridge.mm @@ -854,21 +854,16 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNo - (UISemanticContentAttribute)semanticContentAttribute { _bridge_prologue_read; - if (AS_AT_LEAST_IOS9) { - return _getFromViewOnly(semanticContentAttribute); - } - return UISemanticContentAttributeUnspecified; + return _getFromViewOnly(semanticContentAttribute); } - (void)setSemanticContentAttribute:(UISemanticContentAttribute)semanticContentAttribute { _bridge_prologue_write; - if (AS_AT_LEAST_IOS9) { - _setToViewOnly(semanticContentAttribute, semanticContentAttribute); + _setToViewOnly(semanticContentAttribute, semanticContentAttribute); #if YOGA - [self semanticContentAttributeDidChange:semanticContentAttribute]; + [self semanticContentAttributeDidChange:semanticContentAttribute]; #endif - } } @end diff --git a/Source/Private/TextExperiment/Component/ASTextLayout.m b/Source/Private/TextExperiment/Component/ASTextLayout.m index df8d356f6b..fa61946c53 100755 --- a/Source/Private/TextExperiment/Component/ASTextLayout.m +++ b/Source/Private/TextExperiment/Component/ASTextLayout.m @@ -405,25 +405,9 @@ dispatch_semaphore_signal(_lock); container->_readonly = YES; maximumNumberOfRows = container.maximumNumberOfRows; - // CoreText bug when draw joined emoji since iOS 8.3. - // See -[NSMutableAttributedString setClearColorToJoinedEmoji] for more information. - static BOOL needFixJoinedEmojiBug = NO; // It may use larger constraint size when create CTFrame with // CTFramesetterCreateFrame in iOS 10. - static BOOL needFixLayoutSizeBug = NO; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - double systemVersionDouble = [UIDevice currentDevice].systemVersion.doubleValue; - if (8.3 <= systemVersionDouble && systemVersionDouble < 9) { - needFixJoinedEmojiBug = YES; - } - if (systemVersionDouble >= 10) { - needFixLayoutSizeBug = YES; - } - }); - if (needFixJoinedEmojiBug) { - [((NSMutableAttributedString *)text) as_setClearColorToJoinedEmoji]; - } + BOOL needFixLayoutSizeBug = AS_AT_LEAST_IOS10; layout = [[ASTextLayout alloc] _init]; layout.text = text; diff --git a/Source/Private/TextExperiment/String/ASTextAttribute.m b/Source/Private/TextExperiment/String/ASTextAttribute.m index 084bdcf820..0cc8e22a72 100755 --- a/Source/Private/TextExperiment/String/ASTextAttribute.m +++ b/Source/Private/TextExperiment/String/ASTextAttribute.m @@ -63,7 +63,10 @@ ASTextAttributeType ASTextAttributeGetType(NSString *name){ dic[(id)kCTSuperscriptAttributeName] = UIKit; //it's a CoreText attrubite, but only supported by UIKit... dic[NSVerticalGlyphFormAttributeName] = All; dic[(id)kCTGlyphInfoAttributeName] = CoreText_ASText; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" dic[(id)kCTCharacterShapeAttributeName] = CoreText_ASText; +#pragma clang diagnostic pop dic[(id)kCTRunDelegateAttributeName] = CoreText_ASText; dic[(id)kCTBaselineClassAttributeName] = CoreText_ASText; dic[(id)kCTBaselineInfoAttributeName] = CoreText_ASText; diff --git a/Source/Private/TextExperiment/Utility/NSAttributedString+ASText.h b/Source/Private/TextExperiment/Utility/NSAttributedString+ASText.h index 9fc8ec2228..31ef75d121 100755 --- a/Source/Private/TextExperiment/Utility/NSAttributedString+ASText.h +++ b/Source/Private/TextExperiment/Utility/NSAttributedString+ASText.h @@ -1357,21 +1357,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)as_appendString:(NSString *)string; -/** - Set foreground color with [UIColor clearColor] in joined-emoji range. - Emoji drawing will not be affected by the foreground color. - - @discussion In iOS 8.3, Apple releases some new diversified emojis. - There's some single emoji which can be assembled to a new 'joined-emoji'. - The joiner is unicode character 'ZERO WIDTH JOINER' (U+200D). - For example: 👨👩👧👧 -> 👨‍👩‍👧‍👧. - - When there are more than 5 'joined-emoji' in a same CTLine, CoreText may render some - extra glyphs above the emoji. It's a bug in CoreText, try this method to avoid. - This bug is fixed in iOS 9. - */ -- (void)as_setClearColorToJoinedEmoji; - /** Removes all discontinuous attributes in a specified range. See `allDiscontinuousAttributeKeys`. diff --git a/Source/Private/TextExperiment/Utility/NSAttributedString+ASText.m b/Source/Private/TextExperiment/Utility/NSAttributedString+ASText.m index d67dd15d2c..327bc41e2c 100755 --- a/Source/Private/TextExperiment/Utility/NSAttributedString+ASText.m +++ b/Source/Private/TextExperiment/Utility/NSAttributedString+ASText.m @@ -600,7 +600,10 @@ return style. _attr_; dispatch_once(&onceToken, ^{ failSet = [NSMutableSet new]; [failSet addObject:(id)kCTGlyphInfoAttributeName]; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" [failSet addObject:(id)kCTCharacterShapeAttributeName]; +#pragma clang diagnostic pop [failSet addObject:(id)kCTLanguageAttributeName]; [failSet addObject:(id)kCTRunDelegateAttributeName]; [failSet addObject:(id)kCTBaselineClassAttributeName]; @@ -1049,7 +1052,10 @@ style. _attr_ = _attr_; \ } - (void)as_setCharacterShape:(NSNumber *)characterShape range:(NSRange)range { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" [self as_setAttribute:(id)kCTCharacterShapeAttributeName value:characterShape range:range]; +#pragma clang diagnostic pop } - (void)as_setRunDelegate:(CTRunDelegateRef)runDelegate range:(NSRange)range { @@ -1178,51 +1184,6 @@ style. _attr_ = _attr_; \ [self as_removeDiscontinuousAttributesInRange:NSMakeRange(length, string.length)]; } -- (void)as_setClearColorToJoinedEmoji { - NSString *str = self.string; - if (str.length < 8) return; - - // Most string do not contains the joined-emoji, test the joiner first. - BOOL containsJoiner = NO; - { - CFStringRef cfStr = (__bridge CFStringRef)str; - BOOL needFree = NO; - UniChar *chars = NULL; - chars = (UniChar *)CFStringGetCharactersPtr(cfStr); - if (!chars) { - chars = (UniChar *)malloc(str.length * sizeof(UniChar)); - if (chars) { - needFree = YES; - CFStringGetCharacters(cfStr, CFRangeMake(0, str.length), chars); - } - } - if (!chars) { // fail to get unichar.. - containsJoiner = YES; - } else { - for (int i = 0, max = (int)str.length; i < max; i++) { - if (chars[i] == 0x200D) { // 'ZERO WIDTH JOINER' (U+200D) - containsJoiner = YES; - break; - } - } - if (needFree) free(chars); - } - } - if (!containsJoiner) return; - - // NSRegularExpression is designed to be immutable and thread safe. - static NSRegularExpression *regex; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - regex = [NSRegularExpression regularExpressionWithPattern:@"((👨‍👩‍👧‍👦|👨‍👩‍👦‍👦|👨‍👩‍👧‍👧|👩‍👩‍👧‍👦|👩‍👩‍👦‍👦|👩‍👩‍👧‍👧|👨‍👨‍👧‍👦|👨‍👨‍👦‍👦|👨‍👨‍👧‍👧)+|(👨‍👩‍👧|👩‍👩‍👦|👩‍👩‍👧|👨‍👨‍👦|👨‍👨‍👧))" options:kNilOptions error:nil]; - }); - - UIColor *clear = [UIColor clearColor]; - [regex enumerateMatchesInString:str options:kNilOptions range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { - [self as_setColor:clear range:result.range]; - }]; -} - - (void)as_removeDiscontinuousAttributesInRange:(NSRange)range { NSArray *keys = [NSMutableAttributedString as_allDiscontinuousAttributeKeys]; for (NSString *key in keys) { diff --git a/Source/Private/_ASPendingState.mm b/Source/Private/_ASPendingState.mm index 4374401d87..1a87e496b3 100644 --- a/Source/Private/_ASPendingState.mm +++ b/Source/Private/_ASPendingState.mm @@ -295,9 +295,7 @@ static BOOL defaultAllowsEdgeAntialiasing = NO; accessibilityActivationPoint = CGPointZero; accessibilityPath = nil; edgeAntialiasingMask = (kCALayerLeftEdge | kCALayerRightEdge | kCALayerTopEdge | kCALayerBottomEdge); - if (AS_AVAILABLE_IOS(9)) { - semanticContentAttribute = UISemanticContentAttributeUnspecified; - } + semanticContentAttribute = UISemanticContentAttributeUnspecified; return self; } @@ -1051,10 +1049,8 @@ static BOOL defaultAllowsEdgeAntialiasing = NO; if (flags.setOpaque) ASDisplayNodeAssert(layer.opaque == opaque, @"Didn't set opaque as desired"); - if (AS_AVAILABLE_IOS(9)) { - if (flags.setSemanticContentAttribute) { - view.semanticContentAttribute = semanticContentAttribute; - } + if (flags.setSemanticContentAttribute) { + view.semanticContentAttribute = semanticContentAttribute; } if (flags.setIsAccessibilityElement) @@ -1215,9 +1211,7 @@ static BOOL defaultAllowsEdgeAntialiasing = NO; pendingState.allowsGroupOpacity = layer.allowsGroupOpacity; pendingState.allowsEdgeAntialiasing = layer.allowsEdgeAntialiasing; pendingState.edgeAntialiasingMask = layer.edgeAntialiasingMask; - if (AS_AVAILABLE_IOS(9)) { - pendingState.semanticContentAttribute = view.semanticContentAttribute; - } + pendingState.semanticContentAttribute = view.semanticContentAttribute; pendingState.isAccessibilityElement = view.isAccessibilityElement; pendingState.accessibilityLabel = view.accessibilityLabel; pendingState.accessibilityHint = view.accessibilityHint; diff --git a/SubspecWorkspaces/ASDKListKit/ASDKListKit.xcodeproj/project.pbxproj b/SubspecWorkspaces/ASDKListKit/ASDKListKit.xcodeproj/project.pbxproj index 814ad945a4..e275becc19 100644 --- a/SubspecWorkspaces/ASDKListKit/ASDKListKit.xcodeproj/project.pbxproj +++ b/SubspecWorkspaces/ASDKListKit/ASDKListKit.xcodeproj/project.pbxproj @@ -283,14 +283,14 @@ CC55321D1E16EB7A0011C01F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; }; name = Debug; }; CC55321E1E16EB7A0011C01F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; }; name = Release; }; @@ -336,7 +336,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = ASDKListKitTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; @@ -382,7 +382,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = ASDKListKitTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = asyncdisplaykit.ASDKListKitTests; diff --git a/SubspecWorkspaces/ASDKListKit/Podfile b/SubspecWorkspaces/ASDKListKit/Podfile index d5b84556fb..36498c2473 100644 --- a/SubspecWorkspaces/ASDKListKit/Podfile +++ b/SubspecWorkspaces/ASDKListKit/Podfile @@ -1,6 +1,6 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'ASDKListKitTests' do pod 'Texture/IGListKit', :path => '../..' pod 'OCMock', '~> 3.4' diff --git a/Texture.podspec b/Texture.podspec index 73e9047728..8b75b43343 100644 --- a/Texture.podspec +++ b/Texture.podspec @@ -14,7 +14,7 @@ Pod::Spec.new do |spec| spec.weak_frameworks = 'Photos','MapKit','AssetsLibrary' spec.requires_arc = true - spec.ios.deployment_target = '8.0' + spec.ios.deployment_target = '9.0' # Uncomment when fixed: issues with tvOS build for release 2.0 # spec.tvos.deployment_target = '9.0' diff --git a/examples/ASCollectionView/Podfile b/examples/ASCollectionView/Podfile index 922ff50ec1..08d1b7add6 100644 --- a/examples/ASCollectionView/Podfile +++ b/examples/ASCollectionView/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples/ASCollectionView/Sample.xcodeproj/project.pbxproj b/examples/ASCollectionView/Sample.xcodeproj/project.pbxproj index 634184297c..967d1f7cd6 100644 --- a/examples/ASCollectionView/Sample.xcodeproj/project.pbxproj +++ b/examples/ASCollectionView/Sample.xcodeproj/project.pbxproj @@ -240,13 +240,16 @@ files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -302,7 +305,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.1; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -338,7 +341,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.1; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; @@ -353,7 +356,6 @@ ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; INFOPLIST_FILE = Sample/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.1; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_NAME = "$(TARGET_NAME)"; TARGETED_DEVICE_FAMILY = 1; @@ -367,7 +369,6 @@ ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; INFOPLIST_FILE = Sample/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.1; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_NAME = "$(TARGET_NAME)"; TARGETED_DEVICE_FAMILY = 1; diff --git a/examples/ASDKLayoutTransition/Podfile b/examples/ASDKLayoutTransition/Podfile index 90c6ad7ea8..0fbf4c9524 100644 --- a/examples/ASDKLayoutTransition/Podfile +++ b/examples/ASDKLayoutTransition/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' pod 'Texture/Yoga', :path => '../..' diff --git a/examples/ASDKLayoutTransition/Sample.xcodeproj/project.pbxproj b/examples/ASDKLayoutTransition/Sample.xcodeproj/project.pbxproj index 3b4f1e6f1d..17537d093d 100644 --- a/examples/ASDKLayoutTransition/Sample.xcodeproj/project.pbxproj +++ b/examples/ASDKLayoutTransition/Sample.xcodeproj/project.pbxproj @@ -196,13 +196,16 @@ files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */ = { @@ -271,7 +274,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -306,7 +309,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/examples/ASDKTube/Podfile b/examples/ASDKTube/Podfile index b75e492fab..71a7f2c4b2 100644 --- a/examples/ASDKTube/Podfile +++ b/examples/ASDKTube/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples/ASDKTube/Sample.xcodeproj/project.pbxproj b/examples/ASDKTube/Sample.xcodeproj/project.pbxproj index 0e6c890fa4..1b8c982bed 100644 --- a/examples/ASDKTube/Sample.xcodeproj/project.pbxproj +++ b/examples/ASDKTube/Sample.xcodeproj/project.pbxproj @@ -277,13 +277,16 @@ files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */ = { @@ -356,7 +359,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -391,7 +394,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/examples/ASDKgram/Podfile b/examples/ASDKgram/Podfile index eb879f3f12..4aebd04935 100644 --- a/examples/ASDKgram/Podfile +++ b/examples/ASDKgram/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture/IGListKit', :path => '../..' pod 'Texture/PINRemoteImage', :path => '../..' diff --git a/examples/ASDKgram/Sample.xcodeproj/project.pbxproj b/examples/ASDKgram/Sample.xcodeproj/project.pbxproj index 9cfb0e254d..743b9456e8 100644 --- a/examples/ASDKgram/Sample.xcodeproj/project.pbxproj +++ b/examples/ASDKgram/Sample.xcodeproj/project.pbxproj @@ -381,13 +381,16 @@ files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */ = { @@ -477,7 +480,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -512,7 +515,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/examples/ASMapNode/Podfile b/examples/ASMapNode/Podfile index 922ff50ec1..08d1b7add6 100644 --- a/examples/ASMapNode/Podfile +++ b/examples/ASMapNode/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples/ASMapNode/Sample.xcodeproj/project.pbxproj b/examples/ASMapNode/Sample.xcodeproj/project.pbxproj index 529b587837..48093ffda1 100644 --- a/examples/ASMapNode/Sample.xcodeproj/project.pbxproj +++ b/examples/ASMapNode/Sample.xcodeproj/project.pbxproj @@ -214,13 +214,16 @@ files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -288,7 +291,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -325,7 +328,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/examples/ASViewController/Podfile b/examples/ASViewController/Podfile index 922ff50ec1..08d1b7add6 100644 --- a/examples/ASViewController/Podfile +++ b/examples/ASViewController/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples/ASViewController/Sample.xcodeproj/project.pbxproj b/examples/ASViewController/Sample.xcodeproj/project.pbxproj index 200b3c2255..e304fdb8e3 100644 --- a/examples/ASViewController/Sample.xcodeproj/project.pbxproj +++ b/examples/ASViewController/Sample.xcodeproj/project.pbxproj @@ -219,13 +219,16 @@ files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -294,7 +297,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -331,7 +334,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/examples/AnimatedGIF/Podfile b/examples/AnimatedGIF/Podfile index e784c52d14..c998fa0a8d 100644 --- a/examples/AnimatedGIF/Podfile +++ b/examples/AnimatedGIF/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' pod 'PINRemoteImage/WebP' diff --git a/examples/AnimatedGIF/Sample.xcodeproj/project.pbxproj b/examples/AnimatedGIF/Sample.xcodeproj/project.pbxproj index 7498a38dde..453c359c0e 100644 --- a/examples/AnimatedGIF/Sample.xcodeproj/project.pbxproj +++ b/examples/AnimatedGIF/Sample.xcodeproj/project.pbxproj @@ -214,13 +214,16 @@ files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ diff --git a/examples/AsyncDisplayKitOverview/Podfile b/examples/AsyncDisplayKitOverview/Podfile index 84d7dee82e..ff6cb63a31 100644 --- a/examples/AsyncDisplayKitOverview/Podfile +++ b/examples/AsyncDisplayKitOverview/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -platform :ios, '8.0' +platform :ios, '9.0' # Uncomment this line if you're using Swift # use_frameworks! diff --git a/examples/AsyncDisplayKitOverview/Sample.xcodeproj/project.pbxproj b/examples/AsyncDisplayKitOverview/Sample.xcodeproj/project.pbxproj index 8d5abb4bbc..3683dbd638 100644 --- a/examples/AsyncDisplayKitOverview/Sample.xcodeproj/project.pbxproj +++ b/examples/AsyncDisplayKitOverview/Sample.xcodeproj/project.pbxproj @@ -217,13 +217,16 @@ files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 84F93825AFB1CA7FBB116BA4 /* [CP] Copy Pods Resources */ = { @@ -309,7 +312,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -348,7 +351,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/examples/CatDealsCollectionView/Podfile b/examples/CatDealsCollectionView/Podfile index b75e492fab..71a7f2c4b2 100644 --- a/examples/CatDealsCollectionView/Podfile +++ b/examples/CatDealsCollectionView/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples/CatDealsCollectionView/Sample.xcodeproj/project.pbxproj b/examples/CatDealsCollectionView/Sample.xcodeproj/project.pbxproj index 5a9485def8..29fb32d342 100644 --- a/examples/CatDealsCollectionView/Sample.xcodeproj/project.pbxproj +++ b/examples/CatDealsCollectionView/Sample.xcodeproj/project.pbxproj @@ -242,13 +242,16 @@ files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -308,7 +311,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.1; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -344,7 +347,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.1; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/examples/CustomCollectionView-Swift/Podfile b/examples/CustomCollectionView-Swift/Podfile index a0eec32ed0..92a9acc9c8 100644 --- a/examples/CustomCollectionView-Swift/Podfile +++ b/examples/CustomCollectionView-Swift/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' use_frameworks! diff --git a/examples/CustomCollectionView-Swift/Sample.xcodeproj/project.pbxproj b/examples/CustomCollectionView-Swift/Sample.xcodeproj/project.pbxproj index 6969496c87..e1912f909f 100755 --- a/examples/CustomCollectionView-Swift/Sample.xcodeproj/project.pbxproj +++ b/examples/CustomCollectionView-Swift/Sample.xcodeproj/project.pbxproj @@ -294,7 +294,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -344,7 +344,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; diff --git a/examples/CustomCollectionView/Podfile b/examples/CustomCollectionView/Podfile index b75e492fab..71a7f2c4b2 100644 --- a/examples/CustomCollectionView/Podfile +++ b/examples/CustomCollectionView/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples/CustomCollectionView/Sample.xcodeproj/project.pbxproj b/examples/CustomCollectionView/Sample.xcodeproj/project.pbxproj index 91818f8f89..edaa0d1278 100644 --- a/examples/CustomCollectionView/Sample.xcodeproj/project.pbxproj +++ b/examples/CustomCollectionView/Sample.xcodeproj/project.pbxproj @@ -228,13 +228,16 @@ files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -291,7 +294,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.1; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -327,7 +330,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.1; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; @@ -342,7 +345,6 @@ ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; DEVELOPMENT_TEAM = XSR3D45JSF; INFOPLIST_FILE = Sample/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.1; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.facebook.AsyncDisplayKit.Sample.CustomCollectionView; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -357,7 +359,6 @@ ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; DEVELOPMENT_TEAM = XSR3D45JSF; INFOPLIST_FILE = Sample/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.1; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.facebook.AsyncDisplayKit.Sample.CustomCollectionView; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/examples/CustomCollectionView/Sample/AppDelegate.m b/examples/CustomCollectionView/Sample/AppDelegate.m index c0769e5d5a..dec3c29fe7 100644 --- a/examples/CustomCollectionView/Sample/AppDelegate.m +++ b/examples/CustomCollectionView/Sample/AppDelegate.m @@ -4,15 +4,15 @@ // // 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. +// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. // -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// Modifications to this file made after 4/13/2017 are: Copyright (c) through the present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 // #import "AppDelegate.h" @@ -23,7 +23,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds]; self.window.backgroundColor = [UIColor whiteColor]; self.window.rootViewController = [[ViewController alloc] init]; diff --git a/examples/HorizontalWithinVerticalScrolling/Podfile b/examples/HorizontalWithinVerticalScrolling/Podfile index b75e492fab..71a7f2c4b2 100644 --- a/examples/HorizontalWithinVerticalScrolling/Podfile +++ b/examples/HorizontalWithinVerticalScrolling/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples/HorizontalWithinVerticalScrolling/Sample.xcodeproj/project.pbxproj b/examples/HorizontalWithinVerticalScrolling/Sample.xcodeproj/project.pbxproj index c999679905..6472106a7b 100644 --- a/examples/HorizontalWithinVerticalScrolling/Sample.xcodeproj/project.pbxproj +++ b/examples/HorizontalWithinVerticalScrolling/Sample.xcodeproj/project.pbxproj @@ -123,12 +123,12 @@ isa = PBXNativeTarget; buildConfigurationList = 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */; buildPhases = ( - E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */, + E080B80F89C34A25B3488E26 /* [CP] Check Pods Manifest.lock */, 05E2127D19D4DB510098F589 /* Sources */, 05E2127E19D4DB510098F589 /* Frameworks */, 05E2127F19D4DB510098F589 /* Resources */, - F012A6F39E0149F18F564F50 /* Copy Pods Resources */, - A79A9172A45D7C9595AA01CC /* Embed Pods Frameworks */, + F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */, + A79A9172A45D7C9595AA01CC /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -185,14 +185,14 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - A79A9172A45D7C9595AA01CC /* Embed Pods Frameworks */ = { + A79A9172A45D7C9595AA01CC /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Embed Pods Frameworks"; + name = "[CP] Embed Pods Frameworks"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -200,29 +200,32 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */ = { + E080B80F89C34A25B3488E26 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); - name = "Check Pods Manifest.lock"; + name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - 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"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - F012A6F39E0149F18F564F50 /* Copy Pods Resources */ = { + F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Copy Pods Resources"; + name = "[CP] Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -282,7 +285,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -317,7 +320,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/examples/Kittens/Podfile b/examples/Kittens/Podfile index b75e492fab..71a7f2c4b2 100644 --- a/examples/Kittens/Podfile +++ b/examples/Kittens/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples/Kittens/Sample.xcodeproj/project.pbxproj b/examples/Kittens/Sample.xcodeproj/project.pbxproj index 7e5f1a9ef9..3c2cb1a592 100644 --- a/examples/Kittens/Sample.xcodeproj/project.pbxproj +++ b/examples/Kittens/Sample.xcodeproj/project.pbxproj @@ -125,12 +125,12 @@ isa = PBXNativeTarget; buildConfigurationList = 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */; buildPhases = ( - E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */, + E080B80F89C34A25B3488E26 /* [CP] Check Pods Manifest.lock */, 05E2127D19D4DB510098F589 /* Sources */, 05E2127E19D4DB510098F589 /* Frameworks */, 05E2127F19D4DB510098F589 /* Resources */, - F012A6F39E0149F18F564F50 /* Copy Pods Resources */, - 9C2078E0C7EEEFF207C7F6A9 /* Embed Pods Frameworks */, + F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */, + 9C2078E0C7EEEFF207C7F6A9 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -187,14 +187,14 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 9C2078E0C7EEEFF207C7F6A9 /* Embed Pods Frameworks */ = { + 9C2078E0C7EEEFF207C7F6A9 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Embed Pods Frameworks"; + name = "[CP] Embed Pods Frameworks"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -202,29 +202,32 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */ = { + E080B80F89C34A25B3488E26 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); - name = "Check Pods Manifest.lock"; + name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - 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"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - F012A6F39E0149F18F564F50 /* Copy Pods Resources */ = { + F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Copy Pods Resources"; + name = "[CP] Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -285,7 +288,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -320,7 +323,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/examples/LayoutSpecExamples-Swift/Podfile b/examples/LayoutSpecExamples-Swift/Podfile index 8458e64c60..83d2cae8bf 100644 --- a/examples/LayoutSpecExamples-Swift/Podfile +++ b/examples/LayoutSpecExamples-Swift/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' use_frameworks! diff --git a/examples/LayoutSpecExamples-Swift/Sample.xcodeproj/project.pbxproj b/examples/LayoutSpecExamples-Swift/Sample.xcodeproj/project.pbxproj index d69d6e6d85..5ac0a8718a 100644 --- a/examples/LayoutSpecExamples-Swift/Sample.xcodeproj/project.pbxproj +++ b/examples/LayoutSpecExamples-Swift/Sample.xcodeproj/project.pbxproj @@ -310,7 +310,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -355,7 +355,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; diff --git a/examples/LayoutSpecExamples/Podfile b/examples/LayoutSpecExamples/Podfile index 922ff50ec1..08d1b7add6 100644 --- a/examples/LayoutSpecExamples/Podfile +++ b/examples/LayoutSpecExamples/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples/LayoutSpecExamples/Sample.xcodeproj/project.pbxproj b/examples/LayoutSpecExamples/Sample.xcodeproj/project.pbxproj index 3c9041a2b3..c065fcab3f 100644 --- a/examples/LayoutSpecExamples/Sample.xcodeproj/project.pbxproj +++ b/examples/LayoutSpecExamples/Sample.xcodeproj/project.pbxproj @@ -299,7 +299,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -336,7 +336,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/examples/PagerNode/Podfile b/examples/PagerNode/Podfile index b75e492fab..71a7f2c4b2 100644 --- a/examples/PagerNode/Podfile +++ b/examples/PagerNode/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples/PagerNode/Sample.xcodeproj/project.pbxproj b/examples/PagerNode/Sample.xcodeproj/project.pbxproj index 16d7ec8406..febfa871c5 100644 --- a/examples/PagerNode/Sample.xcodeproj/project.pbxproj +++ b/examples/PagerNode/Sample.xcodeproj/project.pbxproj @@ -118,12 +118,12 @@ isa = PBXNativeTarget; buildConfigurationList = 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */; buildPhases = ( - E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */, + E080B80F89C34A25B3488E26 /* [CP] Check Pods Manifest.lock */, 05E2127D19D4DB510098F589 /* Sources */, 05E2127E19D4DB510098F589 /* Frameworks */, 05E2127F19D4DB510098F589 /* Resources */, - F012A6F39E0149F18F564F50 /* Copy Pods Resources */, - 6E05308BEF86AD80AEB4EEE7 /* Embed Pods Frameworks */, + F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */, + 6E05308BEF86AD80AEB4EEE7 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -180,14 +180,14 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 6E05308BEF86AD80AEB4EEE7 /* Embed Pods Frameworks */ = { + 6E05308BEF86AD80AEB4EEE7 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Embed Pods Frameworks"; + name = "[CP] Embed Pods Frameworks"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -195,29 +195,32 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */ = { + E080B80F89C34A25B3488E26 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); - name = "Check Pods Manifest.lock"; + name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - 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"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - F012A6F39E0149F18F564F50 /* Copy Pods Resources */ = { + F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Copy Pods Resources"; + name = "[CP] Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -277,7 +280,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -312,7 +315,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/examples/SocialAppLayout-Inverted/Podfile b/examples/SocialAppLayout-Inverted/Podfile index b75e492fab..71a7f2c4b2 100644 --- a/examples/SocialAppLayout-Inverted/Podfile +++ b/examples/SocialAppLayout-Inverted/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples/SocialAppLayout-Inverted/Sample.xcodeproj/project.pbxproj b/examples/SocialAppLayout-Inverted/Sample.xcodeproj/project.pbxproj index b43fcaac6e..3ad0bf8772 100644 --- a/examples/SocialAppLayout-Inverted/Sample.xcodeproj/project.pbxproj +++ b/examples/SocialAppLayout-Inverted/Sample.xcodeproj/project.pbxproj @@ -319,13 +319,16 @@ files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -385,7 +388,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -422,7 +425,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/examples/SocialAppLayout/Podfile b/examples/SocialAppLayout/Podfile index b75e492fab..71a7f2c4b2 100644 --- a/examples/SocialAppLayout/Podfile +++ b/examples/SocialAppLayout/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples/SocialAppLayout/Sample.xcodeproj/project.pbxproj b/examples/SocialAppLayout/Sample.xcodeproj/project.pbxproj index abd0cb6653..b187eec74a 100644 --- a/examples/SocialAppLayout/Sample.xcodeproj/project.pbxproj +++ b/examples/SocialAppLayout/Sample.xcodeproj/project.pbxproj @@ -200,12 +200,12 @@ isa = PBXNativeTarget; buildConfigurationList = 3EEA4EFB1BECC4A1008A7F35 /* Build configuration list for PBXNativeTarget "Sample" */; buildPhases = ( - B5BD9E5609B2CB179EEE0CF4 /* Check Pods Manifest.lock */, + B5BD9E5609B2CB179EEE0CF4 /* [CP] Check Pods Manifest.lock */, 3EEA4EE01BECC4A1008A7F35 /* Sources */, 3EEA4EE11BECC4A1008A7F35 /* Frameworks */, 3EEA4EE21BECC4A1008A7F35 /* Resources */, - 21F2C1D9B53F9468EAF1653F /* Copy Pods Resources */, - 852437589F1D53B9483A75DF /* Embed Pods Frameworks */, + 21F2C1D9B53F9468EAF1653F /* [CP] Copy Pods Resources */, + 852437589F1D53B9483A75DF /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -281,14 +281,14 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 21F2C1D9B53F9468EAF1653F /* Copy Pods Resources */ = { + 21F2C1D9B53F9468EAF1653F /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Copy Pods Resources"; + name = "[CP] Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -296,14 +296,14 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 852437589F1D53B9483A75DF /* Embed Pods Frameworks */ = { + 852437589F1D53B9483A75DF /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Embed Pods Frameworks"; + name = "[CP] Embed Pods Frameworks"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -311,19 +311,22 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - B5BD9E5609B2CB179EEE0CF4 /* Check Pods Manifest.lock */ = { + B5BD9E5609B2CB179EEE0CF4 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); - name = "Check Pods Manifest.lock"; + name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - 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"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -383,7 +386,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -420,7 +423,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/examples/Swift/Podfile b/examples/Swift/Podfile index 8458e64c60..83d2cae8bf 100644 --- a/examples/Swift/Podfile +++ b/examples/Swift/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' use_frameworks! diff --git a/examples/Swift/Sample.xcodeproj/project.pbxproj b/examples/Swift/Sample.xcodeproj/project.pbxproj index a377337a3a..533a738ea5 100644 --- a/examples/Swift/Sample.xcodeproj/project.pbxproj +++ b/examples/Swift/Sample.xcodeproj/project.pbxproj @@ -291,7 +291,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -336,7 +336,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; diff --git a/examples/VerticalWithinHorizontalScrolling/Podfile b/examples/VerticalWithinHorizontalScrolling/Podfile index b75e492fab..71a7f2c4b2 100644 --- a/examples/VerticalWithinHorizontalScrolling/Podfile +++ b/examples/VerticalWithinHorizontalScrolling/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples/VerticalWithinHorizontalScrolling/Sample.xcodeproj/project.pbxproj b/examples/VerticalWithinHorizontalScrolling/Sample.xcodeproj/project.pbxproj index 7f7950060a..80493caefa 100644 --- a/examples/VerticalWithinHorizontalScrolling/Sample.xcodeproj/project.pbxproj +++ b/examples/VerticalWithinHorizontalScrolling/Sample.xcodeproj/project.pbxproj @@ -123,12 +123,12 @@ isa = PBXNativeTarget; buildConfigurationList = 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */; buildPhases = ( - E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */, + E080B80F89C34A25B3488E26 /* [CP] Check Pods Manifest.lock */, 05E2127D19D4DB510098F589 /* Sources */, 05E2127E19D4DB510098F589 /* Frameworks */, 05E2127F19D4DB510098F589 /* Resources */, - F012A6F39E0149F18F564F50 /* Copy Pods Resources */, - 6E94068CFAA7736333E7D960 /* Embed Pods Frameworks */, + F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */, + 6E94068CFAA7736333E7D960 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -185,14 +185,14 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 6E94068CFAA7736333E7D960 /* Embed Pods Frameworks */ = { + 6E94068CFAA7736333E7D960 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Embed Pods Frameworks"; + name = "[CP] Embed Pods Frameworks"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -200,29 +200,32 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */ = { + E080B80F89C34A25B3488E26 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); - name = "Check Pods Manifest.lock"; + name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - 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"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - F012A6F39E0149F18F564F50 /* Copy Pods Resources */ = { + F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Copy Pods Resources"; + name = "[CP] Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -282,7 +285,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -317,7 +320,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/examples/Videos/Podfile b/examples/Videos/Podfile index b75e492fab..71a7f2c4b2 100644 --- a/examples/Videos/Podfile +++ b/examples/Videos/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples/Videos/Sample.xcodeproj/project.pbxproj b/examples/Videos/Sample.xcodeproj/project.pbxproj index b43c7e7fc8..8a04601003 100644 --- a/examples/Videos/Sample.xcodeproj/project.pbxproj +++ b/examples/Videos/Sample.xcodeproj/project.pbxproj @@ -125,12 +125,12 @@ isa = PBXNativeTarget; buildConfigurationList = 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */; buildPhases = ( - E080B80F89C34A25B3488E26 /* 📦 Check Pods Manifest.lock */, + E080B80F89C34A25B3488E26 /* [CP] Check Pods Manifest.lock */, 05E2127D19D4DB510098F589 /* Sources */, 05E2127E19D4DB510098F589 /* Frameworks */, 05E2127F19D4DB510098F589 /* Resources */, - F012A6F39E0149F18F564F50 /* 📦 Copy Pods Resources */, - 93B7780A33739EF25F20366B /* 📦 Embed Pods Frameworks */, + F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */, + 93B7780A33739EF25F20366B /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -191,14 +191,14 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 93B7780A33739EF25F20366B /* 📦 Embed Pods Frameworks */ = { + 93B7780A33739EF25F20366B /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "📦 Embed Pods Frameworks"; + name = "[CP] Embed Pods Frameworks"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -206,29 +206,32 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - E080B80F89C34A25B3488E26 /* 📦 Check Pods Manifest.lock */ = { + E080B80F89C34A25B3488E26 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); - name = "📦 Check Pods Manifest.lock"; + name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - 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"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - F012A6F39E0149F18F564F50 /* 📦 Copy Pods Resources */ = { + F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "📦 Copy Pods Resources"; + name = "[CP] Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -286,7 +289,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -321,7 +324,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/examples_extra/ASLayoutSpecPlayground-Swift/Podfile b/examples_extra/ASLayoutSpecPlayground-Swift/Podfile index f092b7ba8a..3b379097a0 100644 --- a/examples_extra/ASLayoutSpecPlayground-Swift/Podfile +++ b/examples_extra/ASLayoutSpecPlayground-Swift/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' use_frameworks! target 'Sample' do pod 'Texture', :path => '../..' diff --git a/examples_extra/ASLayoutSpecPlayground-Swift/Sample.xcodeproj/project.pbxproj b/examples_extra/ASLayoutSpecPlayground-Swift/Sample.xcodeproj/project.pbxproj index ced4aae5c4..b77a5e410a 100644 --- a/examples_extra/ASLayoutSpecPlayground-Swift/Sample.xcodeproj/project.pbxproj +++ b/examples_extra/ASLayoutSpecPlayground-Swift/Sample.xcodeproj/project.pbxproj @@ -188,13 +188,16 @@ files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -245,7 +248,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 7.1; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -280,7 +283,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 7.1; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; @@ -305,7 +308,6 @@ GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = Sample/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LIBRARY_SEARCH_PATHS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = com.facebook.AsyncDisplayKit.Sample; @@ -339,7 +341,6 @@ GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = Sample/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LIBRARY_SEARCH_PATHS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = com.facebook.AsyncDisplayKit.Sample; diff --git a/examples_extra/ASLayoutSpecPlayground-Swift/Sample.xcworkspace/contents.xcworkspacedata b/examples_extra/ASLayoutSpecPlayground-Swift/Sample.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..7b5a2f3050 --- /dev/null +++ b/examples_extra/ASLayoutSpecPlayground-Swift/Sample.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/examples_extra/ASTableViewStressTest/Podfile b/examples_extra/ASTableViewStressTest/Podfile index b75e492fab..71a7f2c4b2 100644 --- a/examples_extra/ASTableViewStressTest/Podfile +++ b/examples_extra/ASTableViewStressTest/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples_extra/ASTableViewStressTest/Sample.xcodeproj/project.pbxproj b/examples_extra/ASTableViewStressTest/Sample.xcodeproj/project.pbxproj index 3e68545a8d..c0da5a2015 100644 --- a/examples_extra/ASTableViewStressTest/Sample.xcodeproj/project.pbxproj +++ b/examples_extra/ASTableViewStressTest/Sample.xcodeproj/project.pbxproj @@ -113,12 +113,12 @@ isa = PBXNativeTarget; buildConfigurationList = 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */; buildPhases = ( - E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */, + E080B80F89C34A25B3488E26 /* [CP] Check Pods Manifest.lock */, 05E2127D19D4DB510098F589 /* Sources */, 05E2127E19D4DB510098F589 /* Frameworks */, 05E2127F19D4DB510098F589 /* Resources */, - F012A6F39E0149F18F564F50 /* Copy Pods Resources */, - 75CADB9ECE58AB74892E1D67 /* Embed Pods Frameworks */, + F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */, + 75CADB9ECE58AB74892E1D67 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -175,14 +175,14 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 75CADB9ECE58AB74892E1D67 /* Embed Pods Frameworks */ = { + 75CADB9ECE58AB74892E1D67 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Embed Pods Frameworks"; + name = "[CP] Embed Pods Frameworks"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -190,29 +190,32 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */ = { + E080B80F89C34A25B3488E26 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); - name = "Check Pods Manifest.lock"; + name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - 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"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - F012A6F39E0149F18F564F50 /* Copy Pods Resources */ = { + F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Copy Pods Resources"; + name = "[CP] Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -270,7 +273,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -305,7 +308,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; @@ -318,7 +321,6 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = Sample/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 7.1; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_NAME = "$(TARGET_NAME)"; }; @@ -330,7 +332,6 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = Sample/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 7.1; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_NAME = "$(TARGET_NAME)"; }; diff --git a/examples_extra/ASTableViewStressTest/Sample.xcworkspace/contents.xcworkspacedata b/examples_extra/ASTableViewStressTest/Sample.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..7b5a2f3050 --- /dev/null +++ b/examples_extra/ASTableViewStressTest/Sample.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/examples_extra/ASTraitCollection/Podfile b/examples_extra/ASTraitCollection/Podfile index b75e492fab..71a7f2c4b2 100644 --- a/examples_extra/ASTraitCollection/Podfile +++ b/examples_extra/ASTraitCollection/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples_extra/ASTraitCollection/Sample.xcodeproj/project.pbxproj b/examples_extra/ASTraitCollection/Sample.xcodeproj/project.pbxproj index 20186b37ad..ca9ec160eb 100644 --- a/examples_extra/ASTraitCollection/Sample.xcodeproj/project.pbxproj +++ b/examples_extra/ASTraitCollection/Sample.xcodeproj/project.pbxproj @@ -292,7 +292,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -327,7 +327,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/examples_extra/BackgroundPropertySetting/Podfile b/examples_extra/BackgroundPropertySetting/Podfile index f092b7ba8a..3b379097a0 100644 --- a/examples_extra/BackgroundPropertySetting/Podfile +++ b/examples_extra/BackgroundPropertySetting/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' use_frameworks! target 'Sample' do pod 'Texture', :path => '../..' diff --git a/examples_extra/CollectionViewWithViewControllerCells/Podfile b/examples_extra/CollectionViewWithViewControllerCells/Podfile index b75e492fab..71a7f2c4b2 100644 --- a/examples_extra/CollectionViewWithViewControllerCells/Podfile +++ b/examples_extra/CollectionViewWithViewControllerCells/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples_extra/CollectionViewWithViewControllerCells/Sample.xcodeproj/project.pbxproj b/examples_extra/CollectionViewWithViewControllerCells/Sample.xcodeproj/project.pbxproj index 0c20d49148..718ba91ba8 100644 --- a/examples_extra/CollectionViewWithViewControllerCells/Sample.xcodeproj/project.pbxproj +++ b/examples_extra/CollectionViewWithViewControllerCells/Sample.xcodeproj/project.pbxproj @@ -284,7 +284,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.1; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -320,7 +320,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.1; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/examples_extra/EditableText/Podfile b/examples_extra/EditableText/Podfile index 922ff50ec1..08d1b7add6 100644 --- a/examples_extra/EditableText/Podfile +++ b/examples_extra/EditableText/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples_extra/EditableText/Sample.xcodeproj/project.pbxproj b/examples_extra/EditableText/Sample.xcodeproj/project.pbxproj index 46aa0e9425..2afe6c7b24 100644 --- a/examples_extra/EditableText/Sample.xcodeproj/project.pbxproj +++ b/examples_extra/EditableText/Sample.xcodeproj/project.pbxproj @@ -273,7 +273,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -308,7 +308,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/examples_extra/Multiplex/Podfile b/examples_extra/Multiplex/Podfile index b75e492fab..71a7f2c4b2 100644 --- a/examples_extra/Multiplex/Podfile +++ b/examples_extra/Multiplex/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples_extra/Placeholders/Podfile b/examples_extra/Placeholders/Podfile index b75e492fab..71a7f2c4b2 100644 --- a/examples_extra/Placeholders/Podfile +++ b/examples_extra/Placeholders/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples_extra/Placeholders/Sample.xcodeproj/project.pbxproj b/examples_extra/Placeholders/Sample.xcodeproj/project.pbxproj index e360fd6cd7..6054b17bd9 100644 --- a/examples_extra/Placeholders/Sample.xcodeproj/project.pbxproj +++ b/examples_extra/Placeholders/Sample.xcodeproj/project.pbxproj @@ -298,7 +298,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -333,7 +333,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/examples_extra/RepoSearcher/Podfile b/examples_extra/RepoSearcher/Podfile index ac26415f17..21dc49860f 100644 --- a/examples_extra/RepoSearcher/Podfile +++ b/examples_extra/RepoSearcher/Podfile @@ -1,5 +1,4 @@ -# Uncomment the next line to define a global platform for your project -# platform :ios, '9.0' +platform :ios, '9.0' target 'RepoSearcher' do # Comment the next line if you're not using Swift and don't want to use dynamic frameworks diff --git a/examples_extra/RepoSearcher/RepoSearcher.xcodeproj/project.pbxproj b/examples_extra/RepoSearcher/RepoSearcher.xcodeproj/project.pbxproj index 9439f16227..aa470f0eb7 100644 --- a/examples_extra/RepoSearcher/RepoSearcher.xcodeproj/project.pbxproj +++ b/examples_extra/RepoSearcher/RepoSearcher.xcodeproj/project.pbxproj @@ -330,7 +330,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -380,7 +380,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; diff --git a/examples_extra/Shop/Shop.xcodeproj/project.pbxproj b/examples_extra/Shop/Shop.xcodeproj/project.pbxproj index 836c0e7716..5c9cc401b9 100644 --- a/examples_extra/Shop/Shop.xcodeproj/project.pbxproj +++ b/examples_extra/Shop/Shop.xcodeproj/project.pbxproj @@ -523,7 +523,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -573,7 +573,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; diff --git a/examples_extra/SynchronousConcurrency/Podfile b/examples_extra/SynchronousConcurrency/Podfile index b75e492fab..71a7f2c4b2 100644 --- a/examples_extra/SynchronousConcurrency/Podfile +++ b/examples_extra/SynchronousConcurrency/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples_extra/SynchronousConcurrency/Sample.xcodeproj/project.pbxproj b/examples_extra/SynchronousConcurrency/Sample.xcodeproj/project.pbxproj index 80d3e6e37e..f3964a9562 100644 --- a/examples_extra/SynchronousConcurrency/Sample.xcodeproj/project.pbxproj +++ b/examples_extra/SynchronousConcurrency/Sample.xcodeproj/project.pbxproj @@ -123,12 +123,12 @@ isa = PBXNativeTarget; buildConfigurationList = 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */; buildPhases = ( - E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */, + E080B80F89C34A25B3488E26 /* [CP] Check Pods Manifest.lock */, 05E2127D19D4DB510098F589 /* Sources */, 05E2127E19D4DB510098F589 /* Frameworks */, 05E2127F19D4DB510098F589 /* Resources */, - F012A6F39E0149F18F564F50 /* Copy Pods Resources */, - 0342F7A1563F38A62746D4B8 /* Embed Pods Frameworks */, + F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */, + 0342F7A1563F38A62746D4B8 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -185,14 +185,14 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 0342F7A1563F38A62746D4B8 /* Embed Pods Frameworks */ = { + 0342F7A1563F38A62746D4B8 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Embed Pods Frameworks"; + name = "[CP] Embed Pods Frameworks"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -200,29 +200,32 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */ = { + E080B80F89C34A25B3488E26 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); - name = "Check Pods Manifest.lock"; + name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - 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"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - F012A6F39E0149F18F564F50 /* Copy Pods Resources */ = { + F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Copy Pods Resources"; + name = "[CP] Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -282,7 +285,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -317,7 +320,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/examples_extra/SynchronousConcurrency/Sample.xcworkspace/contents.xcworkspacedata b/examples_extra/SynchronousConcurrency/Sample.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..7b5a2f3050 --- /dev/null +++ b/examples_extra/SynchronousConcurrency/Sample.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/examples_extra/SynchronousConcurrency/Sample/AsyncTableViewController.m b/examples_extra/SynchronousConcurrency/Sample/AsyncTableViewController.m index 3ab470844a..d87c22ea54 100644 --- a/examples_extra/SynchronousConcurrency/Sample/AsyncTableViewController.m +++ b/examples_extra/SynchronousConcurrency/Sample/AsyncTableViewController.m @@ -64,7 +64,7 @@ tuningParameters.leadingBufferScreenfuls = 0.5; tuningParameters.trailingBufferScreenfuls = 1.0; [_tableView setTuningParameters:tuningParameters forRangeType:ASLayoutRangeTypePreload]; - [_tableView setTuningParameters:tuningParameters forRangeType:ASLayoutRangeTypeRender]; + [_tableView setTuningParameters:tuningParameters forRangeType:ASLayoutRangeTypeDisplay]; [self.view addSubview:_tableView]; } @@ -75,7 +75,7 @@ { return ^{ RandomCoreGraphicsNode *elementNode = [[RandomCoreGraphicsNode alloc] init]; - elementNode.size = ASRelativeSizeRangeMakeWithExactCGSize(CGSizeMake(320, 100)); + elementNode.style.preferredSize = CGSizeMake(320, 100); return elementNode; }; } diff --git a/examples_extra/SynchronousConcurrency/Sample/AsyncViewController.h b/examples_extra/SynchronousConcurrency/Sample/AsyncViewController.h index fd357593ca..f307e65d6b 100644 --- a/examples_extra/SynchronousConcurrency/Sample/AsyncViewController.h +++ b/examples_extra/SynchronousConcurrency/Sample/AsyncViewController.h @@ -17,7 +17,7 @@ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#import "ASViewController.h" +#import @interface AsyncViewController : ASViewController diff --git a/examples_extra/SynchronousConcurrency/Sample/RandomCoreGraphicsNode.m b/examples_extra/SynchronousConcurrency/Sample/RandomCoreGraphicsNode.m index d2afbd6e17..66b490df06 100644 --- a/examples_extra/SynchronousConcurrency/Sample/RandomCoreGraphicsNode.m +++ b/examples_extra/SynchronousConcurrency/Sample/RandomCoreGraphicsNode.m @@ -86,7 +86,7 @@ - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize { - [_textNode measure:constrainedSize]; + [_textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, constrainedSize)]; return CGSizeMake(constrainedSize.width, 100); } diff --git a/examples_extra/SynchronousKittens/Podfile b/examples_extra/SynchronousKittens/Podfile index b75e492fab..71a7f2c4b2 100644 --- a/examples_extra/SynchronousKittens/Podfile +++ b/examples_extra/SynchronousKittens/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples_extra/SynchronousKittens/Sample.xcodeproj/project.pbxproj b/examples_extra/SynchronousKittens/Sample.xcodeproj/project.pbxproj index e7ecd2fc26..05a646e3c3 100644 --- a/examples_extra/SynchronousKittens/Sample.xcodeproj/project.pbxproj +++ b/examples_extra/SynchronousKittens/Sample.xcodeproj/project.pbxproj @@ -282,7 +282,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -317,7 +317,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/examples_extra/TextStressTest/Podfile b/examples_extra/TextStressTest/Podfile index 6670022698..73e26195cd 100644 --- a/examples_extra/TextStressTest/Podfile +++ b/examples_extra/TextStressTest/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture/Yoga', :path => '../..' end diff --git a/examples_extra/TextStressTest/Sample.xcodeproj/project.pbxproj b/examples_extra/TextStressTest/Sample.xcodeproj/project.pbxproj index 86a3f35f70..806d9a2e8e 100644 --- a/examples_extra/TextStressTest/Sample.xcodeproj/project.pbxproj +++ b/examples_extra/TextStressTest/Sample.xcodeproj/project.pbxproj @@ -292,7 +292,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -327,7 +327,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/examples_extra/TextStressTest/Sample.xcworkspace/contents.xcworkspacedata b/examples_extra/TextStressTest/Sample.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..7b5a2f3050 --- /dev/null +++ b/examples_extra/TextStressTest/Sample.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/examples_extra/VideoTableView/Podfile b/examples_extra/VideoTableView/Podfile index b75e492fab..71a7f2c4b2 100644 --- a/examples_extra/VideoTableView/Podfile +++ b/examples_extra/VideoTableView/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples_extra/VideoTableView/Sample.xcodeproj/project.pbxproj b/examples_extra/VideoTableView/Sample.xcodeproj/project.pbxproj index 8bf0789b10..c7a68ff0bf 100644 --- a/examples_extra/VideoTableView/Sample.xcodeproj/project.pbxproj +++ b/examples_extra/VideoTableView/Sample.xcodeproj/project.pbxproj @@ -282,7 +282,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -317,7 +317,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/smoke-tests/Framework/Sample.xcodeproj/project.pbxproj b/smoke-tests/Framework/Sample.xcodeproj/project.pbxproj index b4eeb8cc2c..62bb5e98b1 100644 --- a/smoke-tests/Framework/Sample.xcodeproj/project.pbxproj +++ b/smoke-tests/Framework/Sample.xcodeproj/project.pbxproj @@ -298,7 +298,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -334,7 +334,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; 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 75e17d52f7..dd795b7545 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 @@ -410,7 +410,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -447,7 +447,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; From 193be32bcfbc52c2c34d72ee448f38cc9c3eb071 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Mon, 15 Jan 2018 15:13:54 -0800 Subject: [PATCH 06/40] Update dangerfile for 2018 #trivial (#746) * Update the dangerfile * Make a trivial change to test new dangerfile * Try out the new value with another trivial change --- Dangerfile | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Dangerfile b/Dangerfile index 3b81695827..db9f90b33b 100644 --- a/Dangerfile +++ b/Dangerfile @@ -52,7 +52,11 @@ def check_file_header(files_to_check, licenses) correct_license = false licenses.each do |license| license_header = full_license(license, filename) - if data.start_with?(license_header) + # Hack for https://github.com/TextureGroup/Texture/issues/745 + # If it's already a "modified-post-Texture" file, leave it with it original copyright year. + if data.include "Modifications to this file made after 4/13/2017" + correct_license = true + elsif data.start_with?(license_header) correct_license = true end end @@ -87,7 +91,7 @@ modified_source_license_header = <<-HEREDOC // LICENSE file in the /ASDK-Licenses directory of this source tree. An additional // grant of patent rights can be found in the PATENTS file in the same directory. // -// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present, +// Modifications to this file made after 4/13/2017 are: Copyright (c) through the present, // Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at From a3136b02250bfcedfe98c93caabeb554069ad4e7 Mon Sep 17 00:00:00 2001 From: Yevgen Pogribnyi Date: Tue, 16 Jan 2018 20:08:29 +0200 Subject: [PATCH 07/40] [ASTraitCollection] Add missing properties to ASTraitCollection (#625) * [ASTraitCollection] Add missing properties to ASTraitCollection * ASTraitCollection now completely reflects UITraitCollection * Add ASContentSizeCategory enum that corresponds to UIContentSizeCategory and can be used inside a struct. * * Remove enum ASContentSizeCategory. * Use __unsafe_unretained UIContentSizeCategory instead of the enum. * Added ASPrimitiveTraitCollection lifetime test * Changes requested at code review: * Restore one of the ASTraitCollection constructors with a deprecation notice. * Clean up API by the separation of tvOS-specific interfaces. * Use [NSString -isEqualToString:] for ASPrimitiveContentSizeCategory equality tests for better readability. * Encapsulate fallback logic for UIContentSizeCategoryUnspecified. * Fix failing test --- AsyncDisplayKit.xcodeproj/project.pbxproj | 4 + CHANGELOG.md | 1 + Source/Details/ASTraitCollection.h | 100 +++++- Source/Details/ASTraitCollection.m | 405 +++++++++++++++++++--- Tests/ASCollectionViewTests.mm | 2 +- Tests/ASTraitCollectionTests.m | 34 ++ 6 files changed, 479 insertions(+), 67 deletions(-) create mode 100644 Tests/ASTraitCollectionTests.m diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index a04b064214..1cff2cac06 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -99,6 +99,7 @@ 3917EBD41E9C2FC400D04A01 /* _ASCollectionReusableView.h in Headers */ = {isa = PBXBuildFile; fileRef = 3917EBD21E9C2FC400D04A01 /* _ASCollectionReusableView.h */; settings = {ATTRIBUTES = (Private, ); }; }; 3917EBD51E9C2FC400D04A01 /* _ASCollectionReusableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3917EBD31E9C2FC400D04A01 /* _ASCollectionReusableView.m */; }; 3C9C128519E616EF00E942A0 /* ASTableViewTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3C9C128419E616EF00E942A0 /* ASTableViewTests.mm */; }; + 4496D0731FA9EA6B001CC8D5 /* ASTraitCollectionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4496D0721FA9EA6B001CC8D5 /* ASTraitCollectionTests.m */; }; 4E9127691F64157600499623 /* ASRunLoopQueueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E9127681F64157600499623 /* ASRunLoopQueueTests.m */; }; 509E68601B3AED8E009B9150 /* ASScrollDirection.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E111B371BD7007741D0 /* ASScrollDirection.m */; }; 509E68611B3AEDA0009B9150 /* ASAbstractLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E171B37339C007741D0 /* ASAbstractLayoutController.h */; }; @@ -634,6 +635,7 @@ 3917EBD21E9C2FC400D04A01 /* _ASCollectionReusableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASCollectionReusableView.h; sourceTree = ""; }; 3917EBD31E9C2FC400D04A01 /* _ASCollectionReusableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = _ASCollectionReusableView.m; sourceTree = ""; }; 3C9C128419E616EF00E942A0 /* ASTableViewTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASTableViewTests.mm; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 4496D0721FA9EA6B001CC8D5 /* ASTraitCollectionTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASTraitCollectionTests.m; sourceTree = ""; }; 464052191A3F83C40061C0BA /* ASDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASDataController.h; sourceTree = ""; }; 4640521A1A3F83C40061C0BA /* ASDataController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASDataController.mm; sourceTree = ""; }; 4640521B1A3F83C40061C0BA /* ASTableLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTableLayoutController.h; sourceTree = ""; }; @@ -1256,6 +1258,7 @@ 695BE2541DC1245C008E6EA5 /* ASWrapperSpecSnapshotTests.mm */, 699B83501E3C1BA500433FA4 /* ASLayoutSpecTests.m */, 4E9127681F64157600499623 /* ASRunLoopQueueTests.m */, + 4496D0721FA9EA6B001CC8D5 /* ASTraitCollectionTests.m */, ); path = Tests; sourceTree = ""; @@ -2163,6 +2166,7 @@ buildActionMask = 2147483647; files = ( E51B78BF1F028ABF00E32604 /* ASLayoutFlatteningTests.m in Sources */, + 4496D0731FA9EA6B001CC8D5 /* ASTraitCollectionTests.m in Sources */, 29CDC2E21AAE70D000833CA4 /* ASBasicImageDownloaderContextTests.m in Sources */, CC583AD71EF9BDC100134156 /* NSInvocation+ASTestHelpers.m in Sources */, CC051F1F1D7A286A006434CB /* ASCALayerTests.m in Sources */, diff --git a/CHANGELOG.md b/CHANGELOG.md index c884b80232..87165dc7e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## master * Add your own contributions to the next release on the line below this with your name. +- [ASTraitCollection] Add new properties of UITraitCollection to ASTraitCollection. [Yevgen Pogribnyi](https://github.com/ypogribnyi) - [ASRectMap] Replace implementation of ASRectTable with a simpler one based on unordered_map.[Scott Goodson](https://github.com/appleguy) [#719](https://github.com/TextureGroup/Texture/pull/719) - [ASCollectionView] Add missing flags for ASCollectionDelegate [Ilya Zheleznikov](https://github.com/ilyailya) [#718](https://github.com/TextureGroup/Texture/pull/718) - [ASNetworkImageNode] Deprecates .URLs in favor of .URL [Garrett Moon](https://github.com/garrettmoon) [#699](https://github.com/TextureGroup/Texture/pull/699) diff --git a/Source/Details/ASTraitCollection.h b/Source/Details/ASTraitCollection.h index 26714aa649..7f8ae3a476 100644 --- a/Source/Details/ASTraitCollection.h +++ b/Source/Details/ASTraitCollection.h @@ -17,6 +17,7 @@ #import + #import @class ASTraitCollection; @@ -27,14 +28,51 @@ NS_ASSUME_NONNULL_BEGIN ASDISPLAYNODE_EXTERN_C_BEGIN +#pragma mark - ASPrimitiveContentSizeCategory + +/** + * ASPrimitiveContentSizeCategory is a UIContentSizeCategory that can be used inside a struct. + * + * We need an unretained pointer because ARC can't manage struct memory. + * + * WARNING: DO NOT cast UIContentSizeCategory values to ASPrimitiveContentSizeCategory directly. + * Use ASPrimitiveContentSizeCategoryMake(UIContentSizeCategory) instead. + * This is because we make some assumptions about the lifetime of the object it points to. + * Also note that cast from ASPrimitiveContentSizeCategory to UIContentSizeCategory is always safe. + */ +typedef __unsafe_unretained UIContentSizeCategory ASPrimitiveContentSizeCategory; + +/** + * Safely casts from UIContentSizeCategory to ASPrimitiveContentSizeCategory. + * + * The UIKit documentation doesn't specify if we can receive a copy of the UIContentSizeCategory constant. While getting + * copies is fine with ARC, usage of unretained pointers requires us to ensure the lifetime of the object it points to. + * Manual retain&release of the UIContentSizeCategory object is not an option because it would require us to do that + * everywhere ASPrimitiveTraitCollection is used. This is error-prone and can lead to crashes and memory leaks. So, we + * explicitly limit possible values of ASPrimitiveContentSizeCategory to the predetermined set of global constants with + * known lifetime. + * + * @return a pointer to one of the UIContentSizeCategory constants. + */ +extern ASPrimitiveContentSizeCategory ASPrimitiveContentSizeCategoryMake(UIContentSizeCategory sizeCategory); + #pragma mark - ASPrimitiveTraitCollection typedef struct ASPrimitiveTraitCollection { - CGFloat displayScale; UIUserInterfaceSizeClass horizontalSizeClass; - UIUserInterfaceIdiom userInterfaceIdiom; UIUserInterfaceSizeClass verticalSizeClass; + + CGFloat displayScale; + UIDisplayGamut displayGamut; + + UIUserInterfaceIdiom userInterfaceIdiom; UIForceTouchCapability forceTouchCapability; + UITraitEnvironmentLayoutDirection layoutDirection; +#if TARGET_OS_TV + UIUserInterfaceStyle userInterfaceStyle; +#endif + + ASPrimitiveContentSizeCategory preferredContentSizeCategory; CGSize containerSize; } ASPrimitiveTraitCollection; @@ -124,11 +162,21 @@ ASDISPLAYNODE_EXTERN_C_END AS_SUBCLASSING_RESTRICTED @interface ASTraitCollection : NSObject -@property (nonatomic, assign, readonly) CGFloat displayScale; @property (nonatomic, assign, readonly) UIUserInterfaceSizeClass horizontalSizeClass; -@property (nonatomic, assign, readonly) UIUserInterfaceIdiom userInterfaceIdiom; @property (nonatomic, assign, readonly) UIUserInterfaceSizeClass verticalSizeClass; + +@property (nonatomic, assign, readonly) CGFloat displayScale; +@property (nonatomic, assign, readonly) UIDisplayGamut displayGamut; + +@property (nonatomic, assign, readonly) UIUserInterfaceIdiom userInterfaceIdiom; @property (nonatomic, assign, readonly) UIForceTouchCapability forceTouchCapability; +@property (nonatomic, assign, readonly) UITraitEnvironmentLayoutDirection layoutDirection; +#if TARGET_OS_TV +@property (nonatomic, assign, readonly) UIUserInterfaceStyle userInterfaceStyle; +#endif + +@property (nonatomic, assign, readonly) UIContentSizeCategory preferredContentSizeCategory; + @property (nonatomic, assign, readonly) CGSize containerSize; + (ASTraitCollection *)traitCollectionWithASPrimitiveTraitCollection:(ASPrimitiveTraitCollection)traits; @@ -136,18 +184,48 @@ AS_SUBCLASSING_RESTRICTED + (ASTraitCollection *)traitCollectionWithUITraitCollection:(UITraitCollection *)traitCollection containerSize:(CGSize)windowSize; ++ (ASTraitCollection *)traitCollectionWithUITraitCollection:(UITraitCollection *)traitCollection + containerSize:(CGSize)windowSize + fallbackContentSizeCategory:(UIContentSizeCategory)fallbackContentSizeCategory; -+ (ASTraitCollection *)traitCollectionWithDisplayScale:(CGFloat)displayScale - userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom - horizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass - verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass - forceTouchCapability:(UIForceTouchCapability)forceTouchCapability - containerSize:(CGSize)windowSize; - +#if TARGET_OS_TV ++ (ASTraitCollection *)traitCollectionWithHorizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass + verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass + displayScale:(CGFloat)displayScale + displayGamut:(UIDisplayGamut)displayGamut + userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom + forceTouchCapability:(UIForceTouchCapability)forceTouchCapability + layoutDirection:(UITraitEnvironmentLayoutDirection)layoutDirection + userInterfaceStyle:(UIUserInterfaceStyle)userInterfaceStyle + preferredContentSizeCategory:(UIContentSizeCategory)preferredContentSizeCategory + containerSize:(CGSize)windowSize; +#else ++ (ASTraitCollection *)traitCollectionWithHorizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass + verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass + displayScale:(CGFloat)displayScale + displayGamut:(UIDisplayGamut)displayGamut + userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom + forceTouchCapability:(UIForceTouchCapability)forceTouchCapability + layoutDirection:(UITraitEnvironmentLayoutDirection)layoutDirection + preferredContentSizeCategory:(UIContentSizeCategory)preferredContentSizeCategory + containerSize:(CGSize)windowSize; +#endif - (ASPrimitiveTraitCollection)primitiveTraitCollection; - (BOOL)isEqualToTraitCollection:(ASTraitCollection *)traitCollection; @end +@interface ASTraitCollection (Deprecated) + ++ (ASTraitCollection *)traitCollectionWithDisplayScale:(CGFloat)displayScale + userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom + horizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass + verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass + forceTouchCapability:(UIForceTouchCapability)forceTouchCapability + containerSize:(CGSize)windowSize + ASDISPLAYNODE_DEPRECATED_MSG("Use full version of this method instead."); + +@end + NS_ASSUME_NONNULL_END diff --git a/Source/Details/ASTraitCollection.m b/Source/Details/ASTraitCollection.m index 570095ec41..dba756bafa 100644 --- a/Source/Details/ASTraitCollection.m +++ b/Source/Details/ASTraitCollection.m @@ -20,6 +20,60 @@ #import #import +#pragma mark - ASPrimitiveContentSizeCategory + +// UIContentSizeCategoryUnspecified is available only in iOS 10.0 and later. +// This is used for compatibility with older iOS versions. +ASDISPLAYNODE_INLINE UIContentSizeCategory AS_UIContentSizeCategoryUnspecified() { + if (AS_AVAILABLE_IOS(10)) { + return UIContentSizeCategoryUnspecified; + } else { + return @"_UICTContentSizeCategoryUnspecified"; + } +} + +ASPrimitiveContentSizeCategory ASPrimitiveContentSizeCategoryMake(UIContentSizeCategory sizeCategory) { + if ([sizeCategory isEqualToString:UIContentSizeCategoryExtraSmall]) { + return UIContentSizeCategoryExtraSmall; + } + if ([sizeCategory isEqualToString:UIContentSizeCategorySmall]) { + return UIContentSizeCategorySmall; + } + if ([sizeCategory isEqualToString:UIContentSizeCategoryMedium]) { + return UIContentSizeCategoryMedium; + } + if ([sizeCategory isEqualToString:UIContentSizeCategoryLarge]) { + return UIContentSizeCategoryLarge; + } + if ([sizeCategory isEqualToString:UIContentSizeCategoryExtraLarge]) { + return UIContentSizeCategoryExtraLarge; + } + if ([sizeCategory isEqualToString:UIContentSizeCategoryExtraExtraLarge]) { + return UIContentSizeCategoryExtraExtraLarge; + } + if ([sizeCategory isEqualToString:UIContentSizeCategoryExtraExtraExtraLarge]) { + return UIContentSizeCategoryExtraExtraExtraLarge; + } + + if ([sizeCategory isEqualToString:UIContentSizeCategoryAccessibilityMedium]) { + return UIContentSizeCategoryAccessibilityMedium; + } + if ([sizeCategory isEqualToString:UIContentSizeCategoryAccessibilityLarge]) { + return UIContentSizeCategoryAccessibilityLarge; + } + if ([sizeCategory isEqualToString:UIContentSizeCategoryAccessibilityExtraLarge]) { + return UIContentSizeCategoryAccessibilityExtraLarge; + } + if ([sizeCategory isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraLarge]) { + return UIContentSizeCategoryAccessibilityExtraExtraLarge; + } + if ([sizeCategory isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraExtraLarge]) { + return UIContentSizeCategoryAccessibilityExtraExtraExtraLarge; + } + + return AS_UIContentSizeCategoryUnspecified(); +} + #pragma mark - ASPrimitiveTraitCollection extern void ASTraitCollectionPropagateDown(id element, ASPrimitiveTraitCollection traitCollection) { @@ -32,36 +86,57 @@ extern void ASTraitCollectionPropagateDown(id element, ASPrimit } } -ASPrimitiveTraitCollection ASPrimitiveTraitCollectionMakeDefault() -{ +ASPrimitiveTraitCollection ASPrimitiveTraitCollectionMakeDefault() { return (ASPrimitiveTraitCollection) { // Default values can be defined in here + .displayGamut = UIDisplayGamutUnspecified, .userInterfaceIdiom = UIUserInterfaceIdiomUnspecified, + .layoutDirection = UITraitEnvironmentLayoutDirectionUnspecified, + .preferredContentSizeCategory = ASPrimitiveContentSizeCategoryMake(AS_UIContentSizeCategoryUnspecified()), .containerSize = CGSizeZero, }; } -ASPrimitiveTraitCollection ASPrimitiveTraitCollectionFromUITraitCollection(UITraitCollection *traitCollection) -{ +ASPrimitiveTraitCollection ASPrimitiveTraitCollectionFromUITraitCollection(UITraitCollection *traitCollection) { ASPrimitiveTraitCollection environmentTraitCollection = ASPrimitiveTraitCollectionMakeDefault(); - environmentTraitCollection.displayScale = traitCollection.displayScale; environmentTraitCollection.horizontalSizeClass = traitCollection.horizontalSizeClass; environmentTraitCollection.verticalSizeClass = traitCollection.verticalSizeClass; + environmentTraitCollection.displayScale = traitCollection.displayScale; environmentTraitCollection.userInterfaceIdiom = traitCollection.userInterfaceIdiom; - if (AS_AVAILABLE_IOS(9)) { - environmentTraitCollection.forceTouchCapability = traitCollection.forceTouchCapability; + environmentTraitCollection.forceTouchCapability = traitCollection.forceTouchCapability; + if (AS_AVAILABLE_IOS(10)) { + environmentTraitCollection.displayGamut = traitCollection.displayGamut; + environmentTraitCollection.layoutDirection = traitCollection.layoutDirection; + + // preferredContentSizeCategory is also available on older iOS versions, but only via UIApplication class. + // It should be noted that [UIApplication sharedApplication] is unavailable because Texture is built with only extension-safe API. + environmentTraitCollection.preferredContentSizeCategory = ASPrimitiveContentSizeCategoryMake(traitCollection.preferredContentSizeCategory); + + #if TARGET_OS_TV + environmentTraitCollection.userInterfaceStyle = traitCollection.userInterfaceStyle; + #endif } return environmentTraitCollection; } -BOOL ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(ASPrimitiveTraitCollection lhs, ASPrimitiveTraitCollection rhs) -{ +BOOL ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(ASPrimitiveTraitCollection lhs, ASPrimitiveTraitCollection rhs) { + UIContentSizeCategory leftSizeCategory = (UIContentSizeCategory)lhs.preferredContentSizeCategory; + UIContentSizeCategory rightSizeCategory = (UIContentSizeCategory)rhs.preferredContentSizeCategory; + return lhs.verticalSizeClass == rhs.verticalSizeClass && lhs.horizontalSizeClass == rhs.horizontalSizeClass && lhs.displayScale == rhs.displayScale && + lhs.displayGamut == rhs.displayGamut && lhs.userInterfaceIdiom == rhs.userInterfaceIdiom && lhs.forceTouchCapability == rhs.forceTouchCapability && + lhs.layoutDirection == rhs.layoutDirection && + #if TARGET_OS_TV + lhs.userInterfaceStyle == rhs.userInterfaceStyle && + #endif + + [leftSizeCategory isEqualToString:rightSizeCategory] && // Simple pointer comparison should be sufficient here + CGSizeEqualToSize(lhs.containerSize, rhs.containerSize); } @@ -105,14 +180,58 @@ ASDISPLAYNODE_INLINE NSString *AS_NSStringFromUIUserInterfaceSizeClass(UIUserInt } } -NSString *NSStringFromASPrimitiveTraitCollection(ASPrimitiveTraitCollection traits) -{ +// Named so as not to conflict with a hidden Apple function, in case compiler decides not to inline +ASDISPLAYNODE_INLINE NSString *AS_NSStringFromUIDisplayGamut(UIDisplayGamut displayGamut) { + switch (displayGamut) { + case UIDisplayGamutSRGB: + return @"sRGB"; + case UIDisplayGamutP3: + return @"P3"; + default: + return @"Unspecified"; + } +} + +// Named so as not to conflict with a hidden Apple function, in case compiler decides not to inline +ASDISPLAYNODE_INLINE NSString *AS_NSStringFromUITraitEnvironmentLayoutDirection(UITraitEnvironmentLayoutDirection layoutDirection) { + switch (layoutDirection) { + case UITraitEnvironmentLayoutDirectionLeftToRight: + return @"LeftToRight"; + case UITraitEnvironmentLayoutDirectionRightToLeft: + return @"RightToLeft"; + default: + return @"Unspecified"; + } +} + +#if TARGET_OS_TV +// Named so as not to conflict with a hidden Apple function, in case compiler decides not to inline +ASDISPLAYNODE_INLINE NSString *AS_NSStringFromUIUserInterfaceStyle(UIUserInterfaceStyle userInterfaceStyle) { + switch (userInterfaceStyle) { + case UIUserInterfaceStyleLight: + return @"Light"; + case UIUserInterfaceStyleDark: + return @"Dark"; + default: + return @"Unspecified"; + } +} +#endif + +NSString *NSStringFromASPrimitiveTraitCollection(ASPrimitiveTraitCollection traits) { NSMutableArray *props = [NSMutableArray array]; - [props addObject:@{ @"userInterfaceIdiom": AS_NSStringFromUIUserInterfaceIdiom(traits.userInterfaceIdiom) }]; - [props addObject:@{ @"containerSize": NSStringFromCGSize(traits.containerSize) }]; - [props addObject:@{ @"horizontalSizeClass": AS_NSStringFromUIUserInterfaceSizeClass(traits.horizontalSizeClass) }]; [props addObject:@{ @"verticalSizeClass": AS_NSStringFromUIUserInterfaceSizeClass(traits.verticalSizeClass) }]; + [props addObject:@{ @"horizontalSizeClass": AS_NSStringFromUIUserInterfaceSizeClass(traits.horizontalSizeClass) }]; + [props addObject:@{ @"displayScale": [NSString stringWithFormat: @"%.0lf", (double)traits.displayScale] }]; + [props addObject:@{ @"displayGamut": AS_NSStringFromUIDisplayGamut(traits.displayGamut) }]; + [props addObject:@{ @"userInterfaceIdiom": AS_NSStringFromUIUserInterfaceIdiom(traits.userInterfaceIdiom) }]; [props addObject:@{ @"forceTouchCapability": AS_NSStringFromUIForceTouchCapability(traits.forceTouchCapability) }]; + [props addObject:@{ @"layoutDirection": AS_NSStringFromUITraitEnvironmentLayoutDirection(traits.layoutDirection) }]; + #if TARGET_OS_TV + [props addObject:@{ @"userInterfaceStyle": AS_NSStringFromUIUserInterfaceStyle(traits.userInterfaceStyle) }]; + #endif + [props addObject:@{ @"preferredContentSizeCategory": (UIContentSizeCategory)traits.preferredContentSizeCategory }]; + [props addObject:@{ @"containerSize": NSStringFromCGSize(traits.containerSize) }]; return ASObjectDescriptionMakeWithoutObject(props); } @@ -120,69 +239,238 @@ NSString *NSStringFromASPrimitiveTraitCollection(ASPrimitiveTraitCollection trai @implementation ASTraitCollection -- (instancetype)initWithDisplayScale:(CGFloat)displayScale - userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom - horizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass - verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass - forceTouchCapability:(UIForceTouchCapability)forceTouchCapability - containerSize:(CGSize)windowSize +#if TARGET_OS_TV + +- (instancetype)initWithHorizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass + verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass + displayScale:(CGFloat)displayScale + displayGamut:(UIDisplayGamut)displayGamut + userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom + forceTouchCapability:(UIForceTouchCapability)forceTouchCapability + layoutDirection:(UITraitEnvironmentLayoutDirection)layoutDirection + userInterfaceStyle:(UIUserInterfaceStyle)userInterfaceStyle + preferredContentSizeCategory:(UIContentSizeCategory)preferredContentSizeCategory + containerSize:(CGSize)windowSize { self = [super init]; if (self) { - _displayScale = displayScale; - _userInterfaceIdiom = userInterfaceIdiom; _horizontalSizeClass = horizontalSizeClass; _verticalSizeClass = verticalSizeClass; + _displayScale = displayScale; + _displayGamut = displayGamut; + _userInterfaceIdiom = userInterfaceIdiom; _forceTouchCapability = forceTouchCapability; + _layoutDirection = layoutDirection; + _userInterfaceStyle = userInterfaceStyle; + _preferredContentSizeCategory = preferredContentSizeCategory; _containerSize = windowSize; } return self; } -+ (instancetype)traitCollectionWithDisplayScale:(CGFloat)displayScale - userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom - horizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass - verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass - forceTouchCapability:(UIForceTouchCapability)forceTouchCapability - containerSize:(CGSize)windowSize ++ (instancetype)traitCollectionWithHorizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass + verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass + displayScale:(CGFloat)displayScale + displayGamut:(UIDisplayGamut)displayGamut + userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom + forceTouchCapability:(UIForceTouchCapability)forceTouchCapability + layoutDirection:(UITraitEnvironmentLayoutDirection)layoutDirection + userInterfaceStyle:(UIUserInterfaceStyle)userInterfaceStyle + preferredContentSizeCategory:(UIContentSizeCategory)preferredContentSizeCategory + containerSize:(CGSize)windowSize { - return [[self alloc] initWithDisplayScale:displayScale - userInterfaceIdiom:userInterfaceIdiom - horizontalSizeClass:horizontalSizeClass - verticalSizeClass:verticalSizeClass - forceTouchCapability:forceTouchCapability - containerSize:windowSize]; + return [[self alloc] initWithHorizontalSizeClass:horizontalSizeClass + verticalSizeClass:verticalSizeClass + displayScale:displayScale + displayGamut:displayGamut + userInterfaceIdiom:userInterfaceIdiom + forceTouchCapability:forceTouchCapability + layoutDirection:layoutDirection + userInterfaceStyle:userIntefaceStyle + preferredContentSizeCategory:preferredContentSizeCategory + containerSize:windowSize]; +} + +#else + +- (instancetype)initWithHorizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass + verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass + displayScale:(CGFloat)displayScale + displayGamut:(UIDisplayGamut)displayGamut + userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom + forceTouchCapability:(UIForceTouchCapability)forceTouchCapability + layoutDirection:(UITraitEnvironmentLayoutDirection)layoutDirection + preferredContentSizeCategory:(UIContentSizeCategory)preferredContentSizeCategory + containerSize:(CGSize)windowSize +{ + self = [super init]; + if (self) { + _horizontalSizeClass = horizontalSizeClass; + _verticalSizeClass = verticalSizeClass; + _displayScale = displayScale; + _displayGamut = displayGamut; + _userInterfaceIdiom = userInterfaceIdiom; + _forceTouchCapability = forceTouchCapability; + _layoutDirection = layoutDirection; + _preferredContentSizeCategory = preferredContentSizeCategory; + _containerSize = windowSize; + } + return self; +} + ++ (instancetype)traitCollectionWithHorizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass + verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass + displayScale:(CGFloat)displayScale + displayGamut:(UIDisplayGamut)displayGamut + userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom + forceTouchCapability:(UIForceTouchCapability)forceTouchCapability + layoutDirection:(UITraitEnvironmentLayoutDirection)layoutDirection + preferredContentSizeCategory:(UIContentSizeCategory)preferredContentSizeCategory + containerSize:(CGSize)windowSize +{ + return [[self alloc] initWithHorizontalSizeClass:horizontalSizeClass + verticalSizeClass:verticalSizeClass + displayScale:displayScale + displayGamut:displayGamut + userInterfaceIdiom:userInterfaceIdiom + forceTouchCapability:forceTouchCapability + layoutDirection:layoutDirection + preferredContentSizeCategory:preferredContentSizeCategory + containerSize:windowSize]; +} + +#endif + ++ (ASTraitCollection *)traitCollectionWithDisplayScale:(CGFloat)displayScale + userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom + horizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass + verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass + forceTouchCapability:(UIForceTouchCapability)forceTouchCapability + containerSize:(CGSize)windowSize +{ +#if TARGET_OS_TV + return [self traitCollectionWithHorizontalSizeClass:horizontalSizeClass + verticalSizeClass:verticalSizeClass + displayScale:displayScale + displayGamut:UIDisplayGamutUnspecified + userInterfaceIdiom:userInterfaceIdiom + forceTouchCapability:forceTouchCapability + layoutDirection:UITraitEnvironmentLayoutDirectionUnspecified + userInterfaceStyle:UIUserInterfaceStyleUnspecified + preferredContentSizeCategory:AS_UIContentSizeCategoryUnspecified() + containerSize:windowSize]; +#else + return [self traitCollectionWithHorizontalSizeClass:horizontalSizeClass + verticalSizeClass:verticalSizeClass + displayScale:displayScale + displayGamut:UIDisplayGamutUnspecified + userInterfaceIdiom:userInterfaceIdiom + forceTouchCapability:forceTouchCapability + layoutDirection:UITraitEnvironmentLayoutDirectionUnspecified + preferredContentSizeCategory:AS_UIContentSizeCategoryUnspecified() + containerSize:windowSize]; +#endif } + (instancetype)traitCollectionWithASPrimitiveTraitCollection:(ASPrimitiveTraitCollection)traits { - return [self traitCollectionWithDisplayScale:traits.displayScale - userInterfaceIdiom:traits.userInterfaceIdiom - horizontalSizeClass:traits.horizontalSizeClass - verticalSizeClass:traits.verticalSizeClass - forceTouchCapability:traits.forceTouchCapability - containerSize:traits.containerSize]; +#if TARGET_OS_TV + return [self traitCollectionWithHorizontalSizeClass:traits.horizontalSizeClass + verticalSizeClass:traits.verticalSizeClass + displayScale:traits.displayScale + displayGamut:traits.displayGamut + userInterfaceIdiom:traits.userInterfaceIdiom + forceTouchCapability:traits.forceTouchCapability + layoutDirection:traits.layoutDirection + userInterfaceStyle:traits.userInterfaceStyle + preferredContentSizeCategory:(UIContentSizeCategory)traits.preferredContentSizeCategory + containerSize:traits.containerSize]; +#else + return [self traitCollectionWithHorizontalSizeClass:traits.horizontalSizeClass + verticalSizeClass:traits.verticalSizeClass + displayScale:traits.displayScale + displayGamut:traits.displayGamut + userInterfaceIdiom:traits.userInterfaceIdiom + forceTouchCapability:traits.forceTouchCapability + layoutDirection:traits.layoutDirection + preferredContentSizeCategory:(UIContentSizeCategory)traits.preferredContentSizeCategory + containerSize:traits.containerSize]; +#endif } + (instancetype)traitCollectionWithUITraitCollection:(UITraitCollection *)traitCollection - containerSize:(CGSize)windowSize + containerSize:(CGSize)windowSize { - return [self traitCollectionWithDisplayScale:traitCollection.displayScale - userInterfaceIdiom:traitCollection.userInterfaceIdiom - horizontalSizeClass:traitCollection.horizontalSizeClass - verticalSizeClass:traitCollection.verticalSizeClass - forceTouchCapability:traitCollection.forceTouchCapability - containerSize:windowSize]; + return [self traitCollectionWithUITraitCollection:traitCollection + containerSize:windowSize + fallbackContentSizeCategory:AS_UIContentSizeCategoryUnspecified()]; +} + + ++ (instancetype)traitCollectionWithUITraitCollection:(UITraitCollection *)traitCollection + containerSize:(CGSize)windowSize + fallbackContentSizeCategory:(UIContentSizeCategory)fallbackContentSizeCategory +{ + UIDisplayGamut displayGamut; + UITraitEnvironmentLayoutDirection layoutDirection; + UIContentSizeCategory sizeCategory; + #if TARGET_OS_TV + UIUserInterfaceStyle userInterfaceStyle; + #endif + if (AS_AVAILABLE_IOS(10)) { + displayGamut = traitCollection.displayGamut; + layoutDirection = traitCollection.layoutDirection; + sizeCategory = traitCollection.preferredContentSizeCategory; + #if TARGET_OS_TV + userInterfaceStyle = traitCollection.userInterfaceStyle; + #endif + } else { + displayGamut = UIDisplayGamutUnspecified; + layoutDirection = UITraitEnvironmentLayoutDirectionUnspecified; + sizeCategory = fallbackContentSizeCategory; + #if TARGET_OS_TV + userInterfaceStyle = UIUserInterfaceStyleUnspecified; + #endif + } + +#if TARGET_OS_TV + return [self traitCollectionWithHorizontalSizeClass:traitCollection.horizontalSizeClass + verticalSizeClass:traitCollection.verticalSizeClass + displayScale:traitCollection.displayScale + displayGamut:displayGamut + userInterfaceIdiom:traitCollection.userInterfaceIdiom + forceTouchCapability:traitCollection.forceTouchCapability + layoutDirection:layoutDirection + userInterfaceStyle:userInterfaceStyle + preferredContentSizeCategory:sizeCategory + containerSize:windowSize]; +#else + return [self traitCollectionWithHorizontalSizeClass:traitCollection.horizontalSizeClass + verticalSizeClass:traitCollection.verticalSizeClass + displayScale:traitCollection.displayScale + displayGamut:displayGamut + userInterfaceIdiom:traitCollection.userInterfaceIdiom + forceTouchCapability:traitCollection.forceTouchCapability + layoutDirection:layoutDirection + preferredContentSizeCategory:sizeCategory + containerSize:windowSize]; +#endif } - (ASPrimitiveTraitCollection)primitiveTraitCollection { return (ASPrimitiveTraitCollection) { - .displayScale = self.displayScale, .horizontalSizeClass = self.horizontalSizeClass, - .userInterfaceIdiom = self.userInterfaceIdiom, .verticalSizeClass = self.verticalSizeClass, + .displayScale = self.displayScale, + .displayGamut = self.displayGamut, + .userInterfaceIdiom = self.userInterfaceIdiom, .forceTouchCapability = self.forceTouchCapability, + .layoutDirection = self.layoutDirection, + #if TARGET_OS_TV + .userInterfaceStyle = self.userInterfaceStyle, + #endif + .preferredContentSizeCategory = ASPrimitiveContentSizeCategoryMake(self.preferredContentSizeCategory), .containerSize = self.containerSize, }; } @@ -193,12 +481,19 @@ NSString *NSStringFromASPrimitiveTraitCollection(ASPrimitiveTraitCollection trai return YES; } - return self.displayScale == traitCollection.displayScale && - self.horizontalSizeClass == traitCollection.horizontalSizeClass && - self.verticalSizeClass == traitCollection.verticalSizeClass && - self.userInterfaceIdiom == traitCollection.userInterfaceIdiom && - CGSizeEqualToSize(self.containerSize, traitCollection.containerSize) && - self.forceTouchCapability == traitCollection.forceTouchCapability; + return + self.horizontalSizeClass == traitCollection.horizontalSizeClass && + self.verticalSizeClass == traitCollection.verticalSizeClass && + self.displayScale == traitCollection.displayScale && + self.displayGamut == traitCollection.displayGamut && + self.userInterfaceIdiom == traitCollection.userInterfaceIdiom && + self.forceTouchCapability == traitCollection.forceTouchCapability && + self.layoutDirection == traitCollection.layoutDirection && + #if TARGET_OS_TV + self.userInterfaceStyle == traitCollection.userInterfaceStyle && + #endif + [self.preferredContentSizeCategory isEqualToString:traitCollection.preferredContentSizeCategory] && + CGSizeEqualToSize(self.containerSize, traitCollection.containerSize); } @end diff --git a/Tests/ASCollectionViewTests.mm b/Tests/ASCollectionViewTests.mm index 06c6fcd890..0391e992da 100644 --- a/Tests/ASCollectionViewTests.mm +++ b/Tests/ASCollectionViewTests.mm @@ -1097,7 +1097,7 @@ [window layoutIfNeeded]; // The initial reload is async, changing the trait collection here should be "mid-update" - ASPrimitiveTraitCollection traitCollection; + ASPrimitiveTraitCollection traitCollection = ASPrimitiveTraitCollectionMakeDefault(); traitCollection.displayScale = cn.primitiveTraitCollection.displayScale + 1; // Just a dummy change traitCollection.containerSize = screenBounds.size; cn.primitiveTraitCollection = traitCollection; diff --git a/Tests/ASTraitCollectionTests.m b/Tests/ASTraitCollectionTests.m new file mode 100644 index 0000000000..aa106d75ec --- /dev/null +++ b/Tests/ASTraitCollectionTests.m @@ -0,0 +1,34 @@ +// +// ASTraitCollectionTests.m +// Texture +// +// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import +#import + +@interface ASTraitCollectionTests : XCTestCase + +@end + +@implementation ASTraitCollectionTests + +- (void)testPrimitiveContentSizeCategoryLifetime +{ + ASPrimitiveContentSizeCategory primitiveContentSize; + @autoreleasepool { + // Make sure the compiler won't optimize string alloc/dealloc + NSString *contentSizeCategory = [NSString stringWithCString:"UICTContentSizeCategoryL" encoding:NSUTF8StringEncoding]; + primitiveContentSize = ASPrimitiveContentSizeCategoryMake(contentSizeCategory); + } + + XCTAssertEqual(primitiveContentSize, UIContentSizeCategoryLarge); +} + +@end From 4776cb3dcdaf85e6aaf3fed18870a4dacbe821ff Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Tue, 16 Jan 2018 12:55:27 -0800 Subject: [PATCH 08/40] Fix the dangerfile for real (#750) --- Dangerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dangerfile b/Dangerfile index db9f90b33b..e9779d6103 100644 --- a/Dangerfile +++ b/Dangerfile @@ -54,7 +54,7 @@ def check_file_header(files_to_check, licenses) license_header = full_license(license, filename) # Hack for https://github.com/TextureGroup/Texture/issues/745 # If it's already a "modified-post-Texture" file, leave it with it original copyright year. - if data.include "Modifications to this file made after 4/13/2017" + if data.include? "Modifications to this file made after 4/13/2017" correct_license = true elsif data.start_with?(license_header) correct_license = true From 2e9858837251cf16c9ffd19ba2eaeaa1012c8977 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Wed, 17 Jan 2018 15:35:02 +0000 Subject: [PATCH 09/40] [ASDisplayNode] Don't force a layout pass on a visible node that enters preload state (#751) - After #706, a layout pass is forced on an ASM-enabled node that enters preload state to make sure that its subnodes can start preloading as well. However, when the node is visible, a (coalesced, thus more efficient) layout pass will be triggered by CA soon anyways, so rely on it instead. --- CHANGELOG.md | 2 +- Source/ASDisplayNode.mm | 27 +++++++++++++++++---------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87165dc7e2..1f6ea8c3a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ - [ASScrollNode] Ensure the node respects the given size range while calculating its layout. [#637](https://github.com/TextureGroup/Texture/pull/637) [Huy Nguyen](https://github.com/nguyenhuy) - [ASScrollNode] Invalidate the node's calculated layout if its scrollable directions changed. Also add unit tests for the class. [#637](https://github.com/TextureGroup/Texture/pull/637) [Huy Nguyen](https://github.com/nguyenhuy) - Add new unit testing to the layout engine. [Adlai Holler](https://github.com/Adlai-Holler) [#424](https://github.com/TextureGroup/Texture/pull/424) -- [Automatic Subnode Management] Nodes with ASM enabled now insert/delete their subnodes as soon as they enter preload state, so the subnodes can preload too. [Huy Nguyen](https://github.com/nguyenhuy) [#706](https://github.com/TextureGroup/Texture/pull/706) +- [Automatic Subnode Management] Nodes with ASM enabled now insert/delete their subnodes as soon as they enter preload state, so subnodes can start preloading right away. [Huy Nguyen](https://github.com/nguyenhuy) [#706](https://github.com/TextureGroup/Texture/pull/706) [#751](https://github.com/TextureGroup/Texture/pull/751) - [ASCollectionNode] Added support for interactive item movement. [Adlai Holler](https://github.com/Adlai-Holler) - Added an experimental "no-copy" rendering API. See ASGraphicsContext.h for info. [Adlai Holler](https://github.com/Adlai-Holler) - Dropped support for iOS 8. [Adlai Holler](https://github.com/Adlai-Holler) diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index 0f2bf77194..899331ea4b 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -3079,16 +3079,23 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) { ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__); - if (self.automaticallyManagesSubnodes) { - // Tell the node to apply its applicable pending layout, if any, so that its subnodes are inserted/deleted - // and start preloading right away. - // - // If this node has an up-to-date layout (and subnodes), calling layoutIfNeeded will be fast. - // - // If this node doesn't have a calculated or pending layout that fits its current bounds, a measurement pass will occur - // (see __layout and _u_measureNodeWithBoundsIfNecessary:). - // This scenario should be uncommon, and running a measurement pass here is a fine trade-off because preloading - // any time after this point would be late. + // If this node has ASM enabled and is not yet visible, force a layout pass to apply its applicable pending layout, if any, + // so that its subnodes are inserted/deleted and start preloading right away. + // + // - If it has an up-to-date layout (and subnodes), calling -layoutIfNeeded will be fast. + // + // - If it doesn't have a calculated or pending layout that fits its current bounds, a measurement pass will occur + // (see -__layout and -_u_measureNodeWithBoundsIfNecessary:). This scenario is uncommon, + // and running a measurement pass here is a fine trade-off because preloading any time after this point would be late. + // + // Don't force a layout pass if the node is already visible. Soon CoreAnimation will trigger + // a (coalesced, thus more efficient) pass on the backing store. Rely on it instead. + BOOL shouldForceLayoutPass = NO; + { + ASDN::MutexLocker l(__instanceLock__); + shouldForceLayoutPass = _automaticallyManagesSubnodes && !ASInterfaceStateIncludesVisible(_interfaceState); + } + if (shouldForceLayoutPass) { [self layoutIfNeeded]; } From b5d3e52e8b71401a66bb20279ba2993ee787cabd Mon Sep 17 00:00:00 2001 From: janechoi6 Date: Fri, 19 Jan 2018 01:00:15 +0900 Subject: [PATCH 10/40] Update subclassing.md (#753) --- docs/_docs/subclassing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_docs/subclassing.md b/docs/_docs/subclassing.md index 44c3260eed..ea4cd24b9a 100755 --- a/docs/_docs/subclassing.md +++ b/docs/_docs/subclassing.md @@ -51,7 +51,7 @@ An `ASViewController` is a regular `UIViewController` subclass that has special ### `-init` -This method is called once, at the very begining of an ASViewController's lifecycle. As with UIViewController initialization, it is best practice to **never access** `self.view` or `self.node.view` in this method as it will force the view to be created early. Instead, do any view access in -viewDidLoad. +This method is called once, at the very beginning of an ASViewController's lifecycle. As with UIViewController initialization, it is best practice to **never access** `self.view` or `self.node.view` in this method as it will force the view to be created early. Instead, do any view access in -viewDidLoad. ASViewController's designated initializer is `initWithNode:`. A typical initializer will look something like the code below. Note how the ASViewController's node is created _before_ calling super. An ASViewController manages a node similarly to how a UIViewController manages a view, but the initialization is slightly different. From 5c13403ef75c030adc7a4d51a7792a9c6c1c348b Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Mon, 22 Jan 2018 05:22:03 -0800 Subject: [PATCH 11/40] Faster collection operations (#748) * Faster collection operations * Fix a few things * Put the stupid semicolon * Address warning * Cut down retain/releases during collection operations * Update CHANGELOG.md --- .../xcschemes/AsyncDisplayKit.xcscheme | 1 + CHANGELOG.md | 1 + Source/ASCollectionView.mm | 39 ++------- Source/ASDisplayNode+Yoga.mm | 10 +-- Source/ASTableView.mm | 12 +-- Source/Base/ASBaseDefines.h | 83 ++++++++++++------- .../Details/ASCollectionFlowLayoutDelegate.m | 2 +- .../ASCollectionGalleryLayoutDelegate.mm | 6 +- Source/Details/ASElementMap.h | 5 ++ Source/Details/ASElementMap.m | 5 ++ Source/Layout/ASLayout.mm | 17 ++-- Source/Layout/ASLayoutSpec.mm | 10 +-- Source/Private/ASTwoDimensionalArrayUtils.m | 9 +- Source/_ASTransitionContext.m | 8 +- 14 files changed, 100 insertions(+), 108 deletions(-) diff --git a/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit.xcscheme b/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit.xcscheme index 8cf72597a3..5387aa6756 100644 --- a/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit.xcscheme +++ b/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit.xcscheme @@ -60,6 +60,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + disableMainThreadChecker = "YES" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f6ea8c3a8..e9351912f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - [ASCollectionNode] Added support for interactive item movement. [Adlai Holler](https://github.com/Adlai-Holler) - Added an experimental "no-copy" rendering API. See ASGraphicsContext.h for info. [Adlai Holler](https://github.com/Adlai-Holler) - Dropped support for iOS 8. [Adlai Holler](https://github.com/Adlai-Holler) +- Optimize internal collection operations. [Adlai Holler](https://github.com/Adlai-Holler) [#748](https://github.com/TextureGroup/Texture/pull/748) ## 2.6 - [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) diff --git a/Source/ASCollectionView.mm b/Source/ASCollectionView.mm index 1b06d6befd..dfc5dde0c9 100644 --- a/Source/ASCollectionView.mm +++ b/Source/ASCollectionView.mm @@ -719,19 +719,9 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; - (NSArray *)convertIndexPathsToCollectionNode:(NSArray *)indexPaths { - if (indexPaths == nil) { - return nil; - } - - NSMutableArray *indexPathsArray = [NSMutableArray arrayWithCapacity:indexPaths.count]; - - for (NSIndexPath *indexPathInView in indexPaths) { - NSIndexPath *indexPath = [self convertIndexPathToCollectionNode:indexPathInView]; - if (indexPath != nil) { - [indexPathsArray addObject:indexPath]; - } - } - return indexPathsArray; + return ASArrayByFlatMapping(indexPaths, NSIndexPath *indexPathInView, ({ + [self convertIndexPathToCollectionNode:indexPathInView]; + })); } - (ASCellNode *)supplementaryNodeForElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath @@ -747,17 +737,10 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; - (NSArray *)visibleNodes { NSArray *indexPaths = [self indexPathsForVisibleItems]; - NSMutableArray *visibleNodes = [[NSMutableArray alloc] init]; - 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; + return ASArrayByFlatMapping(indexPaths, NSIndexPath *indexPath, ({ + [self nodeForItemAtIndexPath:indexPath]; + })); } - (BOOL)usesSynchronousDataLoading @@ -2176,13 +2159,9 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; return; } - NSMutableArray *uikitIndexPaths = [NSMutableArray arrayWithCapacity:nodes.count]; - for (ASCellNode *node in nodes) { - NSIndexPath *uikitIndexPath = [self indexPathForNode:node]; - if (uikitIndexPath != nil) { - [uikitIndexPaths addObject:uikitIndexPath]; - } - } + NSArray *uikitIndexPaths = ASArrayByFlatMapping(nodes, ASCellNode *node, ({ + [self indexPathForNode:node]; + })); [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:uikitIndexPaths batched:NO]; diff --git a/Source/ASDisplayNode+Yoga.mm b/Source/ASDisplayNode+Yoga.mm index b52f948212..d4a0ba1157 100644 --- a/Source/ASDisplayNode+Yoga.mm +++ b/Source/ASDisplayNode+Yoga.mm @@ -158,14 +158,12 @@ - (void)setupYogaCalculatedLayout { YGNodeRef yogaNode = self.style.yogaNode; - uint32_t childCount = YGNodeGetChildCount(yogaNode); - ASDisplayNodeAssert(childCount == self.yogaChildren.count, + ASDisplayNodeAssert(YGNodeGetChildCount(yogaNode) == self.yogaChildren.count, @"Yoga tree should always be in sync with .yogaNodes array! %@", self.yogaChildren); - NSMutableArray *sublayouts = [NSMutableArray arrayWithCapacity:childCount]; - for (ASDisplayNode *subnode in self.yogaChildren) { - [sublayouts addObject:[subnode layoutForYogaNode]]; - } + NSArray *sublayouts = ASArrayByFlatMapping(self.yogaChildren, ASDisplayNode *subnode, ({ + [subnode layoutForYogaNode]; + })); // The layout for self should have position CGPointNull, but include the calculated size. CGSize size = CGSizeMake(YGNodeLayoutGetWidth(yogaNode), YGNodeLayoutGetHeight(yogaNode)); diff --git a/Source/ASTableView.mm b/Source/ASTableView.mm index f78a688ea8..fc0a2dbbee 100644 --- a/Source/ASTableView.mm +++ b/Source/ASTableView.mm @@ -607,15 +607,9 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; return nil; } - NSMutableArray *indexPathsArray = [NSMutableArray new]; - - for (NSIndexPath *indexPathInView in indexPaths) { - NSIndexPath *indexPath = [self convertIndexPathToTableNode:indexPathInView]; - if (indexPath != nil) { - [indexPathsArray addObject:indexPath]; - } - } - return indexPathsArray; + return ASArrayByFlatMapping(indexPaths, NSIndexPath *indexPathInView, ({ + [self convertIndexPathToTableNode:indexPathInView]; + })); } - (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode diff --git a/Source/Base/ASBaseDefines.h b/Source/Base/ASBaseDefines.h index 9eb1ec0b0f..37749e53de 100755 --- a/Source/Base/ASBaseDefines.h +++ b/Source/Base/ASBaseDefines.h @@ -132,22 +132,6 @@ #define __has_attribute(x) 0 // Compatibility with non-clang compilers. #endif -#ifndef NS_CONSUMED -#if __has_feature(attribute_ns_consumed) -#define NS_CONSUMED __attribute__((ns_consumed)) -#else -#define NS_CONSUMED -#endif -#endif - -#ifndef NS_RETURNS_RETAINED -#if __has_feature(attribute_ns_returns_retained) -#define NS_RETURNS_RETAINED __attribute__((ns_returns_retained)) -#else -#define NS_RETURNS_RETAINED -#endif -#endif - #ifndef CF_RETURNS_RETAINED #if __has_feature(attribute_cf_returns_retained) #define CF_RETURNS_RETAINED __attribute__((cf_returns_retained)) @@ -245,26 +229,38 @@ * Create a new set by mapping `collection` over `work`, ignoring nil. */ #define ASSetByFlatMapping(collection, decl, work) ({ \ - NSMutableSet *s = [NSMutableSet set]; \ + CFTypeRef _cArray[collection.count]; \ + NSUInteger _i = 0; \ for (decl in collection) {\ - id result = work; \ - if (result != nil) { \ - [s addObject:result]; \ + if ((_cArray[_i] = (__bridge_retained CFTypeRef)work)) { \ + _i++; \ } \ } \ - s; \ + NSSet *result; \ + if (_i == 0) { \ + /** Zero fast path. */ \ + result = [NSSet set]; \ + } else if (_i == 1) { \ + /** NSSingleObjectSet is fast. Create one and release. */ \ + CFTypeRef val = _cArray[0]; \ + result = [NSSet setWithObject:(__bridge id)val]; \ + CFBridgingRelease(val); \ + } else { \ + CFSetCallBacks cb = kCFTypeSetCallBacks; \ + cb.retain = NULL; \ + result = (__bridge NSSet *)CFSetCreate(kCFAllocatorDefault, _cArray, _i, &cb); \ + } \ + result; \ }) /** * Create a new ObjectPointerPersonality NSHashTable by mapping `collection` over `work`, ignoring nil. */ #define ASPointerTableByFlatMapping(collection, decl, work) ({ \ - NSHashTable *t = [NSHashTable hashTableWithOptions:NSHashTableObjectPointerPersonality]; \ + NSHashTable *t = [[NSHashTable alloc] initWithOptions:NSHashTableObjectPointerPersonality capacity:collection.count]; \ for (decl in collection) {\ - id result = work; \ - if (result != nil) { \ - [t addObject:result]; \ - } \ + /* NSHashTable accepts nil and avoid extra retain/release. */ \ + [t addObject:work]; \ } \ t; \ }) @@ -273,12 +269,37 @@ * Create a new array by mapping `collection` over `work`, ignoring nil. */ #define ASArrayByFlatMapping(collection, decl, work) ({ \ - NSMutableArray *a = [NSMutableArray array]; \ + CFTypeRef _cArray[collection.count]; \ + NSUInteger _i = 0; \ for (decl in collection) {\ - id result = work; \ - if (result != nil) { \ - [a addObject:result]; \ + if ((_cArray[_i] = (__bridge_retained CFTypeRef)work)) { \ + _i++; \ } \ } \ - a; \ + NSArray *result; \ + if (_i == 0) { \ + /** Zero array fast path. */ \ + result = @[]; \ + } else if (_i == 1) { \ + /** NSSingleObjectArray is fast. Create one and release. */ \ + CFTypeRef val = _cArray[0]; \ + result = [NSArray arrayWithObject:(__bridge id)val]; \ + CFBridgingRelease(val); \ + } else { \ + CFArrayCallBacks cb = kCFTypeArrayCallBacks; \ + cb.retain = NULL; \ + result = (__bridge NSArray *)CFArrayCreate(kCFAllocatorDefault, _cArray, _i, &cb); \ + } \ + result; \ +}) + +#define ASMutableArrayByFlatMapping(collection, decl, work) ({ \ + id _cArray[collection.count]; \ + NSUInteger _i = 0; \ + for (decl in collection) {\ + if ((_cArray[_i] = work)) { \ + _i++; \ + } \ + } \ + [[NSMutableArray alloc] initWithObjects:_cArray count:_i]; \ }) diff --git a/Source/Details/ASCollectionFlowLayoutDelegate.m b/Source/Details/ASCollectionFlowLayoutDelegate.m index 548fa01816..77e0922261 100644 --- a/Source/Details/ASCollectionFlowLayoutDelegate.m +++ b/Source/Details/ASCollectionFlowLayoutDelegate.m @@ -59,7 +59,7 @@ + (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context { ASElementMap *elements = context.elements; - NSMutableArray *children = ASArrayByFlatMapping(elements.itemElements, ASCollectionElement *element, element.node); + NSArray *children = ASArrayByFlatMapping(elements.itemElements, ASCollectionElement *element, element.node); if (children.count == 0) { return [[ASCollectionLayoutState alloc] initWithContext:context]; } diff --git a/Source/Details/ASCollectionGalleryLayoutDelegate.mm b/Source/Details/ASCollectionGalleryLayoutDelegate.mm index 2734e9649a..482123c516 100644 --- a/Source/Details/ASCollectionGalleryLayoutDelegate.mm +++ b/Source/Details/ASCollectionGalleryLayoutDelegate.mm @@ -102,9 +102,9 @@ return [[ASCollectionLayoutState alloc] initWithContext:context]; } - NSMutableArray<_ASGalleryLayoutItem *> *children = ASArrayByFlatMapping(elements.itemElements, - ASCollectionElement *element, - [[_ASGalleryLayoutItem alloc] initWithItemSize:itemSize collectionElement:element]); + NSArray<_ASGalleryLayoutItem *> *children = ASArrayByFlatMapping(elements.itemElements, + ASCollectionElement *element, + [[_ASGalleryLayoutItem alloc] initWithItemSize:itemSize collectionElement:element]); if (children.count == 0) { return [[ASCollectionLayoutState alloc] initWithContext:context]; } diff --git a/Source/Details/ASElementMap.h b/Source/Details/ASElementMap.h index eed7eff262..a73f80a92e 100644 --- a/Source/Details/ASElementMap.h +++ b/Source/Details/ASElementMap.h @@ -31,6 +31,11 @@ NS_ASSUME_NONNULL_BEGIN AS_SUBCLASSING_RESTRICTED @interface ASElementMap : NSObject +/** + * The total number of elements in this map. + */ +@property (readonly) NSUInteger count; + /** * The number of sections (of items) in this map. */ diff --git a/Source/Details/ASElementMap.m b/Source/Details/ASElementMap.m index 08e90d401e..f5646de78a 100644 --- a/Source/Details/ASElementMap.m +++ b/Source/Details/ASElementMap.m @@ -75,6 +75,11 @@ return self; } +- (NSUInteger)count +{ + return _elementToIndexPathMap.count; +} + - (NSArray *)itemIndexPaths { return ASIndexPathsForTwoDimensionalArray(_sectionsOfItems); diff --git a/Source/Layout/ASLayout.mm b/Source/Layout/ASLayout.mm index f6fbe91279..226b4a89e0 100644 --- a/Source/Layout/ASLayout.mm +++ b/Source/Layout/ASLayout.mm @@ -203,13 +203,9 @@ static std::atomic_bool static_retainsSublayoutLayoutElements = ATOMIC_VAR_INIT( _sublayoutLayoutElements = nil; } else { // Add sublayouts layout elements to an internal array to retain it while the layout lives - NSUInteger sublayoutCount = _sublayouts.count; - if (sublayoutCount > 0) { - _sublayoutLayoutElements = [NSMutableArray arrayWithCapacity:sublayoutCount]; - for (ASLayout *sublayout in _sublayouts) { - [_sublayoutLayoutElements addObject:sublayout.layoutElement]; - } - } + _sublayoutLayoutElements = ASMutableArrayByFlatMapping(_sublayouts, ASLayout *sublayout, ({ + sublayout.layoutElement; + })); } } } @@ -236,7 +232,7 @@ static std::atomic_bool static_retainsSublayoutLayoutElements = ATOMIC_VAR_INIT( queue.push_back({sublayout, sublayout.position}); } - NSMutableArray *flattenedSublayouts = [NSMutableArray array]; + std::vector flattenedSublayouts; while (!queue.empty()) { const Context context = queue.front(); @@ -255,7 +251,7 @@ static std::atomic_bool static_retainsSublayoutLayoutElements = ATOMIC_VAR_INIT( position:absolutePosition sublayouts:@[]]; } - [flattenedSublayouts addObject:layout]; + flattenedSublayouts.push_back(layout); } else if (sublayoutsCount > 0){ std::vector sublayoutContexts; for (ASLayout *sublayout in sublayouts) { @@ -265,7 +261,8 @@ static std::atomic_bool static_retainsSublayoutLayoutElements = ATOMIC_VAR_INIT( } } - ASLayout *layout = [ASLayout layoutWithLayoutElement:_layoutElement size:_size sublayouts:flattenedSublayouts]; + NSArray *sublayoutsArray = [NSArray arrayWithObjects:flattenedSublayouts.data() count:flattenedSublayouts.size()]; + ASLayout *layout = [ASLayout layoutWithLayoutElement:_layoutElement size:_size sublayouts:sublayoutsArray]; // All flattened layouts must have this flag enabled // to ensure sublayout elements are retained until the layouts are applied. layout.retainSublayoutLayoutElements = YES; diff --git a/Source/Layout/ASLayoutSpec.mm b/Source/Layout/ASLayoutSpec.mm index 76901d1ac7..a6aabdb399 100644 --- a/Source/Layout/ASLayoutSpec.mm +++ b/Source/Layout/ASLayoutSpec.mm @@ -295,19 +295,15 @@ ASLayoutElementStyleExtensibilityForwarding - (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize { - NSArray *children = self.children; - NSMutableArray *sublayouts = [NSMutableArray arrayWithCapacity:children.count]; - CGSize size = constrainedSize.min; - for (id child in children) { + NSArray *sublayouts = ASArrayByFlatMapping(self.children, id child, ({ ASLayout *sublayout = [child layoutThatFits:constrainedSize parentSize:constrainedSize.max]; sublayout.position = CGPointZero; size.width = MAX(size.width, sublayout.size.width); size.height = MAX(size.height, sublayout.size.height); - - [sublayouts addObject:sublayout]; - } + sublayout; + })); return [ASLayout layoutWithLayoutElement:self size:size sublayouts:sublayouts]; } diff --git a/Source/Private/ASTwoDimensionalArrayUtils.m b/Source/Private/ASTwoDimensionalArrayUtils.m index 71a48d450b..b21aeb41b6 100644 --- a/Source/Private/ASTwoDimensionalArrayUtils.m +++ b/Source/Private/ASTwoDimensionalArrayUtils.m @@ -27,13 +27,10 @@ NSMutableArray *ASTwoDimensionalArrayDeepMutableCopy(NSArray *array) { - NSMutableArray *newArray = [NSMutableArray arrayWithCapacity:array.count]; - NSInteger i = 0; - for (NSArray *subarray in array) { + return ASMutableArrayByFlatMapping(array, NSArray *subarray, ({ ASDisplayNodeCAssert([subarray isKindOfClass:[NSArray class]], @"This function expects NSArray *"); - newArray[i++] = [subarray mutableCopy]; - } - return newArray; + [subarray mutableCopy]; + })); } void ASDeleteElementsInTwoDimensionalArrayAtIndexPaths(NSMutableArray *mutableArray, NSArray *indexPaths) diff --git a/Source/_ASTransitionContext.m b/Source/_ASTransitionContext.m index da6350a4ab..b664feaf55 100644 --- a/Source/_ASTransitionContext.m +++ b/Source/_ASTransitionContext.m @@ -69,11 +69,9 @@ NSString * const ASTransitionContextToLayoutKey = @"org.asyncdisplaykit.ASTransi - (NSArray *)subnodesForKey:(NSString *)key { - NSMutableArray *subnodes = [NSMutableArray array]; - for (ASLayout *sublayout in [self layoutForKey:key].sublayouts) { - [subnodes addObject:(ASDisplayNode *)sublayout.layoutElement]; - } - return subnodes; + return ASArrayByFlatMapping([self layoutForKey:key].sublayouts, ASLayout *sublayout, ({ + (ASDisplayNode *)sublayout.layoutElement; + })); } - (NSArray *)insertedSubnodes From 9b8a919a938321a34982208346d7a8e1d7949ef6 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Mon, 22 Jan 2018 11:38:09 -0800 Subject: [PATCH 12/40] Revert "Faster collection operations (#748)" (#759) This reverts commit 5c13403ef75c030adc7a4d51a7792a9c6c1c348b. --- .../xcschemes/AsyncDisplayKit.xcscheme | 1 - CHANGELOG.md | 1 - Source/ASCollectionView.mm | 39 +++++++-- Source/ASDisplayNode+Yoga.mm | 10 ++- Source/ASTableView.mm | 12 ++- Source/Base/ASBaseDefines.h | 83 +++++++------------ .../Details/ASCollectionFlowLayoutDelegate.m | 2 +- .../ASCollectionGalleryLayoutDelegate.mm | 6 +- Source/Details/ASElementMap.h | 5 -- Source/Details/ASElementMap.m | 5 -- Source/Layout/ASLayout.mm | 17 ++-- Source/Layout/ASLayoutSpec.mm | 10 ++- Source/Private/ASTwoDimensionalArrayUtils.m | 9 +- Source/_ASTransitionContext.m | 8 +- 14 files changed, 108 insertions(+), 100 deletions(-) diff --git a/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit.xcscheme b/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit.xcscheme index 5387aa6756..8cf72597a3 100644 --- a/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit.xcscheme +++ b/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit.xcscheme @@ -60,7 +60,6 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - disableMainThreadChecker = "YES" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/CHANGELOG.md b/CHANGELOG.md index e9351912f4..1f6ea8c3a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,6 @@ - [ASCollectionNode] Added support for interactive item movement. [Adlai Holler](https://github.com/Adlai-Holler) - Added an experimental "no-copy" rendering API. See ASGraphicsContext.h for info. [Adlai Holler](https://github.com/Adlai-Holler) - Dropped support for iOS 8. [Adlai Holler](https://github.com/Adlai-Holler) -- Optimize internal collection operations. [Adlai Holler](https://github.com/Adlai-Holler) [#748](https://github.com/TextureGroup/Texture/pull/748) ## 2.6 - [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) diff --git a/Source/ASCollectionView.mm b/Source/ASCollectionView.mm index dfc5dde0c9..1b06d6befd 100644 --- a/Source/ASCollectionView.mm +++ b/Source/ASCollectionView.mm @@ -719,9 +719,19 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; - (NSArray *)convertIndexPathsToCollectionNode:(NSArray *)indexPaths { - return ASArrayByFlatMapping(indexPaths, NSIndexPath *indexPathInView, ({ - [self convertIndexPathToCollectionNode:indexPathInView]; - })); + if (indexPaths == nil) { + return nil; + } + + NSMutableArray *indexPathsArray = [NSMutableArray arrayWithCapacity:indexPaths.count]; + + for (NSIndexPath *indexPathInView in indexPaths) { + NSIndexPath *indexPath = [self convertIndexPathToCollectionNode:indexPathInView]; + if (indexPath != nil) { + [indexPathsArray addObject:indexPath]; + } + } + return indexPathsArray; } - (ASCellNode *)supplementaryNodeForElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath @@ -737,10 +747,17 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; - (NSArray *)visibleNodes { NSArray *indexPaths = [self indexPathsForVisibleItems]; + NSMutableArray *visibleNodes = [[NSMutableArray alloc] init]; - return ASArrayByFlatMapping(indexPaths, NSIndexPath *indexPath, ({ - [self nodeForItemAtIndexPath:indexPath]; - })); + 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; } - (BOOL)usesSynchronousDataLoading @@ -2159,9 +2176,13 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; return; } - NSArray *uikitIndexPaths = ASArrayByFlatMapping(nodes, ASCellNode *node, ({ - [self indexPathForNode:node]; - })); + NSMutableArray *uikitIndexPaths = [NSMutableArray arrayWithCapacity:nodes.count]; + for (ASCellNode *node in nodes) { + NSIndexPath *uikitIndexPath = [self indexPathForNode:node]; + if (uikitIndexPath != nil) { + [uikitIndexPaths addObject:uikitIndexPath]; + } + } [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:uikitIndexPaths batched:NO]; diff --git a/Source/ASDisplayNode+Yoga.mm b/Source/ASDisplayNode+Yoga.mm index d4a0ba1157..b52f948212 100644 --- a/Source/ASDisplayNode+Yoga.mm +++ b/Source/ASDisplayNode+Yoga.mm @@ -158,12 +158,14 @@ - (void)setupYogaCalculatedLayout { YGNodeRef yogaNode = self.style.yogaNode; - ASDisplayNodeAssert(YGNodeGetChildCount(yogaNode) == self.yogaChildren.count, + uint32_t childCount = YGNodeGetChildCount(yogaNode); + ASDisplayNodeAssert(childCount == self.yogaChildren.count, @"Yoga tree should always be in sync with .yogaNodes array! %@", self.yogaChildren); - NSArray *sublayouts = ASArrayByFlatMapping(self.yogaChildren, ASDisplayNode *subnode, ({ - [subnode layoutForYogaNode]; - })); + NSMutableArray *sublayouts = [NSMutableArray arrayWithCapacity:childCount]; + for (ASDisplayNode *subnode in self.yogaChildren) { + [sublayouts addObject:[subnode layoutForYogaNode]]; + } // The layout for self should have position CGPointNull, but include the calculated size. CGSize size = CGSizeMake(YGNodeLayoutGetWidth(yogaNode), YGNodeLayoutGetHeight(yogaNode)); diff --git a/Source/ASTableView.mm b/Source/ASTableView.mm index fc0a2dbbee..f78a688ea8 100644 --- a/Source/ASTableView.mm +++ b/Source/ASTableView.mm @@ -607,9 +607,15 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; return nil; } - return ASArrayByFlatMapping(indexPaths, NSIndexPath *indexPathInView, ({ - [self convertIndexPathToTableNode:indexPathInView]; - })); + NSMutableArray *indexPathsArray = [NSMutableArray new]; + + for (NSIndexPath *indexPathInView in indexPaths) { + NSIndexPath *indexPath = [self convertIndexPathToTableNode:indexPathInView]; + if (indexPath != nil) { + [indexPathsArray addObject:indexPath]; + } + } + return indexPathsArray; } - (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode diff --git a/Source/Base/ASBaseDefines.h b/Source/Base/ASBaseDefines.h index 37749e53de..9eb1ec0b0f 100755 --- a/Source/Base/ASBaseDefines.h +++ b/Source/Base/ASBaseDefines.h @@ -132,6 +132,22 @@ #define __has_attribute(x) 0 // Compatibility with non-clang compilers. #endif +#ifndef NS_CONSUMED +#if __has_feature(attribute_ns_consumed) +#define NS_CONSUMED __attribute__((ns_consumed)) +#else +#define NS_CONSUMED +#endif +#endif + +#ifndef NS_RETURNS_RETAINED +#if __has_feature(attribute_ns_returns_retained) +#define NS_RETURNS_RETAINED __attribute__((ns_returns_retained)) +#else +#define NS_RETURNS_RETAINED +#endif +#endif + #ifndef CF_RETURNS_RETAINED #if __has_feature(attribute_cf_returns_retained) #define CF_RETURNS_RETAINED __attribute__((cf_returns_retained)) @@ -229,38 +245,26 @@ * Create a new set by mapping `collection` over `work`, ignoring nil. */ #define ASSetByFlatMapping(collection, decl, work) ({ \ - CFTypeRef _cArray[collection.count]; \ - NSUInteger _i = 0; \ + NSMutableSet *s = [NSMutableSet set]; \ for (decl in collection) {\ - if ((_cArray[_i] = (__bridge_retained CFTypeRef)work)) { \ - _i++; \ + id result = work; \ + if (result != nil) { \ + [s addObject:result]; \ } \ } \ - NSSet *result; \ - if (_i == 0) { \ - /** Zero fast path. */ \ - result = [NSSet set]; \ - } else if (_i == 1) { \ - /** NSSingleObjectSet is fast. Create one and release. */ \ - CFTypeRef val = _cArray[0]; \ - result = [NSSet setWithObject:(__bridge id)val]; \ - CFBridgingRelease(val); \ - } else { \ - CFSetCallBacks cb = kCFTypeSetCallBacks; \ - cb.retain = NULL; \ - result = (__bridge NSSet *)CFSetCreate(kCFAllocatorDefault, _cArray, _i, &cb); \ - } \ - result; \ + s; \ }) /** * Create a new ObjectPointerPersonality NSHashTable by mapping `collection` over `work`, ignoring nil. */ #define ASPointerTableByFlatMapping(collection, decl, work) ({ \ - NSHashTable *t = [[NSHashTable alloc] initWithOptions:NSHashTableObjectPointerPersonality capacity:collection.count]; \ + NSHashTable *t = [NSHashTable hashTableWithOptions:NSHashTableObjectPointerPersonality]; \ for (decl in collection) {\ - /* NSHashTable accepts nil and avoid extra retain/release. */ \ - [t addObject:work]; \ + id result = work; \ + if (result != nil) { \ + [t addObject:result]; \ + } \ } \ t; \ }) @@ -269,37 +273,12 @@ * Create a new array by mapping `collection` over `work`, ignoring nil. */ #define ASArrayByFlatMapping(collection, decl, work) ({ \ - CFTypeRef _cArray[collection.count]; \ - NSUInteger _i = 0; \ + NSMutableArray *a = [NSMutableArray array]; \ for (decl in collection) {\ - if ((_cArray[_i] = (__bridge_retained CFTypeRef)work)) { \ - _i++; \ + id result = work; \ + if (result != nil) { \ + [a addObject:result]; \ } \ } \ - NSArray *result; \ - if (_i == 0) { \ - /** Zero array fast path. */ \ - result = @[]; \ - } else if (_i == 1) { \ - /** NSSingleObjectArray is fast. Create one and release. */ \ - CFTypeRef val = _cArray[0]; \ - result = [NSArray arrayWithObject:(__bridge id)val]; \ - CFBridgingRelease(val); \ - } else { \ - CFArrayCallBacks cb = kCFTypeArrayCallBacks; \ - cb.retain = NULL; \ - result = (__bridge NSArray *)CFArrayCreate(kCFAllocatorDefault, _cArray, _i, &cb); \ - } \ - result; \ -}) - -#define ASMutableArrayByFlatMapping(collection, decl, work) ({ \ - id _cArray[collection.count]; \ - NSUInteger _i = 0; \ - for (decl in collection) {\ - if ((_cArray[_i] = work)) { \ - _i++; \ - } \ - } \ - [[NSMutableArray alloc] initWithObjects:_cArray count:_i]; \ + a; \ }) diff --git a/Source/Details/ASCollectionFlowLayoutDelegate.m b/Source/Details/ASCollectionFlowLayoutDelegate.m index 77e0922261..548fa01816 100644 --- a/Source/Details/ASCollectionFlowLayoutDelegate.m +++ b/Source/Details/ASCollectionFlowLayoutDelegate.m @@ -59,7 +59,7 @@ + (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context { ASElementMap *elements = context.elements; - NSArray *children = ASArrayByFlatMapping(elements.itemElements, ASCollectionElement *element, element.node); + NSMutableArray *children = ASArrayByFlatMapping(elements.itemElements, ASCollectionElement *element, element.node); if (children.count == 0) { return [[ASCollectionLayoutState alloc] initWithContext:context]; } diff --git a/Source/Details/ASCollectionGalleryLayoutDelegate.mm b/Source/Details/ASCollectionGalleryLayoutDelegate.mm index 482123c516..2734e9649a 100644 --- a/Source/Details/ASCollectionGalleryLayoutDelegate.mm +++ b/Source/Details/ASCollectionGalleryLayoutDelegate.mm @@ -102,9 +102,9 @@ return [[ASCollectionLayoutState alloc] initWithContext:context]; } - NSArray<_ASGalleryLayoutItem *> *children = ASArrayByFlatMapping(elements.itemElements, - ASCollectionElement *element, - [[_ASGalleryLayoutItem alloc] initWithItemSize:itemSize collectionElement:element]); + NSMutableArray<_ASGalleryLayoutItem *> *children = ASArrayByFlatMapping(elements.itemElements, + ASCollectionElement *element, + [[_ASGalleryLayoutItem alloc] initWithItemSize:itemSize collectionElement:element]); if (children.count == 0) { return [[ASCollectionLayoutState alloc] initWithContext:context]; } diff --git a/Source/Details/ASElementMap.h b/Source/Details/ASElementMap.h index a73f80a92e..eed7eff262 100644 --- a/Source/Details/ASElementMap.h +++ b/Source/Details/ASElementMap.h @@ -31,11 +31,6 @@ NS_ASSUME_NONNULL_BEGIN AS_SUBCLASSING_RESTRICTED @interface ASElementMap : NSObject -/** - * The total number of elements in this map. - */ -@property (readonly) NSUInteger count; - /** * The number of sections (of items) in this map. */ diff --git a/Source/Details/ASElementMap.m b/Source/Details/ASElementMap.m index f5646de78a..08e90d401e 100644 --- a/Source/Details/ASElementMap.m +++ b/Source/Details/ASElementMap.m @@ -75,11 +75,6 @@ return self; } -- (NSUInteger)count -{ - return _elementToIndexPathMap.count; -} - - (NSArray *)itemIndexPaths { return ASIndexPathsForTwoDimensionalArray(_sectionsOfItems); diff --git a/Source/Layout/ASLayout.mm b/Source/Layout/ASLayout.mm index 226b4a89e0..f6fbe91279 100644 --- a/Source/Layout/ASLayout.mm +++ b/Source/Layout/ASLayout.mm @@ -203,9 +203,13 @@ static std::atomic_bool static_retainsSublayoutLayoutElements = ATOMIC_VAR_INIT( _sublayoutLayoutElements = nil; } else { // Add sublayouts layout elements to an internal array to retain it while the layout lives - _sublayoutLayoutElements = ASMutableArrayByFlatMapping(_sublayouts, ASLayout *sublayout, ({ - sublayout.layoutElement; - })); + NSUInteger sublayoutCount = _sublayouts.count; + if (sublayoutCount > 0) { + _sublayoutLayoutElements = [NSMutableArray arrayWithCapacity:sublayoutCount]; + for (ASLayout *sublayout in _sublayouts) { + [_sublayoutLayoutElements addObject:sublayout.layoutElement]; + } + } } } } @@ -232,7 +236,7 @@ static std::atomic_bool static_retainsSublayoutLayoutElements = ATOMIC_VAR_INIT( queue.push_back({sublayout, sublayout.position}); } - std::vector flattenedSublayouts; + NSMutableArray *flattenedSublayouts = [NSMutableArray array]; while (!queue.empty()) { const Context context = queue.front(); @@ -251,7 +255,7 @@ static std::atomic_bool static_retainsSublayoutLayoutElements = ATOMIC_VAR_INIT( position:absolutePosition sublayouts:@[]]; } - flattenedSublayouts.push_back(layout); + [flattenedSublayouts addObject:layout]; } else if (sublayoutsCount > 0){ std::vector sublayoutContexts; for (ASLayout *sublayout in sublayouts) { @@ -261,8 +265,7 @@ static std::atomic_bool static_retainsSublayoutLayoutElements = ATOMIC_VAR_INIT( } } - NSArray *sublayoutsArray = [NSArray arrayWithObjects:flattenedSublayouts.data() count:flattenedSublayouts.size()]; - ASLayout *layout = [ASLayout layoutWithLayoutElement:_layoutElement size:_size sublayouts:sublayoutsArray]; + ASLayout *layout = [ASLayout layoutWithLayoutElement:_layoutElement size:_size sublayouts:flattenedSublayouts]; // All flattened layouts must have this flag enabled // to ensure sublayout elements are retained until the layouts are applied. layout.retainSublayoutLayoutElements = YES; diff --git a/Source/Layout/ASLayoutSpec.mm b/Source/Layout/ASLayoutSpec.mm index a6aabdb399..76901d1ac7 100644 --- a/Source/Layout/ASLayoutSpec.mm +++ b/Source/Layout/ASLayoutSpec.mm @@ -295,15 +295,19 @@ ASLayoutElementStyleExtensibilityForwarding - (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize { + NSArray *children = self.children; + NSMutableArray *sublayouts = [NSMutableArray arrayWithCapacity:children.count]; + CGSize size = constrainedSize.min; - NSArray *sublayouts = ASArrayByFlatMapping(self.children, id child, ({ + for (id child in children) { ASLayout *sublayout = [child layoutThatFits:constrainedSize parentSize:constrainedSize.max]; sublayout.position = CGPointZero; size.width = MAX(size.width, sublayout.size.width); size.height = MAX(size.height, sublayout.size.height); - sublayout; - })); + + [sublayouts addObject:sublayout]; + } return [ASLayout layoutWithLayoutElement:self size:size sublayouts:sublayouts]; } diff --git a/Source/Private/ASTwoDimensionalArrayUtils.m b/Source/Private/ASTwoDimensionalArrayUtils.m index b21aeb41b6..71a48d450b 100644 --- a/Source/Private/ASTwoDimensionalArrayUtils.m +++ b/Source/Private/ASTwoDimensionalArrayUtils.m @@ -27,10 +27,13 @@ NSMutableArray *ASTwoDimensionalArrayDeepMutableCopy(NSArray *array) { - return ASMutableArrayByFlatMapping(array, NSArray *subarray, ({ + NSMutableArray *newArray = [NSMutableArray arrayWithCapacity:array.count]; + NSInteger i = 0; + for (NSArray *subarray in array) { ASDisplayNodeCAssert([subarray isKindOfClass:[NSArray class]], @"This function expects NSArray *"); - [subarray mutableCopy]; - })); + newArray[i++] = [subarray mutableCopy]; + } + return newArray; } void ASDeleteElementsInTwoDimensionalArrayAtIndexPaths(NSMutableArray *mutableArray, NSArray *indexPaths) diff --git a/Source/_ASTransitionContext.m b/Source/_ASTransitionContext.m index b664feaf55..da6350a4ab 100644 --- a/Source/_ASTransitionContext.m +++ b/Source/_ASTransitionContext.m @@ -69,9 +69,11 @@ NSString * const ASTransitionContextToLayoutKey = @"org.asyncdisplaykit.ASTransi - (NSArray *)subnodesForKey:(NSString *)key { - return ASArrayByFlatMapping([self layoutForKey:key].sublayouts, ASLayout *sublayout, ({ - (ASDisplayNode *)sublayout.layoutElement; - })); + NSMutableArray *subnodes = [NSMutableArray array]; + for (ASLayout *sublayout in [self layoutForKey:key].sublayouts) { + [subnodes addObject:(ASDisplayNode *)sublayout.layoutElement]; + } + return subnodes; } - (NSArray *)insertedSubnodes From c3ae4474d0fa31036981f63542fe741b3d496a0c Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Tue, 23 Jan 2018 10:45:34 -0800 Subject: [PATCH 13/40] NoCopyRendering experiment: Fix possible memory leak if image node rendering is canceled #trivial (#765) * Fix memory leak if image node rendering is canceled * Update comment --- Source/ASImageNode.mm | 5 ++--- Source/Details/ASGraphicsContext.h | 3 +++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Source/ASImageNode.mm b/Source/ASImageNode.mm index ed56c9ceb8..b1a561eeaa 100644 --- a/Source/ASImageNode.mm +++ b/Source/ASImageNode.mm @@ -522,10 +522,9 @@ static ASDN::StaticMutex& cacheLock = *new ASDN::StaticMutex; key.didDisplayNodeContentWithRenderingContext(context, drawParameters); } - // The following `UIGraphicsGetImageFromCurrentImageContext` call will commonly take more than 20ms on an - // A5 processor. Check for cancellation before we call it. + // Check cancellation one last time before forming image. if (isCancelled()) { - UIGraphicsEndImageContext(); + ASGraphicsEndImageContext(); return nil; } diff --git a/Source/Details/ASGraphicsContext.h b/Source/Details/ASGraphicsContext.h index 0a1c80fc4b..26183d5951 100644 --- a/Source/Details/ASGraphicsContext.h +++ b/Source/Details/ASGraphicsContext.h @@ -23,6 +23,9 @@ * * The API mirrors the UIGraphics API, with the exception that forming an image * ends the context as well. + * + * Note: You must not mix-and-match between ASGraphics* and UIGraphics* functions + * within the same drawing operation. */ NS_ASSUME_NONNULL_BEGIN From 3f27546ec8834620e8b35e0b0cccea178e37b92f Mon Sep 17 00:00:00 2001 From: Denis Morozov Date: Mon, 29 Jan 2018 15:05:33 +0300 Subject: [PATCH 14/40] Fix typos in layout2-layoutspec-types.md #trivial (#770) --- docs/_docs/layout2-layoutspec-types.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/_docs/layout2-layoutspec-types.md b/docs/_docs/layout2-layoutspec-types.md index ad14cf0754..f1bb432e34 100755 --- a/docs/_docs/layout2-layoutspec-types.md +++ b/docs/_docs/layout2-layoutspec-types.md @@ -394,17 +394,17 @@ Within `ASAbsoluteLayoutSpec` you can specify exact locations (x/y coordinates) CGSize maxConstrainedSize = constrainedSize.max; // Layout all nodes absolute in a static layout spec - guitarVideoNode.layoutPosition = CGPointMake(0, 0); - guitarVideoNode.size = ASSizeMakeFromCGSize(CGSizeMake(maxConstrainedSize.width, maxConstrainedSize.height / 3.0)); + guitarVideoNode.style.layoutPosition = CGPointMake(0, 0); + guitarVideoNode.style.size = ASSizeMakeFromCGSize(CGSizeMake(maxConstrainedSize.width, maxConstrainedSize.height / 3.0)); - nicCageVideoNode.layoutPosition = CGPointMake(maxConstrainedSize.width / 2.0, maxConstrainedSize.height / 3.0); - nicCageVideoNode.size = ASSizeMakeFromCGSize(CGSizeMake(maxConstrainedSize.width / 2.0, maxConstrainedSize.height / 3.0)); + nicCageVideoNode.style.layoutPosition = CGPointMake(maxConstrainedSize.width / 2.0, maxConstrainedSize.height / 3.0); + nicCageVideoNode.style.size = ASSizeMakeFromCGSize(CGSizeMake(maxConstrainedSize.width / 2.0, maxConstrainedSize.height / 3.0)); - simonVideoNode.layoutPosition = CGPointMake(0.0, maxConstrainedSize.height - (maxConstrainedSize.height / 3.0)); - simonVideoNode.size = ASSizeMakeFromCGSize(CGSizeMake(maxConstrainedSize.width/2, maxConstrainedSize.height / 3.0)); + simonVideoNode.style.layoutPosition = CGPointMake(0.0, maxConstrainedSize.height - (maxConstrainedSize.height / 3.0)); + simonVideoNode.style.size = ASSizeMakeFromCGSize(CGSizeMake(maxConstrainedSize.width/2, maxConstrainedSize.height / 3.0)); - hlsVideoNode.layoutPosition = CGPointMake(0.0, maxConstrainedSize.height / 3.0); - hlsVideoNode.size = ASSizeMakeFromCGSize(CGSizeMake(maxConstrainedSize.width / 2.0, maxConstrainedSize.height / 3.0)); + hlsVideoNode.style.layoutPosition = CGPointMake(0.0, maxConstrainedSize.height / 3.0); + hlsVideoNode.style.size = ASSizeMakeFromCGSize(CGSizeMake(maxConstrainedSize.width / 2.0, maxConstrainedSize.height / 3.0)); return [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[guitarVideoNode, nicCageVideoNode, simonVideoNode, hlsVideoNode]]; } From b0c3e65da887b4eca4ba5b58d974eef1490da9dd Mon Sep 17 00:00:00 2001 From: Flatout73 Date: Mon, 29 Jan 2018 15:05:48 +0300 Subject: [PATCH 15/40] Fix misprint (#768) --- docs/_docs/editable-text-node.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_docs/editable-text-node.md b/docs/_docs/editable-text-node.md index a1fccd44a4..9eccfd0321 100755 --- a/docs/_docs/editable-text-node.md +++ b/docs/_docs/editable-text-node.md @@ -137,7 +137,7 @@ optional public func editableTextNodeDidUpdateText(_ editableTextNode: ASEditabl ---  Indicates to the delegate that teh text node has finished editing. +--  Indicates to the delegate that the text node has finished editing.
SwiftObjective-C From 2e94bb8120adc3ee5fd1f523e5e44a4b02a7c68e Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Tue, 30 Jan 2018 14:18:37 -0500 Subject: [PATCH 16/40] Improve no-copy rendering experiment, remove +load method (#771) * Improve graphics contexts experiment * Update changelog * Remove extra space * Add a unit test for screen scale * Fix typo and use unique value --- CHANGELOG.md | 1 + Source/ASDisplayNode.mm | 6 -- Source/Details/ASGraphicsContext.m | 111 +++++++++++++++++++---------- Source/Private/ASInternalHelpers.m | 5 +- Tests/ASDisplayNodeTests.mm | 5 ++ 5 files changed, 81 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f6ea8c3a8..0c1f2475ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - [ASCollectionNode] Added support for interactive item movement. [Adlai Holler](https://github.com/Adlai-Holler) - Added an experimental "no-copy" rendering API. See ASGraphicsContext.h for info. [Adlai Holler](https://github.com/Adlai-Holler) - Dropped support for iOS 8. [Adlai Holler](https://github.com/Adlai-Holler) +- Removed +load static initializer from ASDisplayNode. [Adlai Holler](https://github.com/Adlai-Holler) ## 2.6 - [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index 899331ea4b..a976e3f098 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -226,12 +226,6 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) class_replaceMethod(self, @selector(_staticInitialize), staticInitialize, "v:@"); } -+ (void)load -{ - // Ensure this value is cached on the main thread before needed in the background. - ASScreenScale(); -} - + (Class)viewClass { return [_ASDisplayView class]; diff --git a/Source/Details/ASGraphicsContext.m b/Source/Details/ASGraphicsContext.m index 1c3092598a..f87102fabb 100644 --- a/Source/Details/ASGraphicsContext.m +++ b/Source/Details/ASGraphicsContext.m @@ -12,9 +12,11 @@ #import "ASGraphicsContext.h" #import +#import #import #import #import +#import #pragma mark - Feature Gating @@ -40,13 +42,26 @@ static BOOL ASNoCopyRenderingBlockAndCheckEnabled() { return (oldFlags & ASNoCopyEnabled) != 0; } -#pragma mark - Callbacks - -void _ASReleaseCGDataProviderData(__unused void *info, const void *data, __unused size_t size) -{ - free((void *)data); +/** + * Our version of the private CGBitmapGetAlignedBytesPerRow function. + * + * In both 32-bit and 64-bit, this function rounds up to nearest multiple of 32 + * in iOS 9, 10, and 11. We'll try to catch if this ever changes by asserting that + * the bytes-per-row for a 1x1 context from the system is 32. + */ +static size_t ASGraphicsGetAlignedBytesPerRow(size_t baseValue) { + // Add 31 then zero out low 5 bits. + return (baseValue + 31) & ~0x1F; } +/** + * A key used to associate CGContextRef -> NSMutableData, nonatomic retain. + * + * That way the data will be released when the context dies. If they pull an image, + * we will retain the data object (in a CGDataProvider) before releasing the context. + */ +static UInt8 __contextDataAssociationKey; + #pragma mark - Graphics Contexts extern void ASGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale) @@ -56,34 +71,46 @@ extern void ASGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGF return; } - // Only create device RGB color space once. UIGraphics actually doesn't do this but it's safe. + // We use "reference contexts" to get device-specific options that UIKit + // uses. static dispatch_once_t onceToken; - static CGFloat defaultScale; - static CGColorSpaceRef deviceRGB; + static CGContextRef refCtxOpaque; + static CGContextRef refCtxTransparent; dispatch_once(&onceToken, ^{ - deviceRGB = CGColorSpaceCreateDeviceRGB(); - UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), NO, 0); - CGContextRef uikitContext = UIGraphicsGetCurrentContext(); - defaultScale = CGContextGetCTM(uikitContext).a; + UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), YES, 1); + refCtxOpaque = CGContextRetain(UIGraphicsGetCurrentContext()); + ASDisplayNodeCAssert(CGBitmapContextGetBytesPerRow(refCtxOpaque) == 32, @"Expected bytes per row to be aligned to 32. Has CGBitmapGetAlignedBytesPerRow implementation changed?"); + UIGraphicsEndImageContext(); + + // Make transparent ref context. + UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), NO, 1); + refCtxTransparent = CGContextRetain(UIGraphicsGetCurrentContext()); UIGraphicsEndImageContext(); }); // These options are taken from UIGraphicsBeginImageContext. - CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host | (opaque ? kCGImageAlphaNoneSkipFirst : kCGImageAlphaPremultipliedFirst); + CGContextRef refCtx = opaque ? refCtxOpaque : refCtxTransparent; + CGBitmapInfo bitmapInfo = CGBitmapContextGetBitmapInfo(refCtx); if (scale == 0) { - scale = defaultScale; + scale = ASScreenScale(); } size_t intWidth = (size_t)ceil(size.width * scale); size_t intHeight = (size_t)ceil(size.height * scale); - size_t bytesPerPixel = 4; - size_t bytesPerRow = bytesPerPixel * intWidth; + size_t bitsPerComponent = CGBitmapContextGetBitsPerComponent(refCtx); + size_t bytesPerRow = CGBitmapContextGetBitsPerPixel(refCtx) * intWidth / 8; + bytesPerRow = ASGraphicsGetAlignedBytesPerRow(bytesPerRow); size_t bufferSize = bytesPerRow * intHeight; + CGColorSpaceRef colorspace = CGBitmapContextGetColorSpace(refCtx); // We create our own buffer, and wrap the context around that. This way we can prevent // the copy that usually gets made when you form a CGImage from the context. - void *buf = calloc(bufferSize, 1); - CGContextRef context = CGBitmapContextCreate(buf, intWidth, intHeight, 8, bytesPerRow, deviceRGB, bitmapInfo); + NSMutableData *data = [[NSMutableData alloc] initWithLength:bufferSize]; + CGContextRef context = CGBitmapContextCreate(data.mutableBytes, intWidth, intHeight, bitsPerComponent, bytesPerRow, colorspace, bitmapInfo); + + // Transfer ownership of the data to the context. So that if the context + // is destroyed before we create an image from it, the data will be released. + objc_setAssociatedObject((__bridge id)context, &__contextDataAssociationKey, data, OBJC_ASSOCIATION_RETAIN_NONATOMIC); // Set the CTM to account for iOS orientation & specified scale. // If only we could use CGContextSetBaseCTM. It doesn't @@ -96,7 +123,9 @@ extern void ASGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGF // Save the state so we can restore it and recover our scale in GetImageAndEnd CGContextSaveGState(context); + // Transfer context ownership to the UIKit stack. UIGraphicsPushContext(context); + CGContextRelease(context); } extern UIImage * _Nullable ASGraphicsGetImageAndEndCurrentContext() @@ -113,30 +142,40 @@ extern UIImage * _Nullable ASGraphicsGetImageAndEndCurrentContext() ASDisplayNodeCFailAssert(@"Can't end image context without having begun one."); return nil; } - UIGraphicsPopContext(); - // Do some math to get the image properties. - size_t width = CGBitmapContextGetWidth(context); - size_t height = CGBitmapContextGetHeight(context); - size_t bitsPerPixel = CGBitmapContextGetBitsPerPixel(context); - size_t bytesPerRow = CGBitmapContextGetBytesPerRow(context); - size_t bufferSize = bytesPerRow * height; + // Read the device-specific ICC-based color space to use for the image. + // For DeviceRGB contexts (e.g. UIGraphics), CGBitmapContextCreateImage + // generates an image in a device-specific color space (for wide color support). + // We replicate that behavior, even though at this time CA does not + // require the image to be in this space. Plain DeviceRGB images seem + // to be treated exactly the same, but better safe than sorry. + static CGColorSpaceRef imageColorSpace; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), YES, 0); + UIImage *refImage = UIGraphicsGetImageFromCurrentImageContext(); + imageColorSpace = CGImageGetColorSpace(refImage.CGImage); + UIGraphicsEndImageContext(); + }); - // This is the buf that we malloc'd above. - void *buf = CGBitmapContextGetData(context); - - // Wrap it in a CGDataProvider, passing along our release callback for when the CGImage dies. - CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, buf, bufferSize, _ASReleaseCGDataProviderData); + // Retrieve our data and wrap it in a CGDataProvider. + // Don't worry, the provider doesn't copy the data – it just retains it. + NSMutableData *data = objc_getAssociatedObject((__bridge id)context, &__contextDataAssociationKey); + ASDisplayNodeCAssertNotNil(data, nil); + CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data); // Create the CGImage. Options taken from CGBitmapContextCreateImage. - CGImageRef cgImg = CGImageCreate(width, height, CGBitmapContextGetBitsPerComponent(context), bitsPerPixel, bytesPerRow, CGBitmapContextGetColorSpace(context), CGBitmapContextGetBitmapInfo(context), provider, NULL, true, kCGRenderingIntentDefault); + CGImageRef cgImg = CGImageCreate(CGBitmapContextGetWidth(context), CGBitmapContextGetHeight(context), CGBitmapContextGetBitsPerComponent(context), CGBitmapContextGetBitsPerPixel(context), CGBitmapContextGetBytesPerRow(context), imageColorSpace, CGBitmapContextGetBitmapInfo(context), provider, NULL, true, kCGRenderingIntentDefault); CGDataProviderRelease(provider); // We saved our GState right after setting the CTM so that we could restore it // here and get the original scale back. CGContextRestoreGState(context); CGFloat scale = CGContextGetCTM(context).a; - CGContextRelease(context); + + // Note: popping from the UIKit stack will probably destroy the context. + context = NULL; + UIGraphicsPopContext(); UIImage *result = [[UIImage alloc] initWithCGImage:cgImg scale:scale orientation:UIImageOrientationUp]; CGImageRelease(cgImg); @@ -150,11 +189,5 @@ extern void ASGraphicsEndImageContext() return; } - CGContextRef context = UIGraphicsGetCurrentContext(); - if (context) { - // We manually allocated this buffer so we need to free it. - free(CGBitmapContextGetData(context)); - CGContextRelease(context); - UIGraphicsPopContext(); - } + UIGraphicsPopContext(); } diff --git a/Source/Private/ASInternalHelpers.m b/Source/Private/ASInternalHelpers.m index ad55f295aa..dab3d2ae5f 100644 --- a/Source/Private/ASInternalHelpers.m +++ b/Source/Private/ASInternalHelpers.m @@ -143,8 +143,9 @@ CGFloat ASScreenScale() static CGFloat __scale = 0.0; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - ASDisplayNodeCAssertMainThread(); - __scale = [[UIScreen mainScreen] scale]; + UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), YES, 0); + __scale = CGContextGetCTM(UIGraphicsGetCurrentContext()).a; + UIGraphicsEndImageContext(); }); return __scale; } diff --git a/Tests/ASDisplayNodeTests.mm b/Tests/ASDisplayNodeTests.mm index df4adbec67..9c5b668c4d 100644 --- a/Tests/ASDisplayNodeTests.mm +++ b/Tests/ASDisplayNodeTests.mm @@ -2349,4 +2349,9 @@ static bool stringContainsPointer(NSString *description, id p) { XCTAssert(hasVC); } +- (void)testScreenScale +{ + XCTAssertEqual(ASScreenScale(), UIScreen.mainScreen.scale); +} + @end From 7ba4376b6ff94fd5fb7657d9a3eecca181f45759 Mon Sep 17 00:00:00 2001 From: Justin Swart Date: Tue, 30 Jan 2018 11:19:37 -0800 Subject: [PATCH 17/40] Update PINCache (#769) --- Cartfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cartfile b/Cartfile index aebf9308e8..f1a449b012 100644 --- a/Cartfile +++ b/Cartfile @@ -1,2 +1,2 @@ github "pinterest/PINRemoteImage" "3.0.0-beta.13" -github "pinterest/PINCache" +github "pinterest/PINCache" "3.0.1-beta.6" From 196d76d82d2e6b329fcc90c9234aa6d074fcd3c9 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Tue, 30 Jan 2018 14:24:46 -0800 Subject: [PATCH 18/40] Expose asyncdisplaykit_node in _ASDisplayView same as in _ASDisplayLayer #trivial (#773) * Expose asyncdisplaykit_node in _ASDisplayView same as in _ASDisplayLayer * Change comment --- Source/Details/_ASDisplayView.h | 8 ++++++++ Source/Details/_ASDisplayView.mm | 1 - 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Source/Details/_ASDisplayView.h b/Source/Details/_ASDisplayView.h index c4968d183c..6a0b9a3869 100644 --- a/Source/Details/_ASDisplayView.h +++ b/Source/Details/_ASDisplayView.h @@ -20,8 +20,16 @@ // This class is only for use by ASDisplayNode and should never be subclassed or used directly. // Note that the "node" property is added to UIView directly via a category in ASDisplayNode. +@class ASDisplayNode; + @interface _ASDisplayView : UIView +/** + @discussion This property overrides the UIView category method which implements this via associated objects. + This should result in much better performance for _ASDisplayView. + */ +@property (nonatomic, weak) ASDisplayNode *asyncdisplaykit_node; + // These methods expose a way for ASDisplayNode touch events to let the view call super touch events // Some UIKit mechanisms, like UITableView and UICollectionView selection handling, require this to work - (void)__forwardTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event; diff --git a/Source/Details/_ASDisplayView.mm b/Source/Details/_ASDisplayView.mm index 2c2d2c383f..aa2c43eca3 100644 --- a/Source/Details/_ASDisplayView.mm +++ b/Source/Details/_ASDisplayView.mm @@ -27,7 +27,6 @@ #import @interface _ASDisplayView () -@property (nullable, atomic, weak, readwrite) ASDisplayNode *asyncdisplaykit_node; // Keep the node alive while its view is active. If you create a view, add its layer to a layer hierarchy, then release // the view, the layer retains the view to prevent a crash. This replicates this behaviour for the node abstraction. From fef965f78ef5424b8dae80cfeb29cd829ad62f4a Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Tue, 30 Jan 2018 17:50:38 -0500 Subject: [PATCH 19/40] Add support for providing additional info to network image node delegate (#775) * Add support for piping arbitrary user info from ASImageDownloader to the ASNetworkImageNodeDelegate * s/source/sourceType * Fix stuff and take Michael's advice --- AsyncDisplayKit.xcodeproj/project.pbxproj | 12 +++++ CHANGELOG.md | 3 ++ Source/ASMultiplexImageNode.mm | 2 +- Source/ASNetworkImageLoadInfo.h | 39 ++++++++++++++++ Source/ASNetworkImageLoadInfo.m | 32 ++++++++++++++ Source/ASNetworkImageNode.h | 19 ++------ Source/ASNetworkImageNode.mm | 44 ++++++++++--------- Source/AsyncDisplayKit.h | 1 + Source/Details/ASBasicImageDownloader.h | 5 ++- Source/Details/ASBasicImageDownloader.mm | 10 ++--- Source/Details/ASImageProtocols.h | 3 +- Source/Details/ASPINRemoteImageDownloader.h | 5 ++- Source/Details/ASPINRemoteImageDownloader.m | 8 ++-- .../Private/ASNetworkImageLoadInfo+Private.h | 22 ++++++++++ Tests/ASBasicImageDownloaderTests.m | 4 +- Tests/ASMultiplexImageNodeTests.m | 2 +- examples/ASDKgram/Sample/PhotoCellNode.m | 17 +++++++ 17 files changed, 173 insertions(+), 55 deletions(-) create mode 100644 Source/ASNetworkImageLoadInfo.h create mode 100644 Source/ASNetworkImageLoadInfo.m create mode 100644 Source/Private/ASNetworkImageLoadInfo+Private.h diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 1cff2cac06..19771c9ea4 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -410,6 +410,9 @@ CCE4F9B51F0DA4F300062E4E /* ASLayoutEngineTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCE4F9B41F0DA4F300062E4E /* ASLayoutEngineTests.mm */; }; CCE4F9BA1F0DBB5000062E4E /* ASLayoutTestNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCE4F9B71F0DBA5000062E4E /* ASLayoutTestNode.mm */; }; CCE4F9BE1F0ECE5200062E4E /* ASTLayoutFixture.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCE4F9BD1F0ECE5200062E4E /* ASTLayoutFixture.mm */; }; + CCED5E3E2020D36800395C40 /* ASNetworkImageLoadInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = CCED5E3C2020D36800395C40 /* ASNetworkImageLoadInfo.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CCED5E3F2020D36800395C40 /* ASNetworkImageLoadInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = CCED5E3D2020D36800395C40 /* ASNetworkImageLoadInfo.m */; }; + CCED5E412020D49D00395C40 /* ASNetworkImageLoadInfo+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = CCED5E402020D41600395C40 /* ASNetworkImageLoadInfo+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; CCF18FF41D2575E300DF5895 /* NSIndexSet+ASHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = CC4981BA1D1C7F65004E13CC /* NSIndexSet+ASHelpers.h */; settings = {ATTRIBUTES = (Private, ); }; }; DB55C2671C641AE4004EDCF5 /* ASContextTransitioning.h in Headers */ = {isa = PBXBuildFile; fileRef = DB55C2651C641AE4004EDCF5 /* ASContextTransitioning.h */; settings = {ATTRIBUTES = (Public, ); }; }; DB7121BCD50849C498C886FB /* libPods-AsyncDisplayKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */; }; @@ -913,6 +916,9 @@ CCE4F9BB1F0EA67F00062E4E /* debugbreak.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = debugbreak.h; sourceTree = ""; }; CCE4F9BC1F0ECE5200062E4E /* ASTLayoutFixture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTLayoutFixture.h; sourceTree = ""; }; CCE4F9BD1F0ECE5200062E4E /* ASTLayoutFixture.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTLayoutFixture.mm; sourceTree = ""; }; + CCED5E3C2020D36800395C40 /* ASNetworkImageLoadInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASNetworkImageLoadInfo.h; sourceTree = ""; }; + CCED5E3D2020D36800395C40 /* ASNetworkImageLoadInfo.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASNetworkImageLoadInfo.m; sourceTree = ""; }; + CCED5E402020D41600395C40 /* ASNetworkImageLoadInfo+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ASNetworkImageLoadInfo+Private.h"; sourceTree = ""; }; 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.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASScrollNode.mm; sourceTree = ""; }; @@ -1074,6 +1080,8 @@ 058D09B1195D04C000B7D73C /* Source */ = { isa = PBXGroup; children = ( + CCED5E3C2020D36800395C40 /* ASNetworkImageLoadInfo.h */, + CCED5E3D2020D36800395C40 /* ASNetworkImageLoadInfo.m */, CCE04B1D1E313E99006AEBBB /* Collection Data Adapter */, CC58AA4A1E398E1D002C8CB4 /* ASBlockTypes.h */, DBDB83921C6E879900D0098C /* ASPagerFlowLayout.h */, @@ -1376,6 +1384,7 @@ 058D0A01195D050800B7D73C /* Private */ = { isa = PBXGroup; children = ( + CCED5E402020D41600395C40 /* ASNetworkImageLoadInfo+Private.h */, CCA282CE1E9EBF6C0037E8B7 /* ASTipsWindow.h */, CCA282CF1E9EBF6C0037E8B7 /* ASTipsWindow.m */, CCA282C61E9EB64B0037E8B7 /* ASDisplayNodeTipState.h */, @@ -1913,6 +1922,7 @@ DE4843DC1C93EAC100A1F33B /* ASLayoutTransition.h in Headers */, CC57EAF81E3939450034C595 /* ASTableView+Undeprecated.h in Headers */, 254C6B781BF94DF4003EC431 /* ASTextKitContext.h in Headers */, + CCED5E412020D49D00395C40 /* ASNetworkImageLoadInfo+Private.h in Headers */, 9CDC18CD1B910E12004965E2 /* ASLayoutElementPrivate.h in Headers */, B35062201B010EFD0018CF92 /* ASLayoutController.h in Headers */, B35062211B010EFD0018CF92 /* ASLayoutRangeType.h in Headers */, @@ -1964,6 +1974,7 @@ B35062391B010EFD0018CF92 /* ASThread.h in Headers */, 2C107F5B1BA9F54500F13DE5 /* AsyncDisplayKit.h in Headers */, 509E68651B3AEDC5009B9150 /* CoreGraphics+ASConvenience.h in Headers */, + CCED5E3E2020D36800395C40 /* ASNetworkImageLoadInfo.h in Headers */, B350623A1B010EFD0018CF92 /* NSMutableAttributedString+TextKitAdditions.h in Headers */, 044284FF1BAA3BD600D16268 /* UICollectionViewLayout+ASConvenience.h in Headers */, B35062431B010EFD0018CF92 /* UIView+ASConvenience.h in Headers */, @@ -2292,6 +2303,7 @@ E55D86331CA8A14000A0C26F /* ASLayoutElement.mm in Sources */, 68FC85EC1CE29C7D00EDD713 /* ASVisibilityProtocols.m in Sources */, CC55A7121E52A0F200594372 /* ASResponderChainEnumerator.m in Sources */, + CCED5E3F2020D36800395C40 /* ASNetworkImageLoadInfo.m in Sources */, 68B8A4E41CBDB958007E4543 /* ASWeakProxy.m in Sources */, E5775B041F16759F00CAC9BC /* ASCollectionLayoutCache.mm in Sources */, 9C70F20A1CDBE949007D6C76 /* ASTableNode.mm in Sources */, diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c1f2475ae..55954a8fe2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,9 @@ - [ASCollectionNode] Added support for interactive item movement. [Adlai Holler](https://github.com/Adlai-Holler) - Added an experimental "no-copy" rendering API. See ASGraphicsContext.h for info. [Adlai Holler](https://github.com/Adlai-Holler) - Dropped support for iOS 8. [Adlai Holler](https://github.com/Adlai-Holler) +- **Breaking** Changes to ASNetworkImageNode: [Adlai Holler](https://github.com/Adlai-Holler) + - Modified `ASImageDownloaderCompletion` to add an optional `id userInfo` field. Your custom downloader can pass `nil`. + - Modified the last argument to `-[ASNetworkImageNodeDelegate imageNode:didLoadImage:info:]` method from a struct to an object of new class `ASNetworkImageLoadInfo` which includes other metadata about the load operation. - Removed +load static initializer from ASDisplayNode. [Adlai Holler](https://github.com/Adlai-Holler) ## 2.6 diff --git a/Source/ASMultiplexImageNode.mm b/Source/ASMultiplexImageNode.mm index a2bed044af..ecc5fd7af1 100644 --- a/Source/ASMultiplexImageNode.mm +++ b/Source/ASMultiplexImageNode.mm @@ -823,7 +823,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent [self _setDownloadIdentifier:[_downloader downloadImageWithURL:imageURL callbackQueue:dispatch_get_main_queue() downloadProgress:downloadProgressBlock - completion:^(id imageContainer, NSError *error, id downloadIdentifier) { + completion:^(id imageContainer, NSError *error, id downloadIdentifier, id userInfo) { // We dereference iVars directly, so we can't have weakSelf going nil on us. __typeof__(self) strongSelf = weakSelf; if (!strongSelf) diff --git a/Source/ASNetworkImageLoadInfo.h b/Source/ASNetworkImageLoadInfo.h new file mode 100644 index 0000000000..51e23e3754 --- /dev/null +++ b/Source/ASNetworkImageLoadInfo.h @@ -0,0 +1,39 @@ +// +// ASNetworkImageLoadInfo.h +// AsyncDisplayKit +// +// Created by Adlai on 1/30/18. +// Copyright © 2018 Facebook. All rights reserved. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSInteger, ASNetworkImageSourceType) { + ASNetworkImageSourceUnspecified = 0, + ASNetworkImageSourceSynchronousCache, + ASNetworkImageSourceAsynchronousCache, + ASNetworkImageSourceFileURL, + ASNetworkImageSourceDownload, +}; + +AS_SUBCLASSING_RESTRICTED +@interface ASNetworkImageLoadInfo : NSObject + +/// The type of source from which the image was loaded. +@property (readonly) ASNetworkImageSourceType sourceType; + +/// The image URL that was downloaded. +@property (readonly) NSURL *url; + +/// The download identifier, if one was provided. +@property (nullable, readonly) id downloadIdentifier; + +/// The userInfo object provided by the downloader, if one was provided. +@property (nullable, readonly) id userInfo; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/ASNetworkImageLoadInfo.m b/Source/ASNetworkImageLoadInfo.m new file mode 100644 index 0000000000..69dfee760b --- /dev/null +++ b/Source/ASNetworkImageLoadInfo.m @@ -0,0 +1,32 @@ +// +// ASNetworkImageLoadInfo.m +// AsyncDisplayKit +// +// Created by Adlai on 1/30/18. +// Copyright © 2018 Facebook. All rights reserved. +// + +#import +#import + +@implementation ASNetworkImageLoadInfo + +- (instancetype)initWithURL:(NSURL *)url sourceType:(ASNetworkImageSourceType)sourceType downloadIdentifier:(id)downloadIdentifier userInfo:(id)userInfo +{ + if (self = [super init]) { + _url = [url copy]; + _sourceType = sourceType; + _downloadIdentifier = downloadIdentifier; + _userInfo = userInfo; + } + return self; +} + +#pragma mark - NSCopying + +- (id)copyWithZone:(NSZone *)zone +{ + return self; +} + +@end diff --git a/Source/ASNetworkImageNode.h b/Source/ASNetworkImageNode.h index c4c83214b6..4100b52209 100644 --- a/Source/ASNetworkImageNode.h +++ b/Source/ASNetworkImageNode.h @@ -20,6 +20,7 @@ NS_ASSUME_NONNULL_BEGIN @protocol ASNetworkImageNodeDelegate, ASImageCacheProtocol, ASImageDownloaderProtocol; +@class ASNetworkImageLoadInfo; /** @@ -134,20 +135,6 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - -typedef NS_ENUM(NSInteger, ASNetworkImageSource) { - ASNetworkImageSourceUnspecified = 0, - ASNetworkImageSourceSynchronousCache, - ASNetworkImageSourceAsynchronousCache, - ASNetworkImageSourceFileURL, - ASNetworkImageSourceDownload, -}; - -/// A struct that carries details about ASNetworkImageNode's image loads. -typedef struct { - /// The source from which the image was loaded. - ASNetworkImageSource imageSource; -} ASNetworkImageNodeDidLoadInfo; - /** * The methods declared by the ASNetworkImageNodeDelegate protocol allow the adopting delegate to respond to * notifications such as finished decoding and downloading an image. @@ -161,11 +148,11 @@ typedef struct { * * @param imageNode The sender. * @param image The newly-loaded image. - * @param info Misc information about the image load. + * @param info Additional information about the image load. * * @discussion Called on a background queue. */ -- (void)imageNode:(ASNetworkImageNode *)imageNode didLoadImage:(UIImage *)image info:(ASNetworkImageNodeDidLoadInfo)info; +- (void)imageNode:(ASNetworkImageNode *)imageNode didLoadImage:(UIImage *)image info:(ASNetworkImageLoadInfo *)info; /** * Notification that the image node finished downloading an image. diff --git a/Source/ASNetworkImageNode.mm b/Source/ASNetworkImageNode.mm index 2240e3ae9e..c41a02ae79 100755 --- a/Source/ASNetworkImageNode.mm +++ b/Source/ASNetworkImageNode.mm @@ -27,6 +27,7 @@ #import #import #import +#import #if AS_PIN_REMOTE_IMAGE #import @@ -334,8 +335,9 @@ if (asynchronously == NO && _cacheFlags.cacheSupportsSynchronousFetch) { ASDN::MutexLocker l(__instanceLock__); - if (_imageLoaded == NO && _URL && _downloadIdentifier == nil) { - UIImage *result = [[_cache synchronouslyFetchedCachedImageWithURL:_URL] asdk_image]; + NSURL *url = _URL; + if (_imageLoaded == NO && url && _downloadIdentifier == nil) { + UIImage *result = [[_cache synchronouslyFetchedCachedImageWithURL:url] asdk_image]; if (result) { [self _locked_setCurrentImageQuality:1.0]; [self _locked__setImage:result]; @@ -344,8 +346,7 @@ // Call out to the delegate. if (_delegateFlags.delegateDidLoadImageWithInfo) { ASDN::MutexUnlocker l(__instanceLock__); - ASNetworkImageNodeDidLoadInfo info = {}; - info.imageSource = ASNetworkImageSourceSynchronousCache; + auto info = [[ASNetworkImageLoadInfo alloc] initWithURL:url sourceType:ASNetworkImageSourceSynchronousCache downloadIdentifier:nil userInfo:nil]; [_delegate imageNode:self didLoadImage:result info:info]; } else if (_delegateFlags.delegateDidLoadImage) { ASDN::MutexUnlocker l(__instanceLock__); @@ -553,7 +554,7 @@ _cacheUUID = nil; } -- (void)_downloadImageWithCompletion:(void (^)(id imageContainer, NSError*, id downloadIdentifier))finished +- (void)_downloadImageWithCompletion:(void (^)(id imageContainer, NSError*, id downloadIdentifier, id userInfo))finished { ASPerformBlockOnBackgroundThread(^{ NSURL *url; @@ -572,9 +573,9 @@ downloadIdentifier = [_downloader downloadImageWithURL:url callbackQueue:dispatch_get_main_queue() downloadProgress:NULL - completion:^(id _Nullable imageContainer, NSError * _Nullable error, id _Nullable downloadIdentifier) { + completion:^(id _Nullable imageContainer, NSError * _Nullable error, id _Nullable downloadIdentifier, id _Nullable userInfo) { if (finished != NULL) { - finished(imageContainer, error, downloadIdentifier); + finished(imageContainer, error, downloadIdentifier, userInfo); } }]; as_log_verbose(ASImageLoadingLog(), "Downloading image for %@ url: %@", self, url); @@ -629,15 +630,15 @@ } if (_shouldCacheImage) { - [self _locked__setImage:[UIImage imageNamed:_URL.path.lastPathComponent]]; + [self _locked__setImage:[UIImage imageNamed:URL.path.lastPathComponent]]; } else { // First try to load the path directly, for efficiency assuming a developer who // doesn't want caching is trying to be as minimal as possible. - UIImage *nonAnimatedImage = [UIImage imageWithContentsOfFile:_URL.path]; + UIImage *nonAnimatedImage = [UIImage imageWithContentsOfFile:URL.path]; if (nonAnimatedImage == nil) { // If we couldn't find it, execute an -imageNamed:-like search so we can find resources even if the // extension is not provided in the path. This allows the same path to work regardless of shouldCacheImage. - NSString *filename = [[NSBundle mainBundle] pathForResource:_URL.path.lastPathComponent ofType:nil]; + NSString *filename = [[NSBundle mainBundle] pathForResource:URL.path.lastPathComponent ofType:nil]; if (filename != nil) { nonAnimatedImage = [UIImage imageWithContentsOfFile:filename]; } @@ -646,7 +647,7 @@ // If the file may be an animated gif and then created an animated image. id animatedImage = nil; if (_downloaderFlags.downloaderImplementsAnimatedImage) { - NSData *data = [NSData dataWithContentsOfURL:_URL]; + NSData *data = [NSData dataWithContentsOfURL:URL]; if (data != nil) { animatedImage = [_downloader animatedImageWithData:data]; @@ -669,8 +670,7 @@ if (_delegateFlags.delegateDidLoadImageWithInfo) { ASDN::MutexUnlocker u(__instanceLock__); - ASNetworkImageNodeDidLoadInfo info = {}; - info.imageSource = ASNetworkImageSourceFileURL; + auto info = [[ASNetworkImageLoadInfo alloc] initWithURL:URL sourceType:ASNetworkImageSourceFileURL downloadIdentifier:nil userInfo:nil]; [delegate imageNode:self didLoadImage:self.image info:info]; } else if (_delegateFlags.delegateDidLoadImage) { ASDN::MutexUnlocker u(__instanceLock__); @@ -679,7 +679,7 @@ }); } else { __weak __typeof__(self) weakSelf = self; - auto finished = ^(id imageContainer, NSError *error, id downloadIdentifier, ASNetworkImageSource imageSource) { + auto finished = ^(id imageContainer, NSError *error, id downloadIdentifier, ASNetworkImageSourceType imageSource, id userInfo) { ASPerformBlockOnBackgroundThread(^{ __typeof__(self) strongSelf = weakSelf; if (strongSelf == nil) { @@ -718,6 +718,9 @@ strongSelf->_downloadIdentifier = nil; strongSelf->_cacheUUID = nil; + // TODO: Why dispatch to main here? + // The docs say the image node delegate methods + // are called from background. ASPerformBlockOnMainThread(^{ __typeof__(self) strongSelf = weakSelf; if (strongSelf == nil) { @@ -730,8 +733,7 @@ if (imageContainer != nil) { if (strongSelf->_delegateFlags.delegateDidLoadImageWithInfo) { ASDN::MutexUnlocker u(strongSelf->__instanceLock__); - ASNetworkImageNodeDidLoadInfo info = {}; - info.imageSource = imageSource; + auto info = [[ASNetworkImageLoadInfo alloc] initWithURL:URL sourceType:imageSource downloadIdentifier:downloadIdentifier userInfo:userInfo]; [delegate imageNode:strongSelf didLoadImage:strongSelf.image info:info]; } else if (strongSelf->_delegateFlags.delegateDidLoadImage) { ASDN::MutexUnlocker u(strongSelf->__instanceLock__); @@ -766,20 +768,20 @@ } if ([imageContainer asdk_image] == nil && _downloader != nil) { - [self _downloadImageWithCompletion:^(id imageContainer, NSError *error, id downloadIdentifier) { - finished(imageContainer, error, downloadIdentifier, ASNetworkImageSourceDownload); + [self _downloadImageWithCompletion:^(id imageContainer, NSError *error, id downloadIdentifier, id userInfo) { + finished(imageContainer, error, downloadIdentifier, ASNetworkImageSourceDownload, userInfo); }]; } else { as_log_verbose(ASImageLoadingLog(), "Decached image for %@ img: %@ url: %@", self, [imageContainer asdk_image], URL); - finished(imageContainer, nil, nil, ASNetworkImageSourceAsynchronousCache); + finished(imageContainer, nil, nil, ASNetworkImageSourceAsynchronousCache, nil); } }; [_cache cachedImageWithURL:URL callbackQueue:dispatch_get_main_queue() completion:completion]; } else { - [self _downloadImageWithCompletion:^(id imageContainer, NSError *error, id downloadIdentifier) { - finished(imageContainer, error, downloadIdentifier, ASNetworkImageSourceDownload); + [self _downloadImageWithCompletion:^(id imageContainer, NSError *error, id downloadIdentifier, id userInfo) { + finished(imageContainer, error, downloadIdentifier, ASNetworkImageSourceDownload, userInfo); }]; } } diff --git a/Source/AsyncDisplayKit.h b/Source/AsyncDisplayKit.h index a3935ec21e..241d8f2bb0 100644 --- a/Source/AsyncDisplayKit.h +++ b/Source/AsyncDisplayKit.h @@ -35,6 +35,7 @@ #import #import #import +#import #import #import diff --git a/Source/Details/ASBasicImageDownloader.h b/Source/Details/ASBasicImageDownloader.h index 7238048c0c..d1f8862d48 100644 --- a/Source/Details/ASBasicImageDownloader.h +++ b/Source/Details/ASBasicImageDownloader.h @@ -25,14 +25,15 @@ NS_ASSUME_NONNULL_BEGIN @interface ASBasicImageDownloader : NSObject /** - * A shared image downloader which can be used by @c ASNetworkImageNodes and @c ASMultiplexImageNodes + * A shared image downloader which can be used by @c ASNetworkImageNodes and @c ASMultiplexImageNodes. + * The userInfo provided by this downloader is `nil`. * * This is a very basic image downloader. It does not support caching, progressive downloading and likely * isn't something you should use in production. If you'd like something production ready, see @c ASPINRemoteImageDownloader * * @note It is strongly recommended you include PINRemoteImage and use @c ASPINRemoteImageDownloader instead. */ -+ (instancetype)sharedImageDownloader; +@property (class, readonly) ASBasicImageDownloader *sharedImageDownloader; + (instancetype)new __attribute__((unavailable("+[ASBasicImageDownloader sharedImageDownloader] must be used."))); - (instancetype)init __attribute__((unavailable("+[ASBasicImageDownloader sharedImageDownloader] must be used."))); diff --git a/Source/Details/ASBasicImageDownloader.mm b/Source/Details/ASBasicImageDownloader.mm index 0d80842654..ab88dfa4e6 100644 --- a/Source/Details/ASBasicImageDownloader.mm +++ b/Source/Details/ASBasicImageDownloader.mm @@ -130,7 +130,7 @@ static ASDN::StaticMutex& currentRequestsLock = *new ASDN::StaticMutex; if (completionBlock) { dispatch_async(callbackQueue, ^{ - completionBlock(image, error, nil); + completionBlock(image, error, nil, nil); }); } } @@ -206,7 +206,7 @@ static const char *kContextKey = NSStringFromClass(ASBasicImageDownloaderContext @implementation ASBasicImageDownloader -+ (instancetype)sharedImageDownloader ++ (ASBasicImageDownloader *)sharedImageDownloader { static ASBasicImageDownloader *sharedImageDownloader = nil; static dispatch_once_t once = 0; @@ -235,9 +235,9 @@ static const char *kContextKey = NSStringFromClass(ASBasicImageDownloaderContext #pragma mark ASImageDownloaderProtocol. - (id)downloadImageWithURL:(NSURL *)URL - callbackQueue:(dispatch_queue_t)callbackQueue - downloadProgress:(nullable ASImageDownloaderProgress)downloadProgress - completion:(ASImageDownloaderCompletion)completion + callbackQueue:(dispatch_queue_t)callbackQueue + downloadProgress:(nullable ASImageDownloaderProgress)downloadProgress + completion:(ASImageDownloaderCompletion)completion { ASBasicImageDownloaderContext *context = [ASBasicImageDownloaderContext contextForURL:URL]; diff --git a/Source/Details/ASImageProtocols.h b/Source/Details/ASImageProtocols.h index 3fdf321e45..c998672192 100644 --- a/Source/Details/ASImageProtocols.h +++ b/Source/Details/ASImageProtocols.h @@ -72,8 +72,9 @@ typedef void(^ASImageCacherCompletion)(id _Nullable i @param image The image that was downloaded, if the image could be successfully downloaded; nil otherwise. @param error An error describing why the download of `URL` failed, if the download failed; nil otherwise. @param downloadIdentifier The identifier for the download task that completed. + @param userInfo Any additional info that your downloader would like to communicate through Texture. */ -typedef void(^ASImageDownloaderCompletion)(id _Nullable image, NSError * _Nullable error, id _Nullable downloadIdentifier); +typedef void(^ASImageDownloaderCompletion)(id _Nullable image, NSError * _Nullable error, id _Nullable downloadIdentifier, id _Nullable userInfo); /** @param progress The progress of the download, in the range of (0.0, 1.0), inclusive. diff --git a/Source/Details/ASPINRemoteImageDownloader.h b/Source/Details/ASPINRemoteImageDownloader.h index 179104901d..abccd9f84a 100644 --- a/Source/Details/ASPINRemoteImageDownloader.h +++ b/Source/Details/ASPINRemoteImageDownloader.h @@ -28,12 +28,13 @@ NS_ASSUME_NONNULL_BEGIN @interface ASPINRemoteImageDownloader : NSObject /** - * A shared image downloader which can be used by @c ASNetworkImageNodes and @c ASMultiplexImageNodes + * A shared image downloader which can be used by @c ASNetworkImageNodes and @c ASMultiplexImageNodes. + * The userInfo provided by this downloader is an instance of `PINRemoteImageManagerResult`. * * This is the default downloader used by network backed image nodes if PINRemoteImage and PINCache are * available. It uses PINRemoteImage's features to provide caching and progressive image downloads. */ -+ (ASPINRemoteImageDownloader *)sharedDownloader; +@property (class, readonly) ASPINRemoteImageDownloader *sharedDownloader; /** diff --git a/Source/Details/ASPINRemoteImageDownloader.m b/Source/Details/ASPINRemoteImageDownloader.m index 3b46fc3142..3d050fd9eb 100644 --- a/Source/Details/ASPINRemoteImageDownloader.m +++ b/Source/Details/ASPINRemoteImageDownloader.m @@ -114,7 +114,7 @@ static ASPINRemoteImageDownloader *sharedDownloader = nil; @implementation ASPINRemoteImageDownloader -+ (instancetype)sharedDownloader ++ (ASPINRemoteImageDownloader *)sharedDownloader { static dispatch_once_t onceToken = 0; @@ -235,12 +235,12 @@ static ASPINRemoteImageDownloader *sharedDownloader = nil; [ASPINRemoteImageDownloader _performWithCallbackQueue:callbackQueue work:^{ #if PIN_ANIMATED_AVAILABLE if (result.alternativeRepresentation) { - completion(result.alternativeRepresentation, result.error, result.UUID); + completion(result.alternativeRepresentation, result.error, result.UUID, result); } else { - completion(result.image, result.error, result.UUID); + completion(result.image, result.error, result.UUID, result); } #else - completion(result.image, result.error, result.UUID); + completion(result.image, result.error, result.UUID, result); #endif }]; }; diff --git a/Source/Private/ASNetworkImageLoadInfo+Private.h b/Source/Private/ASNetworkImageLoadInfo+Private.h new file mode 100644 index 0000000000..db04352311 --- /dev/null +++ b/Source/Private/ASNetworkImageLoadInfo+Private.h @@ -0,0 +1,22 @@ +// +// ASNetworkImageLoadInfo+Private.h +// AsyncDisplayKit +// +// Created by Adlai on 1/30/18. +// Copyright © 2018 Facebook. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface ASNetworkImageLoadInfo () + +- (instancetype)initWithURL:(NSURL *)url + sourceType:(ASNetworkImageSourceType)sourceType + downloadIdentifier:(nullable id)downloadIdentifier + userInfo:(nullable id)userInfo; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Tests/ASBasicImageDownloaderTests.m b/Tests/ASBasicImageDownloaderTests.m index 2dd7d3375e..6144b5c253 100644 --- a/Tests/ASBasicImageDownloaderTests.m +++ b/Tests/ASBasicImageDownloaderTests.m @@ -38,14 +38,14 @@ [downloader downloadImageWithURL:URL callbackQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) downloadProgress:nil - completion:^(id _Nullable image, NSError * _Nullable error, id _Nullable downloadIdentifier) { + completion:^(id _Nullable image, NSError * _Nullable error, id _Nullable downloadIdentifier, id _Nullable userInfo) { [firstExpectation fulfill]; }]; [downloader downloadImageWithURL:URL callbackQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) downloadProgress:nil - completion:^(id _Nullable image, NSError * _Nullable error, id _Nullable downloadIdentifier) { + completion:^(id _Nullable image, NSError * _Nullable error, id _Nullable downloadIdentifier, id _Nullable userInfo) { [secondExpectation fulfill]; }]; diff --git a/Tests/ASMultiplexImageNodeTests.m b/Tests/ASMultiplexImageNodeTests.m index 9eb41874b8..71b1fe05e4 100644 --- a/Tests/ASMultiplexImageNodeTests.m +++ b/Tests/ASMultiplexImageNodeTests.m @@ -238,7 +238,7 @@ // Simulate completion. ASImageDownloaderCompletion completionBlock = [inv as_argumentAtIndexAsObject:5]; - completionBlock([self _testImage], nil, nil); + completionBlock([self _testImage], nil, nil, nil); }); NSNumber *imageIdentifier = @1; diff --git a/examples/ASDKgram/Sample/PhotoCellNode.m b/examples/ASDKgram/Sample/PhotoCellNode.m index d424587c60..a094ad5607 100644 --- a/examples/ASDKgram/Sample/PhotoCellNode.m +++ b/examples/ASDKgram/Sample/PhotoCellNode.m @@ -44,6 +44,9 @@ #define InsetForHeader UIEdgeInsetsMake(0, HORIZONTAL_BUFFER, 0, HORIZONTAL_BUFFER) #define InsetForFooter UIEdgeInsetsMake(VERTICAL_BUFFER, HORIZONTAL_BUFFER, VERTICAL_BUFFER, HORIZONTAL_BUFFER) +@interface PhotoCellNode () +@end + @implementation PhotoCellNode { PhotoModel *_photoModel; @@ -77,6 +80,7 @@ }]; _photoImageNode = [[ASNetworkImageNode alloc] init]; + _photoImageNode.delegate = self; _photoImageNode.URL = photo.URL; _photoImageNode.layerBacked = YES; @@ -284,6 +288,19 @@ }]; } +#pragma mark - Network Image Delegate + +- (void)imageNode:(ASNetworkImageNode *)imageNode didLoadImage:(UIImage *)image info:(ASNetworkImageLoadInfo *)info +{ + // Docs say method is called from bg but right now it's called from main. + // Save main thread time by shunting this. + if (info.sourceType == ASNetworkImageSourceDownload) { + ASPerformBlockOnBackgroundThread(^{ + NSLog(@"Received image %@ from %@ with userInfo %@", image, info.url.path, ASObjectDescriptionMakeTiny(info.userInfo)); + }); + } +} + #pragma mark - Helper Methods - (ASTextNode *)createLayerBackedTextNodeWithString:(NSAttributedString *)attributedString From 20e31f7d70ff44b603853d5bd3739aaacf73e002 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Wed, 31 Jan 2018 07:07:38 -0800 Subject: [PATCH 20/40] Fix synchronous state of node if +viewClass or +layerClass is overwritten #trivial (#776) * Fix synchronous state of node if +viewClass is overwritten * Also check for _layerClass overwrite for synchronous flag * Update some code style --- Source/ASDisplayNode.mm | 5 +++++ Tests/ASDisplayNodeTests.mm | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index a976e3f098..000b57acd8 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -253,6 +253,11 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) _viewClass = [self.class viewClass]; _layerClass = [self.class layerClass]; + BOOL isSynchronous = ![_viewClass isSubclassOfClass:[_ASDisplayView class]] + || ![_layerClass isSubclassOfClass:[_ASDisplayLayer class]]; + setFlag(Synchronous, isSynchronous); + + _contentsScaleForDisplay = ASScreenScale(); _drawingPriority = ASDefaultDrawingPriority; diff --git a/Tests/ASDisplayNodeTests.mm b/Tests/ASDisplayNodeTests.mm index 9c5b668c4d..ef5baee058 100644 --- a/Tests/ASDisplayNodeTests.mm +++ b/Tests/ASDisplayNodeTests.mm @@ -173,6 +173,28 @@ for (ASDisplayNode *n in @[ nodes ]) {\ @end +@interface ASSynchronousTestDisplayNodeViaViewClass : ASDisplayNode +@end + +@implementation ASSynchronousTestDisplayNodeViaViewClass + ++ (Class)viewClass { + return [UIView class]; +} + +@end + +@interface ASSynchronousTestDisplayNodeViaLayerClass : ASDisplayNode +@end + +@implementation ASSynchronousTestDisplayNodeViaLayerClass + ++ (Class)layerClass { + return [CALayer class]; +} + +@end + @interface UIDisplayNodeTestView : UIView @end @@ -2354,4 +2376,16 @@ static bool stringContainsPointer(NSString *description, id p) { XCTAssertEqual(ASScreenScale(), UIScreen.mainScreen.scale); } +- (void)testThatIfViewClassIsOverwrittenItsSynchronous +{ + ASSynchronousTestDisplayNodeViaViewClass *node = [[ASSynchronousTestDisplayNodeViaViewClass alloc] init]; + XCTAssertTrue([node isSynchronous], @"Node should be synchronous if viewClass is ovewritten and not a subclass of _ASDisplayView"); +} + +- (void)testThatIfLayerClassIsOverwrittenItsSynchronous +{ + ASSynchronousTestDisplayNodeViaLayerClass *node = [[ASSynchronousTestDisplayNodeViaLayerClass alloc] init]; + XCTAssertTrue([node isSynchronous], @"Node should be synchronous if viewClass is ovewritten and not a subclass of _ASDisplayView"); +} + @end From 511bec63a2eb80e60a7c819c1b85b7de0e219d3b Mon Sep 17 00:00:00 2001 From: Denis Morozov Date: Wed, 31 Jan 2018 18:20:00 +0300 Subject: [PATCH 21/40] Fix capturing self in the block while loading image in ASNetworkImageNode (#777) * Fix capturing self in the block while loading image in ASNetworkImageNode * Restore re-strongify while switching on the main thread * Update CHANGELOG.md --- CHANGELOG.md | 1 + Source/ASNetworkImageNode.mm | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55954a8fe2..9371048b0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## master * Add your own contributions to the next release on the line below this with your name. +- [ASNetworkImageNode] Fix capturing self in the block while loading image in ASNetworkImageNode. [Denis Mororozov](https://github.com/morozkin) [#777](https://github.com/TextureGroup/Texture/pull/777) - [ASTraitCollection] Add new properties of UITraitCollection to ASTraitCollection. [Yevgen Pogribnyi](https://github.com/ypogribnyi) - [ASRectMap] Replace implementation of ASRectTable with a simpler one based on unordered_map.[Scott Goodson](https://github.com/appleguy) [#719](https://github.com/TextureGroup/Texture/pull/719) - [ASCollectionView] Add missing flags for ASCollectionDelegate [Ilya Zheleznikov](https://github.com/ilyailya) [#718](https://github.com/TextureGroup/Texture/pull/718) diff --git a/Source/ASNetworkImageNode.mm b/Source/ASNetworkImageNode.mm index c41a02ae79..1189d58a0d 100755 --- a/Source/ASNetworkImageNode.mm +++ b/Source/ASNetworkImageNode.mm @@ -697,9 +697,9 @@ } //No longer in preload range, no point in setting the results (they won't be cleared in exit preload range) - if (ASInterfaceStateIncludesPreload(self->_interfaceState) == NO) { - self->_downloadIdentifier = nil; - self->_cacheUUID = nil; + if (ASInterfaceStateIncludesPreload(strongSelf->_interfaceState) == NO) { + strongSelf->_downloadIdentifier = nil; + strongSelf->_cacheUUID = nil; return; } From d6971980a396c6e76efb7dc6a612a5cc70734bba Mon Sep 17 00:00:00 2001 From: Aaron Rosenberger Date: Wed, 31 Jan 2018 10:23:04 -0500 Subject: [PATCH 22/40] Fixed: completeBatchFetching is called on a background thread (#731) * Fixed breaking issue where completeBatchFetching is called on a background thread when no items are added to the collection * Changed spaces to tabs for consistency * Moved return statement for Code Review feedback * Fixed the same issue in the Objective-C version of ASDKgram * One more * Update PhotoFeedModel.m Fix header --- examples/ASDKgram/Sample/PhotoFeedModel.m | 28 +++++++++---------- .../ASDKgram-Swift/PhotoFeedModel.swift | 9 ++++-- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/examples/ASDKgram/Sample/PhotoFeedModel.m b/examples/ASDKgram/Sample/PhotoFeedModel.m index 27cf185e9d..afac8ca9fe 100644 --- a/examples/ASDKgram/Sample/PhotoFeedModel.m +++ b/examples/ASDKgram/Sample/PhotoFeedModel.m @@ -1,20 +1,18 @@ // // PhotoFeedModel.m -// Sample -// -// Created by Hannah Troisi on 2/28/16. +// Texture // // 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. +// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. // -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 // #import "PhotoFeedModel.h" @@ -184,9 +182,11 @@ // early return if reached end of pages if (_totalPages) { if (_currentPage == _totalPages) { - if (block){ - block(@[]); - } + dispatch_async(dispatch_get_main_queue(), ^{ + if (block) { + block(@[]); + } + }); return; } } diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoFeedModel.swift b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoFeedModel.swift index 9a7cd52020..f344df34a7 100644 --- a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoFeedModel.swift +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoFeedModel.swift @@ -67,7 +67,10 @@ final class PhotoFeedModel { private func fetchNextPageOfPopularPhotos(replaceData: Bool, numberOfAdditionsCompletion: @escaping (Int, NetworkingErrors?) -> ()) { if currentPage == totalPages, currentPage != 0 { - return numberOfAdditionsCompletion(0, .customError("No pages left to parse")) + DispatchQueue.main.async { + numberOfAdditionsCompletion(0, .customError("No pages left to parse")) + } + return } var newPhotos: [PhotoModel] = [] @@ -106,7 +109,9 @@ final class PhotoFeedModel { case .failure(let fail): print(fail) - numberOfAdditionsCompletion(0, fail) + DispatchQueue.main.async { + numberOfAdditionsCompletion(0, fail) + } } } } From ea547270f2d05e015167ebdaaac48c95318fe9d5 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Wed, 31 Jan 2018 15:35:14 +0000 Subject: [PATCH 23/40] [ASDisplayNode] Force a layout pass on a visible node as soon as it enters preload state (#779) This reverts commit 2e9858837251cf16c9ffd19ba2eaeaa1012c8977 (#751). The reason we can't wait for the coming CA's layout pass is that cell node visibility events occur before the pass, at which time the cell's subnodes don't have correct frames for impression tracking. The root cause of this problem is that, right now, cell node visible states are set by ASRangeController well before the layout pass of the hosting collection/table view. That means we're "jumping the gun". The more I think about this, the more I agree with @Adlai-Holler that we need to treat visible state differently. That is, a node should only be visible (and thus get visibility events) after it's fully loaded, it's view/layer attached to the hierarchy and laid out by a CA transaction. In other words, at the end of the CA layout pass. Such change needs time and effort to be thoroughly reviewed and tested. Until then, let's roll with this fix. --- CHANGELOG.md | 2 +- Source/ASDisplayNode.mm | 10 +--------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9371048b0f..140fcb916c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ - [ASScrollNode] Ensure the node respects the given size range while calculating its layout. [#637](https://github.com/TextureGroup/Texture/pull/637) [Huy Nguyen](https://github.com/nguyenhuy) - [ASScrollNode] Invalidate the node's calculated layout if its scrollable directions changed. Also add unit tests for the class. [#637](https://github.com/TextureGroup/Texture/pull/637) [Huy Nguyen](https://github.com/nguyenhuy) - Add new unit testing to the layout engine. [Adlai Holler](https://github.com/Adlai-Holler) [#424](https://github.com/TextureGroup/Texture/pull/424) -- [Automatic Subnode Management] Nodes with ASM enabled now insert/delete their subnodes as soon as they enter preload state, so subnodes can start preloading right away. [Huy Nguyen](https://github.com/nguyenhuy) [#706](https://github.com/TextureGroup/Texture/pull/706) [#751](https://github.com/TextureGroup/Texture/pull/751) +- [Automatic Subnode Management] Nodes with ASM enabled now insert/delete their subnodes as soon as they enter preload state, so subnodes can start preloading right away. [Huy Nguyen](https://github.com/nguyenhuy) [#706](https://github.com/TextureGroup/Texture/pull/706) - [ASCollectionNode] Added support for interactive item movement. [Adlai Holler](https://github.com/Adlai-Holler) - Added an experimental "no-copy" rendering API. See ASGraphicsContext.h for info. [Adlai Holler](https://github.com/Adlai-Holler) - Dropped support for iOS 8. [Adlai Holler](https://github.com/Adlai-Holler) diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index 000b57acd8..1e772e5148 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -3086,15 +3086,7 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) { // - If it doesn't have a calculated or pending layout that fits its current bounds, a measurement pass will occur // (see -__layout and -_u_measureNodeWithBoundsIfNecessary:). This scenario is uncommon, // and running a measurement pass here is a fine trade-off because preloading any time after this point would be late. - // - // Don't force a layout pass if the node is already visible. Soon CoreAnimation will trigger - // a (coalesced, thus more efficient) pass on the backing store. Rely on it instead. - BOOL shouldForceLayoutPass = NO; - { - ASDN::MutexLocker l(__instanceLock__); - shouldForceLayoutPass = _automaticallyManagesSubnodes && !ASInterfaceStateIncludesVisible(_interfaceState); - } - if (shouldForceLayoutPass) { + if (self.automaticallyManagesSubnodes) { [self layoutIfNeeded]; } From 0bb53552b01cd11a2c78ed3f3f3bd37e3aa616a7 Mon Sep 17 00:00:00 2001 From: Alex Hill Date: Wed, 31 Jan 2018 10:07:08 -0800 Subject: [PATCH 24/40] [ASCellNode] focusStyle mapping (#727) * [ASCellNode] Adds mapping for UITableViewCell focusStyle * Update CHANGELOG.md --- CHANGELOG.md | 1 + Source/ASCellNode.h | 6 ++++++ Source/ASCellNode.mm | 1 + Source/ASTableView.mm | 4 ++-- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 140fcb916c..d389e0231b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## master * Add your own contributions to the next release on the line below this with your name. +- [ASCellNode] Adds mapping for UITableViewCell focusStyle [Alex Hill](https://github.com/alexhillc) [#727](https://github.com/TextureGroup/Texture/pull/727) - [ASNetworkImageNode] Fix capturing self in the block while loading image in ASNetworkImageNode. [Denis Mororozov](https://github.com/morozkin) [#777](https://github.com/TextureGroup/Texture/pull/777) - [ASTraitCollection] Add new properties of UITraitCollection to ASTraitCollection. [Yevgen Pogribnyi](https://github.com/ypogribnyi) - [ASRectMap] Replace implementation of ASRectTable with a simpler one based on unordered_map.[Scott Goodson](https://github.com/appleguy) [#719](https://github.com/TextureGroup/Texture/pull/719) diff --git a/Source/ASCellNode.h b/Source/ASCellNode.h index 2c911f17ae..b846e549d2 100644 --- a/Source/ASCellNode.h +++ b/Source/ASCellNode.h @@ -190,6 +190,12 @@ typedef NS_ENUM(NSUInteger, ASCellNodeVisibilityEvent) { */ @property (nonatomic) UITableViewCellSelectionStyle selectionStyle; +/* @abstract The focus style when a cell is focused + * @default UITableViewCellFocusStyleDefault + * ASTableView uses these properties when configuring UITableViewCells that host ASCellNodes. + */ +@property (nonatomic) UITableViewCellFocusStyle focusStyle; + /* @abstract The view used as the background of the cell when it is selected. * ASTableView uses these properties when configuring UITableViewCells that host ASCellNodes. * ASCollectionView uses these properties when configuring UICollectionViewCells that host ASCellNodes. diff --git a/Source/ASCellNode.mm b/Source/ASCellNode.mm index a3816df182..cd3bed4476 100644 --- a/Source/ASCellNode.mm +++ b/Source/ASCellNode.mm @@ -58,6 +58,7 @@ // Use UITableViewCell defaults _selectionStyle = UITableViewCellSelectionStyleDefault; + _focusStyle = UITableViewCellFocusStyleDefault; self.clipsToBounds = YES; return self; diff --git a/Source/ASTableView.mm b/Source/ASTableView.mm index f78a688ea8..9e135d6ad9 100644 --- a/Source/ASTableView.mm +++ b/Source/ASTableView.mm @@ -114,10 +114,10 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; if (node) { self.backgroundColor = node.backgroundColor; - self.selectionStyle = node.selectionStyle; self.selectedBackgroundView = node.selectedBackgroundView; self.separatorInset = node.separatorInset; - self.selectionStyle = node.selectionStyle; + self.selectionStyle = node.selectionStyle; + self.focusStyle = node.focusStyle; self.accessoryType = node.accessoryType; // the following ensures that we clip the entire cell to it's bounds if node.clipsToBounds is set (the default) From 0c4ccc5253346fa056e73229b08a4fb045074066 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Wed, 31 Jan 2018 12:18:04 -0800 Subject: [PATCH 25/40] Improve ASNetworkImageNode delegate callout behavior (#778) * Improve ASNetworkImageNode delegate callout behavior * no message --- CHANGELOG.md | 1 + Source/ASNetworkImageNode.mm | 52 +++++++++++++++++++----------------- 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d389e0231b..7d00152608 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - Modified `ASImageDownloaderCompletion` to add an optional `id userInfo` field. Your custom downloader can pass `nil`. - Modified the last argument to `-[ASNetworkImageNodeDelegate imageNode:didLoadImage:info:]` method from a struct to an object of new class `ASNetworkImageLoadInfo` which includes other metadata about the load operation. - Removed +load static initializer from ASDisplayNode. [Adlai Holler](https://github.com/Adlai-Holler) +- Optimized ASNetworkImageNode loading and resolved edge cases where the image provided to the delegate was not the image that was loaded. [Adlai Holler](https://github.com/Adlai-Holler) [#778](https://github.com/TextureGroup/Texture/pull/778/) ## 2.6 - [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) diff --git a/Source/ASNetworkImageNode.mm b/Source/ASNetworkImageNode.mm index 1189d58a0d..6cf70fa623 100755 --- a/Source/ASNetworkImageNode.mm +++ b/Source/ASNetworkImageNode.mm @@ -703,6 +703,7 @@ return; } + UIImage *newImage; if (imageContainer != nil) { [strongSelf _locked_setCurrentImageQuality:1.0]; NSData *animatedImageData = [imageContainer asdk_animatedImageData]; @@ -710,7 +711,8 @@ id animatedImage = [strongSelf->_downloader animatedImageWithData:animatedImageData]; [strongSelf _locked_setAnimatedImage:animatedImage]; } else { - [strongSelf _locked__setImage:[imageContainer asdk_image]]; + newImage = [imageContainer asdk_image]; + [strongSelf _locked__setImage:newImage]; } strongSelf->_imageLoaded = YES; } @@ -718,32 +720,32 @@ strongSelf->_downloadIdentifier = nil; strongSelf->_cacheUUID = nil; - // TODO: Why dispatch to main here? - // The docs say the image node delegate methods - // are called from background. - ASPerformBlockOnMainThread(^{ - __typeof__(self) strongSelf = weakSelf; - if (strongSelf == nil) { - return; - } - - // Grab the lock for the rest of the block - ASDN::MutexLocker l(strongSelf->__instanceLock__); - - if (imageContainer != nil) { - if (strongSelf->_delegateFlags.delegateDidLoadImageWithInfo) { - ASDN::MutexUnlocker u(strongSelf->__instanceLock__); + void (^calloutBlock)(ASNetworkImageNode *inst); + + if (newImage) { + if (_delegateFlags.delegateDidLoadImageWithInfo) { + calloutBlock = ^(ASNetworkImageNode *strongSelf) { auto info = [[ASNetworkImageLoadInfo alloc] initWithURL:URL sourceType:imageSource downloadIdentifier:downloadIdentifier userInfo:userInfo]; - [delegate imageNode:strongSelf didLoadImage:strongSelf.image info:info]; - } else if (strongSelf->_delegateFlags.delegateDidLoadImage) { - ASDN::MutexUnlocker u(strongSelf->__instanceLock__); - [delegate imageNode:strongSelf didLoadImage:strongSelf.image]; - } - } else if (error && strongSelf->_delegateFlags.delegateDidFailWithError) { - ASDN::MutexUnlocker u(strongSelf->__instanceLock__); - [delegate imageNode:strongSelf didFailWithError:error]; + [delegate imageNode:strongSelf didLoadImage:newImage info:info]; + }; + } else if (_delegateFlags.delegateDidLoadImage) { + calloutBlock = ^(ASNetworkImageNode *strongSelf) { + [delegate imageNode:strongSelf didLoadImage:newImage]; + }; } - }); + } else if (error && _delegateFlags.delegateDidFailWithError) { + calloutBlock = ^(ASNetworkImageNode *strongSelf) { + [delegate imageNode:strongSelf didFailWithError:error]; + }; + } + + if (calloutBlock) { + ASPerformBlockOnMainThread(^{ + if (auto strongSelf = weakSelf) { + calloutBlock(strongSelf); + } + }); + } }); }; From 0f061b401eaa871abbc24774e237eb903d43b10b Mon Sep 17 00:00:00 2001 From: Vladyslav Chapaev Date: Thu, 1 Feb 2018 17:43:46 +0300 Subject: [PATCH 26/40] node tint color fix (#764) --- Source/ASTableView.mm | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/ASTableView.mm b/Source/ASTableView.mm index 9e135d6ad9..acfe239062 100644 --- a/Source/ASTableView.mm +++ b/Source/ASTableView.mm @@ -119,6 +119,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; self.selectionStyle = node.selectionStyle; self.focusStyle = node.focusStyle; self.accessoryType = node.accessoryType; + self.tintColor = node.tintColor; // the following ensures that we clip the entire cell to it's bounds if node.clipsToBounds is set (the default) // This is actually a workaround for a bug we are seeing in some rare cases (selected background view From 1be4d8d088943e602850018a7b2f4aa8bd6db79b Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Thu, 1 Feb 2018 06:45:07 -0800 Subject: [PATCH 27/40] Add #764 to the changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d00152608..3dc967bc1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ - Modified the last argument to `-[ASNetworkImageNodeDelegate imageNode:didLoadImage:info:]` method from a struct to an object of new class `ASNetworkImageLoadInfo` which includes other metadata about the load operation. - Removed +load static initializer from ASDisplayNode. [Adlai Holler](https://github.com/Adlai-Holler) - Optimized ASNetworkImageNode loading and resolved edge cases where the image provided to the delegate was not the image that was loaded. [Adlai Holler](https://github.com/Adlai-Holler) [#778](https://github.com/TextureGroup/Texture/pull/778/) +- Make `ASCellNode` tint color apply to table view cell accessories. [Vladyslav Chapaev](https://github.com/ShogunPhyched) [#764](https://github.com/TextureGroup/Texture/pull/764) ## 2.6 - [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) From 38b76e0eb2d353fa4135a9091d2af49a3b831aec Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Thu, 1 Feb 2018 08:44:47 -0800 Subject: [PATCH 28/40] Improve nullable annotations for _ASDisplayLayer and _ASDisplayView (#780) --- Source/Details/_ASDisplayLayer.h | 16 ++++++++++++---- Source/Details/_ASDisplayView.h | 6 +++++- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/Source/Details/_ASDisplayLayer.h b/Source/Details/_ASDisplayLayer.h index 11ed695781..97c1e9489d 100644 --- a/Source/Details/_ASDisplayLayer.h +++ b/Source/Details/_ASDisplayLayer.h @@ -19,6 +19,8 @@ #import #import +NS_ASSUME_NONNULL_BEGIN + @class ASDisplayNode; @protocol _ASDisplayLayerDelegate; @@ -28,7 +30,7 @@ @discussion This property overrides the CALayer category method which implements this via associated objects. This should result in much better performance for _ASDisplayLayers. */ -@property (nonatomic, weak) ASDisplayNode *asyncdisplaykit_node; +@property (nullable, nonatomic, weak) ASDisplayNode *asyncdisplaykit_node; /** @summary Set to YES to enable asynchronous display for the receiver. @@ -57,7 +59,7 @@ @desc The asyncDelegate will have the opportunity to override the methods related to async display. */ -@property (atomic, weak) id<_ASDisplayLayerDelegate> asyncDelegate; +@property (nullable, atomic, weak) id<_ASDisplayLayerDelegate> asyncDelegate; /** @summary Suspends both asynchronous and synchronous display of the receiver if YES. @@ -109,7 +111,10 @@ @param isCancelledBlock Execute this block to check whether the current drawing operation has been cancelled to avoid unnecessary work. A return value of YES means cancel drawing and return. @param isRasterizing YES if the layer is being rasterized into another layer, in which case drawRect: probably wants to avoid doing things like filling its bounds with a zero-alpha color to clear the backing store. */ -+ (void)drawRect:(CGRect)bounds withParameters:(id)parameters isCancelled:(AS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing; ++ (void)drawRect:(CGRect)bounds + withParameters:(nullable id)parameters + isCancelled:(AS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelledBlock + isRasterizing:(BOOL)isRasterizing; /** @summary Delegate override to provide new layer contents as a UIImage. @@ -117,7 +122,8 @@ @param isCancelledBlock Execute this block to check whether the current drawing operation has been cancelled to avoid unnecessary work. A return value of YES means cancel drawing and return. @return A UIImage with contents that are ready to display on the main thread. Make sure that the image is already decoded before returning it here. */ -+ (UIImage *)displayWithParameters:(id)parameters isCancelled:(AS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelledBlock; ++ (UIImage *)displayWithParameters:(nullable id)parameters + isCancelled:(AS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelledBlock; // Called on the main thread only @@ -147,3 +153,5 @@ - (void)cancelDisplayAsyncLayer:(_ASDisplayLayer *)asyncLayer; @end + +NS_ASSUME_NONNULL_END diff --git a/Source/Details/_ASDisplayView.h b/Source/Details/_ASDisplayView.h index 6a0b9a3869..578dd8d582 100644 --- a/Source/Details/_ASDisplayView.h +++ b/Source/Details/_ASDisplayView.h @@ -17,6 +17,8 @@ #import +NS_ASSUME_NONNULL_BEGIN + // This class is only for use by ASDisplayNode and should never be subclassed or used directly. // Note that the "node" property is added to UIView directly via a category in ASDisplayNode. @@ -28,7 +30,7 @@ @discussion This property overrides the UIView category method which implements this via associated objects. This should result in much better performance for _ASDisplayView. */ -@property (nonatomic, weak) ASDisplayNode *asyncdisplaykit_node; +@property (nullable, nonatomic, weak) ASDisplayNode *asyncdisplaykit_node; // These methods expose a way for ASDisplayNode touch events to let the view call super touch events // Some UIKit mechanisms, like UITableView and UICollectionView selection handling, require this to work @@ -38,3 +40,5 @@ - (void)__forwardTouchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event; @end + +NS_ASSUME_NONNULL_END From c6454214acac298d049613681a1ad796fb3d0354 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Fri, 2 Feb 2018 10:36:17 -0800 Subject: [PATCH 29/40] Retain the reference color space (#784) --- Source/Details/ASGraphicsContext.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Details/ASGraphicsContext.m b/Source/Details/ASGraphicsContext.m index f87102fabb..3aaa14c090 100644 --- a/Source/Details/ASGraphicsContext.m +++ b/Source/Details/ASGraphicsContext.m @@ -154,7 +154,8 @@ extern UIImage * _Nullable ASGraphicsGetImageAndEndCurrentContext() dispatch_once(&onceToken, ^{ UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), YES, 0); UIImage *refImage = UIGraphicsGetImageFromCurrentImageContext(); - imageColorSpace = CGImageGetColorSpace(refImage.CGImage); + imageColorSpace = CGColorSpaceRetain(CGImageGetColorSpace(refImage.CGImage)); + ASDisplayNodeCAssertNotNil(imageColorSpace, nil); UIGraphicsEndImageContext(); }); From 5ea4d51596a298e3ca7a8bb8f59d66118e801788 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Fri, 2 Feb 2018 11:41:19 -0800 Subject: [PATCH 30/40] Get CatDealsCollectionView example running again #trivial (#783) * Get CatDealsCollectionView example running again * Fix it for real and some other styling stuff * Fix some warning * Adjust headers --- .../CatDealsCollectionView/Sample/BlurbNode.h | 18 +-- .../CatDealsCollectionView/Sample/BlurbNode.m | 37 +++-- .../CatDealsCollectionView/Sample/ItemNode.h | 1 - .../CatDealsCollectionView/Sample/ItemNode.m | 131 +++++++++++------- .../Sample/ItemStyles.h | 20 ++- .../Sample/ItemStyles.m | 25 ++-- .../Sample/ItemViewModel.m | 19 +-- .../Sample/LoadingNode.h | 20 ++- .../Sample/LoadingNode.m | 35 ++--- .../Sample/PlaceholderNetworkImageNode.h | 20 ++- .../Sample/PlaceholderNetworkImageNode.m | 24 ++-- .../Sample/ViewController.m | 24 ++-- 12 files changed, 194 insertions(+), 180 deletions(-) diff --git a/examples/CatDealsCollectionView/Sample/BlurbNode.h b/examples/CatDealsCollectionView/Sample/BlurbNode.h index e6574bcd05..e40f99d8a6 100644 --- a/examples/CatDealsCollectionView/Sample/BlurbNode.h +++ b/examples/CatDealsCollectionView/Sample/BlurbNode.h @@ -1,18 +1,18 @@ // // BlurbNode.h -// Sample +// Texture // // 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. +// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. // -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// Modifications to this file made after 4/13/2017 are: Copyright (c) through the present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 // #import diff --git a/examples/CatDealsCollectionView/Sample/BlurbNode.m b/examples/CatDealsCollectionView/Sample/BlurbNode.m index 7dbe86a8b8..0b358273c0 100644 --- a/examples/CatDealsCollectionView/Sample/BlurbNode.m +++ b/examples/CatDealsCollectionView/Sample/BlurbNode.m @@ -1,18 +1,18 @@ // // BlurbNode.m -// Sample +// Texture // // 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. +// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. // -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// Modifications to this file made after 4/13/2017 are: Copyright (c) through the present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 // #import "BlurbNode.h" @@ -57,16 +57,15 @@ static CGFloat kTextPadding = 10.0f; NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString:blurb]; [string addAttribute:NSFontAttributeName value:[UIFont fontWithName:@"HelveticaNeue-Light" size:16.0f] range:NSMakeRange(0, blurb.length)]; [string addAttributes:@{ - NSLinkAttributeName: [NSURL URLWithString:@"http://lorempixel.com/"], - NSForegroundColorAttributeName: [UIColor blueColor], - NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle | NSUnderlinePatternDot), - } - range:[blurb rangeOfString:@"lorempixel.com"]]; + NSLinkAttributeName: [NSURL URLWithString:@"http://lorempixel.com/"], + NSForegroundColorAttributeName: [UIColor blueColor], + NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle | NSUnderlinePatternDot), + } range:[blurb rangeOfString:@"lorempixel.com"]]; [string addAttributes:@{ - NSLinkAttributeName: [NSURL URLWithString:@"http://www.catipsum.com/"], - NSForegroundColorAttributeName: [UIColor blueColor], - NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle | NSUnderlinePatternDot), - } range:[blurb rangeOfString:@"catipsum.com"]]; + NSLinkAttributeName: [NSURL URLWithString:@"http://www.catipsum.com/"], + NSForegroundColorAttributeName: [UIColor blueColor], + NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle | NSUnderlinePatternDot), + } range:[blurb rangeOfString:@"catipsum.com"]]; _textNode.attributedText = string; // add it as a subnode, and we're done @@ -90,7 +89,7 @@ static CGFloat kTextPadding = 10.0f; centerSpec.sizingOptions = ASCenterLayoutSpecSizingOptionMinimumY; centerSpec.child = _textNode; - UIEdgeInsets padding =UIEdgeInsetsMake(kTextPadding, kTextPadding, kTextPadding, kTextPadding); + UIEdgeInsets padding = UIEdgeInsetsMake(kTextPadding, kTextPadding, kTextPadding, kTextPadding); return [ASInsetLayoutSpec insetLayoutSpecWithInsets:padding child:centerSpec]; } diff --git a/examples/CatDealsCollectionView/Sample/ItemNode.h b/examples/CatDealsCollectionView/Sample/ItemNode.h index 678327e111..bb0f06857d 100644 --- a/examples/CatDealsCollectionView/Sample/ItemNode.h +++ b/examples/CatDealsCollectionView/Sample/ItemNode.h @@ -20,7 +20,6 @@ @interface ItemNode : ASCellNode -- initWithViewModel:(ItemViewModel *)viewModel; + (CGSize)sizeForWidth:(CGFloat)width; + (CGSize)preferredViewSize; diff --git a/examples/CatDealsCollectionView/Sample/ItemNode.m b/examples/CatDealsCollectionView/Sample/ItemNode.m index 400f5b79e8..4fd76e8a01 100644 --- a/examples/CatDealsCollectionView/Sample/ItemNode.m +++ b/examples/CatDealsCollectionView/Sample/ItemNode.m @@ -20,6 +20,16 @@ #import "PlaceholderNetworkImageNode.h" #import +static CGFloat ASIsRTL() +{ + static BOOL __isRTL = NO; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + __isRTL = [UIApplication sharedApplication].userInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft; + }); + return __isRTL; +} + const CGFloat kFixedLabelsAreaHeight = 96.0; const CGFloat kDesignWidth = 320.0; const CGFloat kDesignHeight = 299.0; @@ -28,7 +38,7 @@ const CGFloat kSoldOutGBHeight = 50.0; @interface ItemNode() -@property (nonatomic, strong) ItemViewModel *viewModel; +@property (nonatomic, strong) ItemViewModel *nodeModel; @property (nonatomic, strong) PlaceholderNetworkImageNode *dealImageView; @@ -46,28 +56,35 @@ const CGFloat kSoldOutGBHeight = 50.0; @end @implementation ItemNode -@dynamic viewModel; +// Defined on ASCellNode +@dynamic nodeModel; -- (instancetype)initWithViewModel:(ItemViewModel *)viewModel ++ (void)load +{ + // Need to happen on main thread. + ASIsRTL(); +} + +- (instancetype)init { self = [super init]; if (self != nil) { - self.viewModel = viewModel; - [self setup]; - [self updateLabels]; + [self setupNodes]; [self updateBackgroundColor]; - - ASSetDebugName(self, @"Item #%zd", viewModel.identifier); - self.accessibilityIdentifier = viewModel.titleText; } return self; } -+ (BOOL)isRTL { - return [UIApplication sharedApplication].userInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft; +- (void)setNodeModel:(ItemViewModel *)nodeModel +{ + [super setNodeModel:nodeModel]; + + [self updateLabels]; + [self updateAccessibilityIdentifier]; } -- (void)setup { +- (void)setupNodes +{ self.dealImageView = [[PlaceholderNetworkImageNode alloc] init]; self.dealImageView.delegate = self; self.dealImageView.placeholderEnabled = YES; @@ -138,7 +155,7 @@ const CGFloat kSoldOutGBHeight = 50.0; self.soldOutLabelBackground.hidden = YES; self.soldOutLabelFlat.hidden = YES; - if ([ItemNode isRTL]) { + if (ASIsRTL()) { self.titleLabel.style.alignSelf = ASStackLayoutAlignSelfEnd; self.firstInfoLabel.style.alignSelf = ASStackLayoutAlignSelfEnd; self.distanceLabel.style.alignSelf = ASStackLayoutAlignSelfEnd; @@ -154,49 +171,56 @@ const CGFloat kSoldOutGBHeight = 50.0; } } -- (void)updateLabels { +- (void)updateLabels +{ // Set Title text - if (self.viewModel.titleText) { - self.titleLabel.attributedText = [[NSAttributedString alloc] initWithString:self.viewModel.titleText attributes:[ItemStyles titleStyle]]; + if (self.nodeModel.titleText) { + self.titleLabel.attributedText = [[NSAttributedString alloc] initWithString:self.nodeModel.titleText attributes:[ItemStyles titleStyle]]; } - if (self.viewModel.firstInfoText) { - self.firstInfoLabel.attributedText = [[NSAttributedString alloc] initWithString:self.viewModel.firstInfoText attributes:[ItemStyles subtitleStyle]]; + if (self.nodeModel.firstInfoText) { + self.firstInfoLabel.attributedText = [[NSAttributedString alloc] initWithString:self.nodeModel.firstInfoText attributes:[ItemStyles subtitleStyle]]; } - if (self.viewModel.secondInfoText) { - self.secondInfoLabel.attributedText = [[NSAttributedString alloc] initWithString:self.viewModel.secondInfoText attributes:[ItemStyles secondInfoStyle]]; + if (self.nodeModel.secondInfoText) { + self.secondInfoLabel.attributedText = [[NSAttributedString alloc] initWithString:self.nodeModel.secondInfoText attributes:[ItemStyles secondInfoStyle]]; } - if (self.viewModel.originalPriceText) { - self.originalPriceLabel.attributedText = [[NSAttributedString alloc] initWithString:self.viewModel.originalPriceText attributes:[ItemStyles originalPriceStyle]]; + if (self.nodeModel.originalPriceText) { + self.originalPriceLabel.attributedText = [[NSAttributedString alloc] initWithString:self.nodeModel.originalPriceText attributes:[ItemStyles originalPriceStyle]]; } - if (self.viewModel.finalPriceText) { - self.finalPriceLabel.attributedText = [[NSAttributedString alloc] initWithString:self.viewModel.finalPriceText attributes:[ItemStyles finalPriceStyle]]; + if (self.nodeModel.finalPriceText) { + self.finalPriceLabel.attributedText = [[NSAttributedString alloc] initWithString:self.nodeModel.finalPriceText attributes:[ItemStyles finalPriceStyle]]; } - if (self.viewModel.distanceLabelText) { - NSString *format = [ItemNode isRTL] ? @"%@ •" : @"• %@"; - NSString *distanceText = [NSString stringWithFormat:format, self.viewModel.distanceLabelText]; + if (self.nodeModel.distanceLabelText) { + NSString *format = ASIsRTL() ? @"%@ •" : @"• %@"; + NSString *distanceText = [NSString stringWithFormat:format, self.nodeModel.distanceLabelText]; self.distanceLabel.attributedText = [[NSAttributedString alloc] initWithString:distanceText attributes:[ItemStyles distanceStyle]]; } - BOOL isSoldOut = self.viewModel.soldOutText != nil; + BOOL isSoldOut = self.nodeModel.soldOutText != nil; if (isSoldOut) { - NSString *soldOutText = self.viewModel.soldOutText; + NSString *soldOutText = self.nodeModel.soldOutText; self.soldOutLabelFlat.attributedText = [[NSAttributedString alloc] initWithString:soldOutText attributes:[ItemStyles soldOutStyle]]; } self.soldOutOverlay.hidden = !isSoldOut; self.soldOutLabelFlat.hidden = !isSoldOut; self.soldOutLabelBackground.hidden = !isSoldOut; - BOOL hasBadge = self.viewModel.badgeText != nil; + BOOL hasBadge = self.nodeModel.badgeText != nil; if (hasBadge) { - self.badge.attributedText = [[NSAttributedString alloc] initWithString:self.viewModel.badgeText attributes:[ItemStyles badgeStyle]]; + self.badge.attributedText = [[NSAttributedString alloc] initWithString:self.nodeModel.badgeText attributes:[ItemStyles badgeStyle]]; self.badge.backgroundColor = [ItemStyles badgeColor]; } self.badge.hidden = !hasBadge; } +- (void)updateAccessibilityIdentifier +{ + ASSetDebugName(self, @"Item #%zd", self.nodeModel.identifier); + self.accessibilityIdentifier = self.nodeModel.titleText; +} + - (void)updateBackgroundColor { if (self.highlighted) { @@ -208,9 +232,6 @@ const CGFloat kSoldOutGBHeight = 50.0; } } -- (void)imageNode:(ASNetworkImageNode *)imageNode didLoadImage:(UIImage *)image { -} - - (void)setSelected:(BOOL)selected { [super setSelected:selected]; @@ -223,21 +244,22 @@ const CGFloat kSoldOutGBHeight = 50.0; [self updateBackgroundColor]; } -#pragma mark - superclass +#pragma mark - ASDisplayNode -- (void)displayWillStart { +- (void)displayWillStart +{ [super displayWillStart]; [self didEnterPreloadState]; } -- (void)didEnterPreloadState { +- (void)didEnterPreloadState +{ [super didEnterPreloadState]; - if (self.viewModel) { + if (self.nodeModel) { [self loadImage]; } } - - (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize { ASLayoutSpec *textSpec = [self textSpec]; @@ -253,7 +275,8 @@ const CGFloat kSoldOutGBHeight = 50.0; return soldOutOverlay; } -- (ASLayoutSpec *)textSpec { +- (ASLayoutSpec *)textSpec +{ CGFloat kInsetHorizontal = 16.0; CGFloat kInsetTop = 6.0; CGFloat kInsetBottom = 0.0; @@ -271,7 +294,7 @@ const CGFloat kSoldOutGBHeight = 50.0; NSArray *info1Children = @[self.firstInfoLabel, self.distanceLabel, horizontalSpacer1, self.originalPriceLabel]; NSArray *info2Children = @[self.secondInfoLabel, horizontalSpacer2, self.finalPriceLabel]; - if ([ItemNode isRTL]) { + if (ASIsRTL()) { info1Children = [[info1Children reverseObjectEnumerator] allObjects]; info2Children = [[info2Children reverseObjectEnumerator] allObjects]; } @@ -288,7 +311,8 @@ const CGFloat kSoldOutGBHeight = 50.0; return textWrapper; } -- (ASLayoutSpec *)imageSpecWithSize:(ASSizeRange)constrainedSize { +- (ASLayoutSpec *)imageSpecWithSize:(ASSizeRange)constrainedSize +{ CGFloat imageRatio = [self imageRatioFromSize:constrainedSize.max]; ASRatioLayoutSpec *imagePlace = [ASRatioLayoutSpec ratioLayoutSpecWithRatio:imageRatio child:self.dealImageView]; @@ -310,34 +334,38 @@ const CGFloat kSoldOutGBHeight = 50.0; return soldOutLabelOverBackground; } - -+ (CGSize)sizeForWidth:(CGFloat)width { ++ (CGSize)sizeForWidth:(CGFloat)width +{ CGFloat height = [self scaledHeightForPreferredSize:[self preferredViewSize] scaledWidth:width]; return CGSizeMake(width, height); } -+ (CGSize)preferredViewSize { ++ (CGSize)preferredViewSize +{ return CGSizeMake(kDesignWidth, kDesignHeight); } -+ (CGFloat)scaledHeightForPreferredSize:(CGSize)preferredSize scaledWidth:(CGFloat)scaledWidth { ++ (CGFloat)scaledHeightForPreferredSize:(CGSize)preferredSize scaledWidth:(CGFloat)scaledWidth +{ CGFloat scale = scaledWidth / kDesignWidth; CGFloat scaledHeight = ceilf(scale * (kDesignHeight - kFixedLabelsAreaHeight)) + kFixedLabelsAreaHeight; return scaledHeight; } -#pragma mark - view operations +#pragma mark - Image -- (CGFloat)imageRatioFromSize:(CGSize)size { +- (CGFloat)imageRatioFromSize:(CGSize)size +{ CGFloat imageHeight = size.height - kFixedLabelsAreaHeight; CGFloat imageRatio = imageHeight / size.width; return imageRatio; } -- (CGSize)imageSize { +- (CGSize)imageSize +{ if (!CGSizeEqualToSize(self.dealImageView.frame.size, CGSizeZero)) { return self.dealImageView.frame.size; } else if (!CGSizeEqualToSize(self.calculatedSize, CGSizeZero)) { @@ -349,13 +377,14 @@ const CGFloat kSoldOutGBHeight = 50.0; } } -- (void)loadImage { +- (void)loadImage +{ CGSize imageSize = [self imageSize]; if (CGSizeEqualToSize(CGSizeZero, imageSize)) { return; } - NSURL *url = [self.viewModel imageURLWithSize:imageSize]; + NSURL *url = [self.nodeModel imageURLWithSize:imageSize]; // if we're trying to set the deal image to what it already was, skip the work if ([[url absoluteString] isEqualToString:[self.dealImageView.URL absoluteString]]) { diff --git a/examples/CatDealsCollectionView/Sample/ItemStyles.h b/examples/CatDealsCollectionView/Sample/ItemStyles.h index a5af90a017..09d3b81c88 100644 --- a/examples/CatDealsCollectionView/Sample/ItemStyles.h +++ b/examples/CatDealsCollectionView/Sample/ItemStyles.h @@ -1,20 +1,18 @@ // // ItemStyles.h -// Sample -// -// Created by Samuel Stow on 12/30/15. +// Texture // // 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. +// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. // -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// Modifications to this file made after 4/13/2017 are: Copyright (c) through the present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 // #import diff --git a/examples/CatDealsCollectionView/Sample/ItemStyles.m b/examples/CatDealsCollectionView/Sample/ItemStyles.m index 12871e3be1..690980d49c 100644 --- a/examples/CatDealsCollectionView/Sample/ItemStyles.m +++ b/examples/CatDealsCollectionView/Sample/ItemStyles.m @@ -1,20 +1,18 @@ // // ItemStyles.m -// Sample -// -// Created by Samuel Stow on 12/30/15. +// Texture // // 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. +// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. // -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// Modifications to this file made after 4/13/2017 are: Copyright (c) through the present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 // #import "ItemStyles.h" @@ -95,9 +93,10 @@ UIFont *kInfoFont; + (UIImage *)placeholderImage { static UIImage *__catFace = nil; - if (!__catFace) { + static dispatch_once_t onceToken; + dispatch_once (&onceToken, ^{ __catFace = [UIImage imageNamed:@"cat_face"]; - } + }); return __catFace; } diff --git a/examples/CatDealsCollectionView/Sample/ItemViewModel.m b/examples/CatDealsCollectionView/Sample/ItemViewModel.m index 4cf925e5d6..6206e399c2 100644 --- a/examples/CatDealsCollectionView/Sample/ItemViewModel.m +++ b/examples/CatDealsCollectionView/Sample/ItemViewModel.m @@ -35,7 +35,8 @@ NSArray *badges; return [[ItemViewModel alloc] init]; } -- (instancetype)init { +- (instancetype)init +{ self = [super init]; if (self) { static _Atomic(NSInteger) nextID = ATOMIC_VAR_INIT(1); @@ -45,11 +46,9 @@ NSArray *badges; _secondInfoText = [NSString stringWithFormat:@"%zd+ bought", [self randomNumberInRange:5 to:6000]]; _originalPriceText = [NSString stringWithFormat:@"$%zd", [self randomNumberInRange:40 to:90]]; _finalPriceText = [NSString stringWithFormat:@"$%zd", [self randomNumberInRange:5 to:30]]; - BOOL isSoldOut = arc4random() % 5 == 0; - _soldOutText = isSoldOut ? @"SOLD OUT" : nil; + _soldOutText = (arc4random() % 5 == 0) ? @"SOLD OUT" : nil; _distanceLabelText = [NSString stringWithFormat:@"%zd mi", [self randomNumberInRange:1 to:20]]; - BOOL isBadged = arc4random() % 2 == 0; - if (isBadged) { + if (arc4random() % 2 == 0) { _badgeText = [self randomObjectFromArray:badges]; } _catNumber = [self randomNumberInRange:1 to:10]; @@ -58,18 +57,20 @@ NSArray *badges; return self; } -- (NSURL *)imageURLWithSize:(CGSize)size { +- (NSURL *)imageURLWithSize:(CGSize)size +{ NSString *imageText = [NSString stringWithFormat:@"Fun cat pic %zd", self.labelNumber]; NSString *urlString = [NSString stringWithFormat:@"http://lorempixel.com/%zd/%zd/cats/%zd/%@", (NSInteger)roundl(size.width), (NSInteger)roundl(size.height), self.catNumber, imageText]; - urlString = [urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; - + + urlString = [urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]]; return [NSURL URLWithString:urlString]; } // titles courtesy of http://www.catipsum.com/ -+ (void)initialize { ++ (void)initialize +{ titles = @[@"Leave fur on owners clothes intrigued by the shower", @"Meowwww", @"Immediately regret falling into bathtub stare out the window", diff --git a/examples/CatDealsCollectionView/Sample/LoadingNode.h b/examples/CatDealsCollectionView/Sample/LoadingNode.h index d144de01a9..6c07157265 100644 --- a/examples/CatDealsCollectionView/Sample/LoadingNode.h +++ b/examples/CatDealsCollectionView/Sample/LoadingNode.h @@ -1,20 +1,18 @@ // // LoadingNode.h -// Sample -// -// Created by Samuel Stow on 1/9/16. +// Texture // // 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. +// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. // -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// Modifications to this file made after 4/13/2017 are: Copyright (c) through the present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 // #import diff --git a/examples/CatDealsCollectionView/Sample/LoadingNode.m b/examples/CatDealsCollectionView/Sample/LoadingNode.m index 4fa29d6e3d..03a6825c65 100644 --- a/examples/CatDealsCollectionView/Sample/LoadingNode.m +++ b/examples/CatDealsCollectionView/Sample/LoadingNode.m @@ -1,41 +1,29 @@ // // LoadingNode.m -// Sample -// -// Created by Samuel Stow on 1/9/16. +// Texture // // 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. +// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. // -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// Modifications to this file made after 4/13/2017 are: Copyright (c) through the present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 // #import "LoadingNode.h" -#import -#import -#import #import -@interface LoadingNode () -{ +@implementation LoadingNode { ASDisplayNode *_loadingSpinner; } -@end - -@implementation LoadingNode - - -#pragma mark - -#pragma mark ASCellNode. +#pragma mark - ASCellNode - (instancetype)init { @@ -61,7 +49,6 @@ centerSpec.centeringOptions = ASCenterLayoutSpecCenteringXY; centerSpec.sizingOptions = ASCenterLayoutSpecSizingOptionDefault; centerSpec.child = _loadingSpinner; - return centerSpec; } diff --git a/examples/CatDealsCollectionView/Sample/PlaceholderNetworkImageNode.h b/examples/CatDealsCollectionView/Sample/PlaceholderNetworkImageNode.h index 53ba71544c..31470610bd 100644 --- a/examples/CatDealsCollectionView/Sample/PlaceholderNetworkImageNode.h +++ b/examples/CatDealsCollectionView/Sample/PlaceholderNetworkImageNode.h @@ -1,20 +1,18 @@ // // PlaceholderNetworkImageNode.h -// Sample -// -// Created by Samuel Stow on 1/14/16. +// Texture // // 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. +// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. // -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// Modifications to this file made after 4/13/2017 are: Copyright (c) through the present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 // #import diff --git a/examples/CatDealsCollectionView/Sample/PlaceholderNetworkImageNode.m b/examples/CatDealsCollectionView/Sample/PlaceholderNetworkImageNode.m index cb1b7e81bc..e7f0de47f9 100644 --- a/examples/CatDealsCollectionView/Sample/PlaceholderNetworkImageNode.m +++ b/examples/CatDealsCollectionView/Sample/PlaceholderNetworkImageNode.m @@ -1,29 +1,27 @@ // // PlaceholderNetworkImageNode.m -// Sample -// -// Created by Samuel Stow on 1/14/16. +// Texture // // 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. +// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. // -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// Modifications to this file made after 4/13/2017 are: Copyright (c) through the present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 // #import "PlaceholderNetworkImageNode.h" @implementation PlaceholderNetworkImageNode -- (UIImage *)placeholderImage { +- (UIImage *)placeholderImage +{ return self.placeholderImageOverride; } - @end diff --git a/examples/CatDealsCollectionView/Sample/ViewController.m b/examples/CatDealsCollectionView/Sample/ViewController.m index 6e63dabe09..78b58d67a6 100644 --- a/examples/CatDealsCollectionView/Sample/ViewController.m +++ b/examples/CatDealsCollectionView/Sample/ViewController.m @@ -50,7 +50,6 @@ static const CGFloat kHorizontalSectionPadding = 10.0f; self = [super initWithNode:_collectionNode]; if (self) { - self.title = @"Cat Deals"; _collectionNode.dataSource = self; @@ -89,7 +88,8 @@ static const CGFloat kHorizontalSectionPadding = 10.0f; [self fetchMoreCatsWithCompletion:nil]; } -- (void)fetchMoreCatsWithCompletion:(void (^)(BOOL))completion { +- (void)fetchMoreCatsWithCompletion:(void (^)(BOOL))completion +{ if (kSimulateWebResponse) { __weak typeof(self) weakSelf = self; void(^mockWebService)() = ^{ @@ -110,7 +110,8 @@ static const CGFloat kHorizontalSectionPadding = 10.0f; } } -- (void)appendMoreItems:(NSInteger)numberOfNewItems completion:(void (^)(BOOL))completion { +- (void)appendMoreItems:(NSInteger)numberOfNewItems completion:(void (^)(BOOL))completion +{ NSArray *newData = [self getMoreData:numberOfNewItems]; [_collectionNode performBatchAnimated:YES updates:^{ [_data addObjectsFromArray:newData]; @@ -119,7 +120,8 @@ static const CGFloat kHorizontalSectionPadding = 10.0f; } completion:completion]; } -- (NSArray *)getMoreData:(NSInteger)count { +- (NSArray *)getMoreData:(NSInteger)count +{ NSMutableArray *data = [NSMutableArray array]; for (int i = 0; i < count; i++) { [data addObject:[ItemViewModel randomItem]]; @@ -127,7 +129,8 @@ static const CGFloat kHorizontalSectionPadding = 10.0f; return data; } -- (NSArray *)indexPathsForObjects:(NSArray *)data { +- (NSArray *)indexPathsForObjects:(NSArray *)data +{ NSMutableArray *indexPaths = [NSMutableArray array]; NSInteger section = 0; for (ItemViewModel *viewModel in data) { @@ -138,7 +141,8 @@ static const CGFloat kHorizontalSectionPadding = 10.0f; return indexPaths; } -- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator { +- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator +{ [_collectionNode.view.collectionViewLayout invalidateLayout]; } @@ -151,12 +155,16 @@ static const CGFloat kHorizontalSectionPadding = 10.0f; - (ASCellNodeBlock)collectionNode:(ASCollectionNode *)collectionNode nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath { - ItemViewModel *viewModel = _data[indexPath.item]; return ^{ - return [[ItemNode alloc] initWithViewModel:viewModel]; + return [[ItemNode alloc] init]; }; } +- (id)collectionNode:(ASCollectionNode *)collectionNode nodeModelForItemAtIndexPath:(NSIndexPath *)indexPath +{ + return _data[indexPath.item]; +} + - (ASCellNode *)collectionNode:(ASCollectionNode *)collectionNode nodeForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { if ([kind isEqualToString:UICollectionElementKindSectionHeader] && indexPath.section == 0) { From 600b6cb76d01b3485e14539145303b80f7f18fe7 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Thu, 8 Feb 2018 08:28:14 -0800 Subject: [PATCH 31/40] Fix ASTextNode2 is accessing backgroundColor off main while sizing / layout is happening (#794) --- Source/ASTextNode2.mm | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/Source/ASTextNode2.mm b/Source/ASTextNode2.mm index ecd9496c51..54475368d8 100644 --- a/Source/ASTextNode2.mm +++ b/Source/ASTextNode2.mm @@ -232,7 +232,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; [self _ensureTruncationText]; NSMutableAttributedString *mutableText = [attributedText mutableCopy]; - [self prepareAttributedStringForDrawing:mutableText]; + [self prepareAttributedString:mutableText]; ASTextLayout *layout = [ASTextNode2 compatibleLayoutWithContainer:container text:mutableText]; [self setNeedsDisplay]; @@ -319,7 +319,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; return _textContainer.exclusionPaths; } -- (void)prepareAttributedStringForDrawing:(NSMutableAttributedString *)attributedString +- (void)prepareAttributedString:(NSMutableAttributedString *)attributedString { ASDN::MutexLocker lock(__instanceLock__); @@ -334,12 +334,6 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; [attributedString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:range]; }]; - // Apply background color if needed - UIColor *backgroundColor = self.backgroundColor; - if (CGColorGetAlpha(backgroundColor.CGColor) > 0) { - [attributedString addAttribute:NSBackgroundColorAttributeName value:backgroundColor range:NSMakeRange(0, attributedString.length)]; - } - // Apply shadow if needed if (_shadowOpacity > 0 && (_shadowRadius != 0 || !CGSizeEqualToSize(_shadowOffset, CGSizeZero)) && CGColorGetAlpha(_shadowColor) > 0) { NSShadow *shadow = [[NSShadow alloc] init]; @@ -362,11 +356,19 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; ASTextContainer *copiedContainer = [_textContainer copy]; copiedContainer.size = self.bounds.size; NSMutableAttributedString *mutableText = [self.attributedText mutableCopy] ?: [[NSMutableAttributedString alloc] init]; - [self prepareAttributedStringForDrawing:mutableText]; + + [self prepareAttributedString:mutableText]; + + // Apply background color if needed before drawing. To access the backgroundColor we need to be on the main thread + UIColor *backgroundColor = self.backgroundColor; + if (CGColorGetAlpha(backgroundColor.CGColor) > 0) { + [mutableText addAttribute:NSBackgroundColorAttributeName value:backgroundColor range:NSMakeRange(0, mutableText.length)]; + } + return @{ - @"container": copiedContainer, - @"text": mutableText - }; + @"container": copiedContainer, + @"text": mutableText + }; } /** From 3ee52e5f3b58efe8a5ee7c532914bb3f6dc9c292 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Thu, 8 Feb 2018 08:34:12 -0800 Subject: [PATCH 32/40] Add #794 to changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3dc967bc1d..1ebb9d5732 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ - Removed +load static initializer from ASDisplayNode. [Adlai Holler](https://github.com/Adlai-Holler) - Optimized ASNetworkImageNode loading and resolved edge cases where the image provided to the delegate was not the image that was loaded. [Adlai Holler](https://github.com/Adlai-Holler) [#778](https://github.com/TextureGroup/Texture/pull/778/) - Make `ASCellNode` tint color apply to table view cell accessories. [Vladyslav Chapaev](https://github.com/ShogunPhyched) [#764](https://github.com/TextureGroup/Texture/pull/764) +- Fix ASTextNode2 is accessing backgroundColor off main while sizing / layout is happening. [Michael Schneider](https://github.com/maicki) [#794](https://github.com/TextureGroup/Texture/pull/778/) ## 2.6 - [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) From b4a269aabf9eab1d08c6abb3f98266959da29196 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Thu, 8 Feb 2018 17:08:04 +0000 Subject: [PATCH 33/40] [ASDisplayNode] Always return the thread-safe cornerRadius property, even in slow CALayer rounding mode (#749) - Failing to do so will introduce race conditions in which the property was updated on a background thread but main thread has not executed the block that updates the property of the node's layer. During that window, the layer's property is out-of-date and can't be used. - After this change, ASDisplayNode's cornerRadius is the only source of truth and users must always use it instead of CALayer's. --- CHANGELOG.md | 1 + Source/ASDisplayNode.h | 6 ++++++ Source/Private/ASDisplayNode+UIViewBridge.mm | 12 +++--------- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ebb9d5732..1392553a98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## master * Add your own contributions to the next release on the line below this with your name. +- **Important** ASDisplayNode's cornerRadius is a new thread-safe bridged property that should be preferred over CALayer's. Use the latter at your own risk! [Huy Nguyen](https://github.com/nguyenhuy) [#749](https://github.com/TextureGroup/Texture/pull/749). - [ASCellNode] Adds mapping for UITableViewCell focusStyle [Alex Hill](https://github.com/alexhillc) [#727](https://github.com/TextureGroup/Texture/pull/727) - [ASNetworkImageNode] Fix capturing self in the block while loading image in ASNetworkImageNode. [Denis Mororozov](https://github.com/morozkin) [#777](https://github.com/TextureGroup/Texture/pull/777) - [ASTraitCollection] Add new properties of UITraitCollection to ASTraitCollection. [Yevgen Pogribnyi](https://github.com/ypogribnyi) diff --git a/Source/ASDisplayNode.h b/Source/ASDisplayNode.h index feecc3ad94..49f2d917ca 100644 --- a/Source/ASDisplayNode.h +++ b/Source/ASDisplayNode.h @@ -659,6 +659,12 @@ extern NSInteger const ASDefaultDrawingPriority; * @default ASCornerRoundingTypeDefaultSlowCALayer */ @property (nonatomic, assign) ASCornerRoundingType cornerRoundingType; // default=Slow CALayer .cornerRadius (offscreen rendering) + +/** @abstract The radius to use when rounding corners of the ASDisplayNode. + * + * @discussion This property is thread-safe and should always be preferred over CALayer's cornerRadius property, + * even if corner rounding type is ASCornerRoundingTypeDefaultSlowCALayer. + */ @property (nonatomic, assign) CGFloat cornerRadius; // default=0.0 @property (nonatomic, assign) BOOL clipsToBounds; // default==NO diff --git a/Source/Private/ASDisplayNode+UIViewBridge.mm b/Source/Private/ASDisplayNode+UIViewBridge.mm index a3eceb7412..578caa7f64 100644 --- a/Source/Private/ASDisplayNode+UIViewBridge.mm +++ b/Source/Private/ASDisplayNode+UIViewBridge.mm @@ -186,17 +186,12 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNo - (CGFloat)cornerRadius { ASDN::MutexLocker l(__instanceLock__); - if (_cornerRoundingType == ASCornerRoundingTypeDefaultSlowCALayer) { - return self.layerCornerRadius; - } else { - return _cornerRadius; - } + return _cornerRadius; } - (void)setCornerRadius:(CGFloat)newCornerRadius { - ASDN::MutexLocker l(__instanceLock__); - [self updateCornerRoundingWithType:_cornerRoundingType cornerRadius:newCornerRadius]; + [self updateCornerRoundingWithType:self.cornerRoundingType cornerRadius:newCornerRadius]; } - (ASCornerRoundingType)cornerRoundingType @@ -207,8 +202,7 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNo - (void)setCornerRoundingType:(ASCornerRoundingType)newRoundingType { - ASDN::MutexLocker l(__instanceLock__); - [self updateCornerRoundingWithType:newRoundingType cornerRadius:_cornerRadius]; + [self updateCornerRoundingWithType:newRoundingType cornerRadius:self.cornerRadius]; } - (NSString *)contentsGravity From 31227da5779365672305ae774f4da171b682c878 Mon Sep 17 00:00:00 2001 From: appleguy Date: Fri, 9 Feb 2018 11:04:00 -0800 Subject: [PATCH 34/40] [ASRangeController] Fix stability of "minimum" rangeMode if the app has more than one layout before scrolling. (#790) This should result in memory savings in many apps, since errant relayouts are pretty common. --- CHANGELOG.md | 1 + Source/ASCollectionView.mm | 7 ++++--- Source/ASTableView.mm | 9 ++++---- Source/Details/ASRangeController.h | 5 +++++ Source/Details/ASRangeController.mm | 32 +++++++++++++++++++++-------- 5 files changed, 38 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1392553a98..0b50cd3a58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## master * Add your own contributions to the next release on the line below this with your name. +- [ASRangeController] Fix stability of "minimum" rangeMode if the app has more than one layout before scrolling. - **Important** ASDisplayNode's cornerRadius is a new thread-safe bridged property that should be preferred over CALayer's. Use the latter at your own risk! [Huy Nguyen](https://github.com/nguyenhuy) [#749](https://github.com/TextureGroup/Texture/pull/749). - [ASCellNode] Adds mapping for UITableViewCell focusStyle [Alex Hill](https://github.com/alexhillc) [#727](https://github.com/TextureGroup/Texture/pull/727) - [ASNetworkImageNode] Fix capturing self in the block while loading image in ASNetworkImageNode. [Denis Mororozov](https://github.com/morozkin) [#777](https://github.com/TextureGroup/Texture/pull/777) diff --git a/Source/ASCollectionView.mm b/Source/ASCollectionView.mm index 1b06d6befd..05ed5ebf9d 100644 --- a/Source/ASCollectionView.mm +++ b/Source/ASCollectionView.mm @@ -1549,13 +1549,10 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; - (void)scrollViewDidScroll:(UIScrollView *)scrollView { - // If a scroll happenes the current range mode needs to go to full ASInterfaceState interfaceState = [self interfaceStateForRangeController:_rangeController]; if (ASInterfaceStateIncludesVisible(interfaceState)) { - [_rangeController updateCurrentRangeWithMode:ASLayoutRangeModeFull]; [self _checkForBatchFetching]; } - for (_ASCollectionViewCell *cell in _cellsForVisibilityUpdates) { // _cellsForVisibilityUpdates only includes cells for ASCellNode subclasses with overrides of the visibility method. [cell cellNodeVisibilityEvent:ASCellNodeVisibilityEventVisibleRectChanged inScrollView:scrollView]; @@ -1594,6 +1591,10 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { + // If a scroll happens the current range mode needs to go to full + _rangeController.contentHasBeenScrolled = YES; + [_rangeController updateCurrentRangeWithMode:ASLayoutRangeModeFull]; + for (_ASCollectionViewCell *cell in _cellsForVisibilityUpdates) { [cell cellNodeVisibilityEvent:ASCellNodeVisibilityEventWillBeginDragging inScrollView:scrollView]; } diff --git a/Source/ASTableView.mm b/Source/ASTableView.mm index acfe239062..1b03797caf 100644 --- a/Source/ASTableView.mm +++ b/Source/ASTableView.mm @@ -1214,13 +1214,10 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; [super scrollViewDidScroll:scrollView]; return; } - // If a scroll happenes the current range mode needs to go to full ASInterfaceState interfaceState = [self interfaceStateForRangeController:_rangeController]; if (ASInterfaceStateIncludesVisible(interfaceState)) { - [_rangeController updateCurrentRangeWithMode:ASLayoutRangeModeFull]; [self _checkForBatchFetching]; - } - + } for (_ASTableViewCell *tableCell in _cellsForVisibilityUpdates) { [[tableCell node] cellNodeVisibilityEvent:ASCellNodeVisibilityEventVisibleRectChanged inScrollView:scrollView @@ -1272,6 +1269,10 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; [super scrollViewWillBeginDragging:scrollView]; return; } + // If a scroll happens the current range mode needs to go to full + _rangeController.contentHasBeenScrolled = YES; + [_rangeController updateCurrentRangeWithMode:ASLayoutRangeModeFull]; + for (_ASTableViewCell *tableViewCell in _cellsForVisibilityUpdates) { [[tableViewCell node] cellNodeVisibilityEvent:ASCellNodeVisibilityEventWillBeginDragging inScrollView:scrollView diff --git a/Source/Details/ASRangeController.h b/Source/Details/ASRangeController.h index 46a5cbf693..ca47ef862c 100644 --- a/Source/Details/ASRangeController.h +++ b/Source/Details/ASRangeController.h @@ -100,6 +100,11 @@ AS_SUBCLASSING_RESTRICTED */ @property (nonatomic, weak) id delegate; +/** + * Property that indicates whether the scroll view for this range controller has ever changed its contentOffset. + */ +@property (nonatomic, assign) BOOL contentHasBeenScrolled; + @end diff --git a/Source/Details/ASRangeController.mm b/Source/Details/ASRangeController.mm index 818fb3a1d1..3bb4044289 100644 --- a/Source/Details/ASRangeController.mm +++ b/Source/Details/ASRangeController.mm @@ -45,6 +45,7 @@ NSSet *_allPreviousIndexPaths; NSHashTable *_visibleNodes; ASLayoutRangeMode _currentRangeMode; + BOOL _contentHasBeenScrolled; BOOL _preserveCurrentRangeMode; BOOL _didRegisterForNodeDisplayNotifications; CFTimeInterval _pendingDisplayNodesTimestamp; @@ -77,6 +78,7 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive; _rangeIsValid = YES; _currentRangeMode = ASLayoutRangeModeUnspecified; + _contentHasBeenScrolled = NO; _preserveCurrentRangeMode = NO; _previousScrollDirection = ASScrollDirectionDown | ASScrollDirectionRight; @@ -222,10 +224,6 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive; auto visibleElements = [_dataSource visibleElementsForRangeController:self]; NSHashTable *newVisibleNodes = [NSHashTable hashTableWithOptions:NSHashTableObjectPointerPersonality]; - if (visibleElements.count == 0) { // if we don't have any visibleNodes currently (scrolled before or after content)... - [self _setVisibleNodes:newVisibleNodes]; - return; // don't do anything for this update, but leave _rangeIsValid == NO to make sure we update it later - } ASSignpostStart(ASSignpostRangeControllerUpdate); // Get the scroll direction. Default to using the previous one, if they're not scrolling. @@ -234,12 +232,28 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive; scrollDirection = _previousScrollDirection; } _previousScrollDirection = scrollDirection; - + + if (visibleElements.count == 0) { // if we don't have any visibleNodes currently (scrolled before or after content)... + // Verify the actual state by checking the layout with a "VisibleOnly" range. + // This allows us to avoid thrashing through -didExitVisibleState in the case of -reloadData, since that generates didEndDisplayingCell calls. + // Those didEndDisplayingCell calls result in items being removed from the visibleElements returned by the _dataSource, even though the layout remains correct. + visibleElements = [_layoutController elementsForScrolling:scrollDirection rangeMode:ASLayoutRangeModeVisibleOnly rangeType:ASLayoutRangeTypeDisplay map:map]; + for (ASCollectionElement *element in visibleElements) { + [newVisibleNodes addObject:element.node]; + } + [self _setVisibleNodes:newVisibleNodes]; + return; // don't do anything for this update, but leave _rangeIsValid == NO to make sure we update it later + } + ASInterfaceState selfInterfaceState = [self interfaceState]; ASLayoutRangeMode rangeMode = _currentRangeMode; - // If the range mode is explicitly set via updateCurrentRangeWithMode: it will last in that mode until the - // range controller becomes visible again or explicitly changes the range mode again - if ((!_preserveCurrentRangeMode && ASInterfaceStateIncludesVisible(selfInterfaceState)) || [[self class] isFirstRangeUpdateForRangeMode:rangeMode]) { + BOOL updateRangeMode = (!_preserveCurrentRangeMode && _contentHasBeenScrolled); + + // If we've never scrolled before, we never update the range mode, so it doesn't jump into Full too early. + // This can happen if we have multiple, noisy updates occurring from application code before the user has engaged. + // If the range mode is explicitly set via updateCurrentRangeWithMode:, we'll preserve that for at least one update cycle. + // Once the user has scrolled and the range is visible, we'll always resume managing the range mode automatically. + if ((updateRangeMode && ASInterfaceStateIncludesVisible(selfInterfaceState)) || [[self class] isFirstRangeUpdateForRangeMode:rangeMode]) { rangeMode = [ASRangeController rangeModeForInterfaceState:selfInterfaceState currentRangeMode:_currentRangeMode]; } @@ -412,7 +426,7 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive; // NSLog(@"custom: %@", visibleNodePathsSet); // } [modifiedIndexPaths sortUsingSelector:@selector(compare:)]; - NSLog(@"Range update complete; modifiedIndexPaths: %@", [self descriptionWithIndexPaths:modifiedIndexPaths]); + NSLog(@"Range update complete; modifiedIndexPaths: %@, rangeMode: %d", [self descriptionWithIndexPaths:modifiedIndexPaths], rangeMode); #endif ASSignpostEnd(ASSignpostRangeControllerUpdate); From 479d40464e78c6ac1b39406b2bf6059238b8ba85 Mon Sep 17 00:00:00 2001 From: Max Wang Date: Fri, 9 Feb 2018 12:16:18 -0800 Subject: [PATCH 35/40] [ASTableNode & ASCollectionNode] Keepalive reference for node if their view is necessarily alive (has a superview). (#793) * fix SIMULATE_WEB_RESPONSE not imported #449 * Fix to make rangeMode update in right time * Keep collection/table node alive if view still in use. --- Source/ASCollectionNode.mm | 7 +++++++ Source/ASCollectionView.mm | 21 ++++++++++++++++++++- Source/ASTableNode.mm | 7 +++++++ Source/ASTableView.mm | 21 ++++++++++++++++++++- Tests/ASCollectionViewTests.mm | 1 + 5 files changed, 55 insertions(+), 2 deletions(-) diff --git a/Source/ASCollectionNode.mm b/Source/ASCollectionNode.mm index 2449377fd2..01669ca79e 100644 --- a/Source/ASCollectionNode.mm +++ b/Source/ASCollectionNode.mm @@ -172,6 +172,13 @@ return self; } +- (void)dealloc +{ + if ([self isNodeLoaded]) { + ASDisplayNodeAssert(self.view.superview == nil, @"Node's view should be removed from hierarchy."); + } +} + #pragma mark ASDisplayNode - (void)didLoad diff --git a/Source/ASCollectionView.mm b/Source/ASCollectionView.mm index 05ed5ebf9d..10dd6dc9a5 100644 --- a/Source/ASCollectionView.mm +++ b/Source/ASCollectionView.mm @@ -151,7 +151,12 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; * Counter used to keep track of nested batch updates. */ NSInteger _batchUpdateCount; - + + /** + * Keep a strong reference to node till view is ready to release. + */ + ASCollectionNode *_keepalive_node; + struct { unsigned int scrollViewDidScroll:1; unsigned int scrollViewWillBeginDragging:1; @@ -2250,6 +2255,20 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; } } +- (void)willMoveToSuperview:(UIView *)newSuperview +{ + if (self.superview == nil && newSuperview != nil) { + _keepalive_node = self.collectionNode; + } +} + +- (void)didMoveToSuperview +{ + if (self.superview == nil) { + _keepalive_node = nil; + } +} + #pragma mark ASCALayerExtendedDelegate /** diff --git a/Source/ASTableNode.mm b/Source/ASTableNode.mm index d68e4a9d72..09e5eb9bd4 100644 --- a/Source/ASTableNode.mm +++ b/Source/ASTableNode.mm @@ -104,6 +104,13 @@ return [self initWithStyle:UITableViewStylePlain]; } +- (void)dealloc +{ + if ([self isNodeLoaded]) { + ASDisplayNodeAssert(self.view.superview == nil, @"Node's view should be removed from hierarchy."); + } +} + #pragma mark ASDisplayNode - (void)didLoad diff --git a/Source/ASTableView.mm b/Source/ASTableView.mm index 1b03797caf..1896b37cf7 100644 --- a/Source/ASTableView.mm +++ b/Source/ASTableView.mm @@ -216,7 +216,12 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; * Counter used to keep track of nested batch updates. */ NSInteger _batchUpdateCount; - + + /** + * Keep a strong reference to node till view is ready to release. + */ + ASTableNode *_keepalive_node; + struct { unsigned int scrollViewDidScroll:1; unsigned int scrollViewWillBeginDragging:1; @@ -1913,4 +1918,18 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } } +- (void)willMoveToSuperview:(UIView *)newSuperview +{ + if (self.superview == nil && newSuperview != nil) { + _keepalive_node = self.tableNode; + } +} + +- (void)didMoveToSuperview +{ + if (self.superview == nil) { + _keepalive_node = nil; + } +} + @end diff --git a/Tests/ASCollectionViewTests.mm b/Tests/ASCollectionViewTests.mm index 0391e992da..311d7943f3 100644 --- a/Tests/ASCollectionViewTests.mm +++ b/Tests/ASCollectionViewTests.mm @@ -1071,6 +1071,7 @@ for (NSInteger i = 0; i < c; i++) { NSIndexPath *ip = [NSIndexPath indexPathForItem:i inSection:s]; ASCellNode *node = [cn nodeForItemAtIndexPath:ip]; + [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.001]]; if (node.inPreloadState) { CGRect frame = [cn.view layoutAttributesForItemAtIndexPath:ip].frame; r = CGRectUnion(r, frame); From f0f3f9acfe312bb54530bfc1701e3a8b1d24b0a5 Mon Sep 17 00:00:00 2001 From: Kevin Bui Date: Mon, 12 Feb 2018 11:38:20 -0800 Subject: [PATCH 36/40] Add missing scrollViewWillEndDragging passthrough delegate (#796) * Add scrollViewWillEndDragging delegate * Make sure delegate can respond to scrollViewWillEndDragging * Add changes to CHANGELOG.md --- CHANGELOG.md | 1 + Source/Private/ASIGListAdapterBasedDataSource.m | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b50cd3a58..7704a477b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ - Optimized ASNetworkImageNode loading and resolved edge cases where the image provided to the delegate was not the image that was loaded. [Adlai Holler](https://github.com/Adlai-Holler) [#778](https://github.com/TextureGroup/Texture/pull/778/) - Make `ASCellNode` tint color apply to table view cell accessories. [Vladyslav Chapaev](https://github.com/ShogunPhyched) [#764](https://github.com/TextureGroup/Texture/pull/764) - Fix ASTextNode2 is accessing backgroundColor off main while sizing / layout is happening. [Michael Schneider](https://github.com/maicki) [#794](https://github.com/TextureGroup/Texture/pull/778/) +- Pass scrollViewWillEndDragging delegation through in ASIGListAdapterDataSource for IGListKit integration. [#796](https://github.com/TextureGroup/Texture/pull/796) ## 2.6 - [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) diff --git a/Source/Private/ASIGListAdapterBasedDataSource.m b/Source/Private/ASIGListAdapterBasedDataSource.m index 31a6c4582b..ab18636228 100644 --- a/Source/Private/ASIGListAdapterBasedDataSource.m +++ b/Source/Private/ASIGListAdapterBasedDataSource.m @@ -102,6 +102,14 @@ typedef struct { [self.delegate scrollViewWillBeginDragging:scrollView]; } +- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset +{ + // IGListAdapter doesn't implement scrollViewWillEndDragging yet (pending pull request), so we need this check for now. Doesn't hurt to have it anyways :) + if ([self.delegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) { + [self.delegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset]; + } +} + - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { [self.delegate scrollViewDidEndDragging:scrollView willDecelerate:decelerate]; From e2478fc7995b6ad48801c2a5138c002e4c750c85 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Mon, 12 Feb 2018 21:04:58 +0000 Subject: [PATCH 37/40] [ASDisplayNode layout] Fix an issue that sometimes causes a node's pending layout to not be applied (#792) * [ASDisplayNode layout] Fix an issue that causes a node's pending layout to not be applied - Since the implementation of layout version (#428), if a node's pending and calculated layouts have the same current version as well as the same constrained size, the 2 layouts are considered equal and can be used interchangeably. A layout version check between the 2 layouts was added in #695. This PR adds a missing constrained size check. - If the pending layout has the same version but a different constrained size compare to the calculated layout's, we can assume that the pending layout is newer and should be preferred over the calculated one. That is because layout operations always register their new layout as pending, which then (immediately or eventually) get applied to the node as calculated layout. --- CHANGELOG.md | 3 ++- Source/ASDisplayNode+Layout.mm | 28 ++++++++++++++++++++++------ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7704a477b4..4716156416 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,8 @@ - [ASNetworkImageNode] Deprecates .URLs in favor of .URL [Garrett Moon](https://github.com/garrettmoon) [#699](https://github.com/TextureGroup/Texture/pull/699) - [iOS11] Update project settings and fix errors [Eke](https://github.com/Eke) [#676](https://github.com/TextureGroup/Texture/pull/676) - [ASCornerLayoutSpec] New layout spec class for declarative corner element layout. [#657](https://github.com/TextureGroup/Texture/pull/657) [huangkun](https://github.com/huang-kun) -- [Layout] Fix an issue that causes a pending layout to be applied multiple times. [Huy Nguyen](https://github.com/nguyenhuy) [#695](https://github.com/TextureGroup/Texture/pull/695) +- [ASDisplayNode layout] Fix an issue that causes a pending layout to be applied multiple times. [Huy Nguyen](https://github.com/nguyenhuy) [#695](https://github.com/TextureGroup/Texture/pull/695) +- [ASDisplayNode layout] Fix an issue that sometimes causes a node's pending layout to not be applied. [Huy Nguyen](https://github.com/nguyenhuy) [#792](https://github.com/TextureGroup/Texture/pull/792) - [ASScrollNode] Ensure the node respects the given size range while calculating its layout. [#637](https://github.com/TextureGroup/Texture/pull/637) [Huy Nguyen](https://github.com/nguyenhuy) - [ASScrollNode] Invalidate the node's calculated layout if its scrollable directions changed. Also add unit tests for the class. [#637](https://github.com/TextureGroup/Texture/pull/637) [Huy Nguyen](https://github.com/nguyenhuy) - Add new unit testing to the layout engine. [Adlai Holler](https://github.com/Adlai-Holler) [#424](https://github.com/TextureGroup/Texture/pull/424) diff --git a/Source/ASDisplayNode+Layout.mm b/Source/ASDisplayNode+Layout.mm index dd0a2e2adf..084f4f912a 100644 --- a/Source/ASDisplayNode+Layout.mm +++ b/Source/ASDisplayNode+Layout.mm @@ -2,8 +2,13 @@ // ASDisplayNode+Layout.mm // Texture // -// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. -// Licensed under the Apache License, Version 2.0 (the "License"); +// 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 /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. +// +// Modifications to this file made after 4/13/2017 are: Copyright (c) through the present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // @@ -300,13 +305,24 @@ ASLayoutElementStyleExtensibilityForwarding } CGSize boundsSizeForLayout = ASCeilSizeValues(bounds.size); + NSUInteger calculatedVersion = _calculatedDisplayNodeLayout->version; // Prefer a newer and not yet applied _pendingDisplayNodeLayout over _calculatedDisplayNodeLayout // If there is no such _pending, check if _calculated is valid to reuse (avoiding recalculation below). - BOOL pendingLayoutIsPreferred = (_pendingDisplayNodeLayout != nullptr - && _pendingDisplayNodeLayout->version >= _layoutVersion - && _pendingDisplayNodeLayout->version > _calculatedDisplayNodeLayout->version); // _pending is not yet applied - BOOL calculatedLayoutIsReusable = (_calculatedDisplayNodeLayout->version >= _layoutVersion + BOOL pendingLayoutIsPreferred = NO; + if (_pendingDisplayNodeLayout != nullptr) { + NSUInteger pendingVersion = _pendingDisplayNodeLayout->version; + if (pendingVersion >= _layoutVersion) { + if (pendingVersion > calculatedVersion) { + pendingLayoutIsPreferred = YES; // Newer _pending + } else if (pendingVersion == calculatedVersion + && !ASSizeRangeEqualToSizeRange(_pendingDisplayNodeLayout->constrainedSize, + _calculatedDisplayNodeLayout->constrainedSize)) { + pendingLayoutIsPreferred = YES; // _pending with a different constrained size + } + } + } + BOOL calculatedLayoutIsReusable = (calculatedVersion >= _layoutVersion && (_calculatedDisplayNodeLayout->requestedLayoutFromAbove || CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, boundsSizeForLayout))); if (!pendingLayoutIsPreferred && calculatedLayoutIsReusable) { From 8b431733d38d08c838caaffe44ae26c1949d4b46 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Tue, 13 Feb 2018 12:02:30 -0800 Subject: [PATCH 38/40] Avoid triggering main thread assertions in collection/table dealloc #trivial (#803) * Avoid triggering main thread assertions in ASCollectionNode/ASTableNode dealloc * Put it back --- Source/ASCollectionNode.mm | 9 +++++++-- Source/ASTableNode.mm | 9 +++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/Source/ASCollectionNode.mm b/Source/ASCollectionNode.mm index 01669ca79e..d0cc5f2084 100644 --- a/Source/ASCollectionNode.mm +++ b/Source/ASCollectionNode.mm @@ -172,12 +172,17 @@ return self; } +#if ASDISPLAYNODE_ASSERTIONS_ENABLED - (void)dealloc { - if ([self isNodeLoaded]) { - ASDisplayNodeAssert(self.view.superview == nil, @"Node's view should be removed from hierarchy."); + if (self.nodeLoaded) { + __weak UIView *view = self.view; + ASPerformBlockOnMainThread(^{ + ASDisplayNodeCAssertNil(view.superview, @"Node's view should be removed from hierarchy."); + }); } } +#endif #pragma mark ASDisplayNode diff --git a/Source/ASTableNode.mm b/Source/ASTableNode.mm index 09e5eb9bd4..c35f90a878 100644 --- a/Source/ASTableNode.mm +++ b/Source/ASTableNode.mm @@ -104,12 +104,17 @@ return [self initWithStyle:UITableViewStylePlain]; } +#if ASDISPLAYNODE_ASSERTIONS_ENABLED - (void)dealloc { - if ([self isNodeLoaded]) { - ASDisplayNodeAssert(self.view.superview == nil, @"Node's view should be removed from hierarchy."); + if (self.nodeLoaded) { + __weak UIView *view = self.view; + ASPerformBlockOnMainThread(^{ + ASDisplayNodeCAssertNil(view.superview, @"Node's view should be removed from hierarchy."); + }); } } +#endif #pragma mark ASDisplayNode From 6f34691481fad4c69a3ad1b40ea008b6065cb319 Mon Sep 17 00:00:00 2001 From: John T McIntosh Date: Tue, 13 Feb 2018 14:08:31 -0600 Subject: [PATCH 39/40] Update IGListKit dependency to allow for updated versions (#802) --- Texture.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Texture.podspec b/Texture.podspec index 8b75b43343..b780df2830 100644 --- a/Texture.podspec +++ b/Texture.podspec @@ -51,7 +51,7 @@ Pod::Spec.new do |spec| end spec.subspec 'IGListKit' do |igl| - igl.dependency 'IGListKit', '3.0.0' + igl.dependency 'IGListKit', '~> 3.0' igl.dependency 'Texture/Core' end From 2618c50073092e99fe1df5213ba6a0619e908988 Mon Sep 17 00:00:00 2001 From: Max Wang Date: Tue, 13 Feb 2018 12:10:20 -0800 Subject: [PATCH 40/40] New runloop queue to coalesce Interface state update calls. (#788) * fix SIMULATE_WEB_RESPONSE not imported #449 * Coalesce interface state updates to ASCATransactionQueue before CATransaction commit. This will avoid duplicate interface state delegate calls caused by view repeatly added/removed to/from hierarchy during controller animation transition. * fix tests for new run loop queue * Support for disabling ASCATransactionQueue * Fix didExitHierarchy to use ASCATransactionQueue. * merge range managed and none range managed for didExitHierarchy * Revert "merge range managed and none range managed for didExitHierarchy" This reverts commit f807efaa65ed5dbdb6622d06da542e01a53715fa. * merge range managed and none range managed for didExitHierarchy * remove metadata * abstract queue to impl class methods * Add tests * Fix test fail because of shared object. * guard _pendingInterfaceState access with lock * name refactor * Refactor from comments https://github.com/TextureGroup/Texture/pull/788/\#pullrequestreview-94849919 * Apply InterfaceState immediately after ASCATranactionQueue is processed and before next runloop started. * refactor * no op to start CI build * remove unused var and kick off tests * change lisence * remove code for weak ref * add change log and adjust license --- CHANGELOG.md | 1 + Source/ASDisplayNode.mm | 96 ++++-- Source/ASRunLoopQueue.h | 35 ++- Source/ASRunLoopQueue.mm | 293 ++++++++++++++++-- .../Private/ASDisplayNode+FrameworkPrivate.h | 4 + Source/Private/ASDisplayNodeInternal.h | 2 +- Tests/ASCollectionViewTests.mm | 7 +- Tests/ASDisplayNodeImplicitHierarchyTests.m | 2 + Tests/ASDisplayNodeTests.mm | 19 +- Tests/ASDisplayNodeTestsHelper.h | 1 + Tests/ASDisplayNodeTestsHelper.m | 12 + Tests/ASRunLoopQueueTests.m | 36 ++- Tests/ASVideoNodeTests.m | 3 +- 13 files changed, 445 insertions(+), 66 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4716156416..5716f6857c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## master * Add your own contributions to the next release on the line below this with your name. +- [ASRunloopQueue] Introduce new runloop queue(ASCATransactionQueue) to coalesce Interface state update calls for view controller transitions. - [ASRangeController] Fix stability of "minimum" rangeMode if the app has more than one layout before scrolling. - **Important** ASDisplayNode's cornerRadius is a new thread-safe bridged property that should be preferred over CALayer's. Use the latter at your own risk! [Huy Nguyen](https://github.com/nguyenhuy) [#749](https://github.com/TextureGroup/Texture/pull/749). - [ASCellNode] Adds mapping for UITableViewCell focusStyle [Alex Hill](https://github.com/alexhillc) [#727](https://github.com/TextureGroup/Texture/pull/727) diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index 1e772e5148..5d6f305d39 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -63,7 +63,7 @@ NSInteger const ASDefaultDrawingPriority = ASDefaultTransactionPriority; // We have to forward declare the protocol as this place otherwise it will not compile compiling with an Base SDK < iOS 10 @protocol CALayerDelegate; -@interface ASDisplayNode () +@interface ASDisplayNode () /** * See ASDisplayNodeInternal.h for ivars @@ -2739,7 +2739,7 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) { // Entered or exited range managed state. if ((newState & ASHierarchyStateRangeManaged) != (oldState & ASHierarchyStateRangeManaged)) { if (newState & ASHierarchyStateRangeManaged) { - [self enterInterfaceState:self.supernode.interfaceState]; + [self enterInterfaceState:self.supernode.pendingInterfaceState]; } else { // The case of exiting a range-managed state should be fairly rare. Adding or removing the node // to a view hierarchy will cause its interfaceState to be either fully set or unset (all fields), @@ -2782,30 +2782,34 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) { 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"); ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__); - - if (![self supportsRangeManagedInterfaceState]) { - self.interfaceState = ASInterfaceStateNone; - } else { - // This case is important when tearing down hierarchies. We must deliver a visibileStateDidChange:NO callback, as part our API guarantee that this method can be used for - // things like data analytics about user content viewing. We cannot call the method in the dealloc as any incidental retain operations in client code would fail. - // Additionally, it may be that a Standard UIView which is containing us is moving between hierarchies, and we should not send the call if we will be re-added in the - // same runloop. Strategy: strong reference (might be the last!), wait one runloop, and confirm we are still outside the hierarchy (both layer-backed and view-backed). - // TODO: This approach could be optimized by only performing the dispatch for root elements + recursively apply the interface state change. This would require a closer - // integration with _ASDisplayLayer to ensure that the superlayer pointer has been cleared by this stage (to check if we are root or not), or a different delegate call. - - if (ASInterfaceStateIncludesVisible(self.interfaceState)) { - dispatch_async(dispatch_get_main_queue(), ^{ - // This block intentionally retains self. - __instanceLock__.lock(); - unsigned isInHierarchy = _flags.isInHierarchy; - BOOL isVisible = ASInterfaceStateIncludesVisible(_interfaceState); - ASInterfaceState newState = (_interfaceState & ~ASInterfaceStateVisible); - __instanceLock__.unlock(); - - if (!isInHierarchy && isVisible) { - self.interfaceState = newState; + + // This case is important when tearing down hierarchies. We must deliver a visibileStateDidChange:NO callback, as part our API guarantee that this method can be used for + // things like data analytics about user content viewing. We cannot call the method in the dealloc as any incidental retain operations in client code would fail. + // Additionally, it may be that a Standard UIView which is containing us is moving between hierarchies, and we should not send the call if we will be re-added in the + // same runloop. Strategy: strong reference (might be the last!), wait one runloop, and confirm we are still outside the hierarchy (both layer-backed and view-backed). + // TODO: This approach could be optimized by only performing the dispatch for root elements + recursively apply the interface state change. This would require a closer + // integration with _ASDisplayLayer to ensure that the superlayer pointer has been cleared by this stage (to check if we are root or not), or a different delegate call. + if (ASInterfaceStateIncludesVisible(_pendingInterfaceState)) { + void(^exitVisibleInterfaceState)(void) = ^{ + // This block intentionally retains self. + __instanceLock__.lock(); + unsigned isStillInHierarchy = _flags.isInHierarchy; + BOOL isVisible = ASInterfaceStateIncludesVisible(_pendingInterfaceState); + ASInterfaceState newState = (_pendingInterfaceState & ~ASInterfaceStateVisible); + __instanceLock__.unlock(); + + if (!isStillInHierarchy && isVisible) { + if (![self supportsRangeManagedInterfaceState]) { + newState = ASInterfaceStateNone; } - }); + self.interfaceState = newState; + } + }; + + if ([[ASCATransactionQueue sharedQueue] disabled]) { + dispatch_async(dispatch_get_main_queue(), exitVisibleInterfaceState); + } else { + exitVisibleInterfaceState(); } } } @@ -2866,25 +2870,53 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) { } - (void)setInterfaceState:(ASInterfaceState)newState +{ + if ([[ASCATransactionQueue sharedQueue] disabled]) { + [self applyPendingInterfaceState:newState]; + } else { + ASDN::MutexLocker l(__instanceLock__); + if (_pendingInterfaceState != newState) { + _pendingInterfaceState = newState; + [[ASCATransactionQueue sharedQueue] enqueue:self]; + } + } +} + +- (ASInterfaceState)pendingInterfaceState +{ + ASDN::MutexLocker l(__instanceLock__); + return _pendingInterfaceState; +} + +- (void)applyPendingInterfaceState:(ASInterfaceState)newPendingState { //This method is currently called on the main thread. The assert has been added here because all of the //did(Enter|Exit)(Display|Visible|Preload)State methods currently guarantee calling on main. ASDisplayNodeAssertMainThread(); - // It should never be possible for a node to be visible but not be allowed / expected to display. - ASDisplayNodeAssertFalse(ASInterfaceStateIncludesVisible(newState) && !ASInterfaceStateIncludesDisplay(newState)); + // This method manages __instanceLock__ itself, to ensure the lock is not held while didEnter/Exit(.*)State methods are called, thus avoid potential deadlocks ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__); ASInterfaceState oldState = ASInterfaceStateNone; + ASInterfaceState newState = ASInterfaceStateNone; { ASDN::MutexLocker l(__instanceLock__); - if (_interfaceState == newState) { - return; + // newPendingState will not be used when ASCATransactionQueue is enabled + // and use _pendingInterfaceState instead for interfaceState update. + if ([[ASCATransactionQueue sharedQueue] disabled]) { + _pendingInterfaceState = newPendingState; } oldState = _interfaceState; + newState = _pendingInterfaceState; + if (newState == oldState) { + return; + } _interfaceState = newState; } + // It should never be possible for a node to be visible but not be allowed / expected to display. + ASDisplayNodeAssertFalse(ASInterfaceStateIncludesVisible(newState) && !ASInterfaceStateIncludesDisplay(newState)); + // TODO: Trigger asynchronous measurement if it is not already cached or being calculated. // if ((newState & ASInterfaceStateMeasureLayout) != (oldState & ASInterfaceStateMeasureLayout)) { // } @@ -2981,6 +3013,12 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) { [self interfaceStateDidChange:newState fromState:oldState]; } +- (void)prepareForCATransactionCommit +{ + // Apply _pendingInterfaceState actual _interfaceState, note that ASInterfaceStateNone is not used. + [self applyPendingInterfaceState:ASInterfaceStateNone]; +} + - (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState { // Subclass hook diff --git a/Source/ASRunLoopQueue.h b/Source/ASRunLoopQueue.h index 50a0eeb249..02711910bd 100644 --- a/Source/ASRunLoopQueue.h +++ b/Source/ASRunLoopQueue.h @@ -20,8 +20,15 @@ NS_ASSUME_NONNULL_BEGIN +@protocol ASCATransactionQueueObserving +- (void)prepareForCATransactionCommit; +@end + +@interface ASAbstractRunLoopQueue : NSObject +@end + AS_SUBCLASSING_RESTRICTED -@interface ASRunLoopQueue : NSObject +@interface ASRunLoopQueue : ASAbstractRunLoopQueue /** * Create a new queue with the given run loop and handler. @@ -41,13 +48,37 @@ AS_SUBCLASSING_RESTRICTED - (void)enqueue:(ObjectType)object; -@property (nonatomic, readonly) BOOL isEmpty; +@property (atomic, readonly) BOOL isEmpty; @property (nonatomic, assign) NSUInteger batchSize; // Default == 1. @property (nonatomic, assign) BOOL ensureExclusiveMembership; // Default == YES. Set-like behavior. @end +AS_SUBCLASSING_RESTRICTED +@interface ASCATransactionQueue : ASAbstractRunLoopQueue + +@property (atomic, readonly) BOOL isEmpty; +@property (atomic, readonly) BOOL disabled; +/** + * The queue to run on main run loop before CATransaction commit. + * + * @discussion this queue will run after ASRunLoopQueue and before CATransaction commit + * to get last chance of updating/coalesce info like interface state. + * Each node will only be called once per transaction commit to reflect interface change. + */ +@property (class, atomic, readonly) ASCATransactionQueue *sharedQueue; + +- (void)enqueue:(id)object; + +/** + * @abstract Apply a node's interfaceState immediately rather than adding to the queue. + */ +- (void)disable; + +@end + + AS_SUBCLASSING_RESTRICTED @interface ASDeallocQueue : NSObject diff --git a/Source/ASRunLoopQueue.mm b/Source/ASRunLoopQueue.mm index 974e348142..12265d1dfa 100644 --- a/Source/ASRunLoopQueue.mm +++ b/Source/ASRunLoopQueue.mm @@ -181,27 +181,6 @@ static void runLoopSourceCallback(void *info) { @end -#pragma mark - ASRunLoopQueue - -@interface ASRunLoopQueue () { - CFRunLoopRef _runLoop; - CFRunLoopSourceRef _runLoopSource; - CFRunLoopObserverRef _runLoopObserver; - NSPointerArray *_internalQueue; // Use NSPointerArray so we can decide __strong or __weak per-instance. - ASDN::RecursiveMutex _internalQueueLock; - - // In order to not pollute the top-level activities, each queue has 1 root activity. - os_activity_t _rootActivity; - -#if ASRunLoopQueueLoggingEnabled - NSTimer *_runloopQueueLoggingTimer; -#endif -} - -@property (nonatomic, copy) void (^queueConsumer)(id dequeuedItem, BOOL isQueueDrained); - -@end - #if AS_KDEBUG_ENABLE /** * This is real, private CA API. Valid as of iOS 10. @@ -218,7 +197,23 @@ typedef enum { @end #endif -@implementation ASRunLoopQueue +#pragma mark - ASAbstractRunLoopQueue + +@interface ASAbstractRunLoopQueue (Private) ++ (void)load; ++ (void)registerCATransactionObservers; +@end + +@implementation ASAbstractRunLoopQueue + +- (instancetype)init +{ + if (self != [super init]) { + return nil; + } + ASDisplayNodeAssert(self.class != [ASAbstractRunLoopQueue class], @"Should never create instances of abstract class ASAbstractRunLoopQueue."); + return self; +} #if AS_KDEBUG_ENABLE + (void)load @@ -265,6 +260,31 @@ typedef enum { #endif // AS_KDEBUG_ENABLE +@end + +#pragma mark - ASRunLoopQueue + +@interface ASRunLoopQueue () { + CFRunLoopRef _runLoop; + CFRunLoopSourceRef _runLoopSource; + CFRunLoopObserverRef _runLoopObserver; + NSPointerArray *_internalQueue; // Use NSPointerArray so we can decide __strong or __weak per-instance. + ASDN::RecursiveMutex _internalQueueLock; + + // In order to not pollute the top-level activities, each queue has 1 root activity. + os_activity_t _rootActivity; + +#if ASRunLoopQueueLoggingEnabled + NSTimer *_runloopQueueLoggingTimer; +#endif +} + +@property (nonatomic, copy) void (^queueConsumer)(id dequeuedItem, BOOL isQueueDrained); + +@end + +@implementation ASRunLoopQueue + - (instancetype)initWithRunLoop:(CFRunLoopRef)runloop retainObjects:(BOOL)retainsObjects handler:(void (^)(id _Nullable, BOOL))handlerBlock { if (self = [super init]) { @@ -464,3 +484,232 @@ typedef enum { } @end + +#pragma mark - ASCATransactionQueue + +@interface ASCATransactionQueue () { + CFRunLoopRef _runLoop; + CFRunLoopSourceRef _runLoopSource; + CFRunLoopObserverRef _preTransactionObserver; + CFRunLoopObserverRef _postTransactionObserver; + NSPointerArray *_internalQueue; + ASDN::RecursiveMutex _internalQueueLock; + BOOL _disableInterfaceStateCoalesce; + BOOL _CATransactionCommitInProgress; + + // In order to not pollute the top-level activities, each queue has 1 root activity. + os_activity_t _rootActivity; + +#if ASRunLoopQueueLoggingEnabled + NSTimer *_runloopQueueLoggingTimer; +#endif +} + +@end + +@implementation ASCATransactionQueue + +// CoreAnimation commit order is 2000000, the goal of this is to process shortly beforehand +// but after most other scheduled work on the runloop has processed. +static int const kASASCATransactionQueueOrder = 1000000; +// This will mark the end of current loop and any node enqueued between kASASCATransactionQueueOrder +// and kASASCATransactionQueuePostOrder will apply interface change immediately. +static int const kASASCATransactionQueuePostOrder = 3000000; + ++ (ASCATransactionQueue *)sharedQueue +{ + static dispatch_once_t onceToken; + static ASCATransactionQueue *sharedQueue; + dispatch_once(&onceToken, ^{ + sharedQueue = [[ASCATransactionQueue alloc] init]; + }); + return sharedQueue; +} + +- (instancetype)init +{ + if (self = [super init]) { + _runLoop = CFRunLoopGetMain(); + NSPointerFunctionsOptions options = NSPointerFunctionsStrongMemory; + _internalQueue = [[NSPointerArray alloc] initWithOptions:options]; + + // We don't want to pollute the top-level app activities with run loop batches, so we create one top-level + // activity per queue, and each batch activity joins that one instead. + _rootActivity = as_activity_create("Process run loop queue items", OS_ACTIVITY_NONE, OS_ACTIVITY_FLAG_DEFAULT); + { + // Log a message identifying this queue into the queue's root activity. + as_activity_scope_verbose(_rootActivity); + as_log_verbose(ASDisplayLog(), "Created run loop queue: %@", self); + } + + // Self is guaranteed to outlive the observer. Without the high cost of a weak pointer, + // __unsafe_unretained allows us to avoid flagging the memory cycle detector. + __unsafe_unretained __typeof__(self) weakSelf = self; + void (^handlerBlock) (CFRunLoopObserverRef observer, CFRunLoopActivity activity) = ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { + [weakSelf processQueue]; + }; + void (^postHandlerBlock) (CFRunLoopObserverRef observer, CFRunLoopActivity activity) = ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { + ASDN::MutexLocker l(_internalQueueLock); + _CATransactionCommitInProgress = NO; + }; + _preTransactionObserver = CFRunLoopObserverCreateWithHandler(NULL, kCFRunLoopBeforeWaiting, true, kASASCATransactionQueueOrder, handlerBlock); + _postTransactionObserver = CFRunLoopObserverCreateWithHandler(NULL, kCFRunLoopBeforeWaiting, true, kASASCATransactionQueuePostOrder, postHandlerBlock); + + CFRunLoopAddObserver(_runLoop, _preTransactionObserver, kCFRunLoopCommonModes); + CFRunLoopAddObserver(_runLoop, _postTransactionObserver, kCFRunLoopCommonModes); + + // It is not guaranteed that the runloop will turn if it has no scheduled work, and this causes processing of + // the queue to stop. Attaching a custom loop source to the run loop and signal it if new work needs to be done + CFRunLoopSourceContext sourceContext = {}; + sourceContext.perform = runLoopSourceCallback; +#if ASRunLoopQueueLoggingEnabled + sourceContext.info = (__bridge void *)self; +#endif + _runLoopSource = CFRunLoopSourceCreate(NULL, 0, &sourceContext); + CFRunLoopAddSource(_runLoop, _runLoopSource, kCFRunLoopCommonModes); + +#if ASRunLoopQueueLoggingEnabled + _runloopQueueLoggingTimer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(checkRunLoop) userInfo:nil repeats:YES]; + [[NSRunLoop mainRunLoop] addTimer:_runloopQueueLoggingTimer forMode:NSRunLoopCommonModes]; +#endif + } + return self; +} + +- (void)dealloc +{ + CFRunLoopRemoveSource(_runLoop, _runLoopSource, kCFRunLoopCommonModes); + CFRelease(_runLoopSource); + _runLoopSource = nil; + + if (CFRunLoopObserverIsValid(_preTransactionObserver)) { + CFRunLoopObserverInvalidate(_preTransactionObserver); + } + if (CFRunLoopObserverIsValid(_postTransactionObserver)) { + CFRunLoopObserverInvalidate(_postTransactionObserver); + } + CFRelease(_preTransactionObserver); + CFRelease(_postTransactionObserver); + _preTransactionObserver = nil; + _postTransactionObserver = nil; +} + +#if ASRunLoopQueueLoggingEnabled +- (void)checkRunLoop +{ + NSLog(@"<%@> - Jobs: %ld", self, _internalQueue.size()); +} +#endif + +- (void)processQueue +{ + // If we have an execution block, this vector will be populated, otherwise remains empty. + // This is to avoid needlessly retaining/releasing the objects if we don't have a block. + std::vector itemsToProcess; + + { + ASDN::MutexLocker l(_internalQueueLock); + + // Mark the queue will end coalescing shortly until after CATransactionCommit. + // This will give the queue a chance to apply any further interfaceState changes/enqueue + // immediately within current runloop instead of pushing the work to next runloop cycle. + _CATransactionCommitInProgress = YES; + + NSInteger internalQueueCount = _internalQueue.count; + // Early-exit if the queue is empty. + if (internalQueueCount == 0) { + return; + } + + ASSignpostStart(ASSignpostRunLoopQueueBatch); + + /** + * For each item in the next batch, if it's non-nil then NULL it out + * and if we have an execution block then add it in. + * This could be written a bunch of different ways but + * this particular one nicely balances readability, safety, and efficiency. + */ + NSInteger foundItemCount = 0; + for (NSInteger i = 0; i < internalQueueCount && foundItemCount < internalQueueCount; i++) { + /** + * It is safe to use unsafe_unretained here. If the queue is weak, the + * object will be added to the autorelease pool. If the queue is strong, + * it will retain the object until we transfer it (retain it) in itemsToProcess. + */ + __unsafe_unretained id ptr = (__bridge id)[_internalQueue pointerAtIndex:i]; + if (ptr != nil) { + foundItemCount++; + itemsToProcess.push_back(ptr); + [_internalQueue replacePointerAtIndex:i withPointer:NULL]; + } + } + + [_internalQueue compact]; + } + + // itemsToProcess will be empty if _queueConsumer == nil so no need to check again. + auto count = itemsToProcess.size(); + if (count > 0) { + as_activity_scope_verbose(as_activity_create("Process run loop queue batch", _rootActivity, OS_ACTIVITY_FLAG_DEFAULT)); + auto itemsEnd = itemsToProcess.cend(); + for (auto iterator = itemsToProcess.begin(); iterator < itemsEnd; iterator++) { + __unsafe_unretained id value = *iterator; + [value prepareForCATransactionCommit]; + as_log_verbose(ASDisplayLog(), "processed %@", value); + } + if (count > 1) { + as_log_verbose(ASDisplayLog(), "processed %lu items", (unsigned long)count); + } + } + + ASSignpostEnd(ASSignpostRunLoopQueueBatch); +} + +- (void)enqueue:(id)object +{ + if (!object) { + return; + } + + if (_disableInterfaceStateCoalesce || _CATransactionCommitInProgress) { + [object prepareForCATransactionCommit]; + return; + } + + ASDN::MutexLocker l(_internalQueueLock); + + // Check if the object exists. + BOOL foundObject = NO; + + for (id currentObject in _internalQueue) { + if (currentObject == object) { + foundObject = YES; + break; + } + } + + if (!foundObject) { + [_internalQueue addPointer:(__bridge void *)object]; + + CFRunLoopSourceSignal(_runLoopSource); + CFRunLoopWakeUp(_runLoop); + } +} + +- (BOOL)isEmpty +{ + ASDN::MutexLocker l(_internalQueueLock); + return _internalQueue.count == 0; +} + +- (void)disable +{ + _disableInterfaceStateCoalesce = YES; +} + +- (BOOL)disabled +{ + return _disableInterfaceStateCoalesce; +} + +@end diff --git a/Source/Private/ASDisplayNode+FrameworkPrivate.h b/Source/Private/ASDisplayNode+FrameworkPrivate.h index dbb5207879..b309bdcc22 100644 --- a/Source/Private/ASDisplayNode+FrameworkPrivate.h +++ b/Source/Private/ASDisplayNode+FrameworkPrivate.h @@ -141,6 +141,10 @@ __unused static NSString * _Nonnull NSStringFromASHierarchyStateChange(ASHierarc // delegate to inform of ASInterfaceState changes (used by ASNodeController) @property (nonatomic, weak) id interfaceStateDelegate; +// The -pendingInterfaceState holds the value that will be applied to -interfaceState by the +// ASCATransactionQueue. If already applied, it matches -interfaceState. Thread-safe access. +@property (nonatomic, readonly) ASInterfaceState pendingInterfaceState; + // These methods are recursive, and either union or remove the provided interfaceState to all sub-elements. - (void)enterInterfaceState:(ASInterfaceState)interfaceState; - (void)exitInterfaceState:(ASInterfaceState)interfaceState; diff --git a/Source/Private/ASDisplayNodeInternal.h b/Source/Private/ASDisplayNodeInternal.h index 1be9e8c94c..545f1c5a20 100644 --- a/Source/Private/ASDisplayNodeInternal.h +++ b/Source/Private/ASDisplayNodeInternal.h @@ -77,7 +77,7 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo { @package _ASPendingState *_pendingViewState; - + ASInterfaceState _pendingInterfaceState; UIView *_view; CALayer *_layer; diff --git a/Tests/ASCollectionViewTests.mm b/Tests/ASCollectionViewTests.mm index 311d7943f3..c3dc8a2c47 100644 --- a/Tests/ASCollectionViewTests.mm +++ b/Tests/ASCollectionViewTests.mm @@ -23,6 +23,8 @@ #import #import #import +#import +#import "ASDisplayNodeTestsHelper.h" @interface ASTextCellNodeWithSetSelectedCounter : ASTextCellNode @@ -860,6 +862,7 @@ [cn waitUntilAllUpdatesAreProcessed]; [cn.view layoutIfNeeded]; ASCellNode *node = [cn nodeForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; + ASCATransactionQueueWait(); XCTAssertTrue(node.visible); testController.asyncDelegate->_itemCounts = {0}; [cn deleteItemsAtIndexPaths: @[[NSIndexPath indexPathForItem:0 inSection:0]]]; @@ -1047,7 +1050,7 @@ window.rootViewController = testController; [window makeKeyAndVisible]; - // Trigger the initial reload to start + // Trigger the initial reload to start [window layoutIfNeeded]; // Test the APIs that monitor ASCollectionNode update handling @@ -1071,7 +1074,7 @@ for (NSInteger i = 0; i < c; i++) { NSIndexPath *ip = [NSIndexPath indexPathForItem:i inSection:s]; ASCellNode *node = [cn nodeForItemAtIndexPath:ip]; - [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.001]]; + ASCATransactionQueueWait(); if (node.inPreloadState) { CGRect frame = [cn.view layoutAttributesForItemAtIndexPath:ip].frame; r = CGRectUnion(r, frame); diff --git a/Tests/ASDisplayNodeImplicitHierarchyTests.m b/Tests/ASDisplayNodeImplicitHierarchyTests.m index 11e2ce4afa..d033ac4f6e 100644 --- a/Tests/ASDisplayNodeImplicitHierarchyTests.m +++ b/Tests/ASDisplayNodeImplicitHierarchyTests.m @@ -114,6 +114,7 @@ } ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init]; + [node setHierarchyState:ASHierarchyStateRangeManaged]; node.automaticallyManagesSubnodes = YES; node.layoutSpecBlock = ^(ASDisplayNode *weakNode, ASSizeRange constrainedSize) { ASAbsoluteLayoutSpec *absoluteLayout = [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[subnodes[3]]]; @@ -130,6 +131,7 @@ ASDisplayNodeSizeToFitSizeRange(node, ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX))); [node recursivelySetInterfaceState:ASInterfaceStatePreload]; + ASCATransactionQueueWait(); // No premature view allocation XCTAssertFalse(node.isNodeLoaded); // Subnodes should be inserted, laid out and entered preload state diff --git a/Tests/ASDisplayNodeTests.mm b/Tests/ASDisplayNodeTests.mm index ef5baee058..17bf960e0a 100644 --- a/Tests/ASDisplayNodeTests.mm +++ b/Tests/ASDisplayNodeTests.mm @@ -91,7 +91,7 @@ for (ASDisplayNode *n in @[ nodes ]) {\ @interface ASDisplayNode (HackForTests) - (id)initWithViewClass:(Class)viewClass; - (id)initWithLayerClass:(Class)layerClass; - +- (void)setInterfaceState:(ASInterfaceState)state; // FIXME: Importing ASDisplayNodeInternal.h causes a heap of problems. - (void)enterInterfaceState:(ASInterfaceState)interfaceState; @end @@ -122,6 +122,12 @@ for (ASDisplayNode *n in @[ nodes ]) {\ @implementation ASTestDisplayNode +- (void)setInterfaceState:(ASInterfaceState)state +{ + [super setInterfaceState:state]; + ASCATransactionQueueWait(); +} + - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize { return _calculateSizeBlock ? _calculateSizeBlock(self, constrainedSize) : CGSizeZero; @@ -2058,9 +2064,9 @@ static bool stringContainsPointer(NSString *description, id p) { // Underlying issue for: https://github.com/facebook/AsyncDisplayKit/issues/2205 - (void)testThatRasterizedNodesGetInterfaceStateUpdatesWhenContainerEntersHierarchy { - ASDisplayNode *supernode = [[ASDisplayNode alloc] init]; + ASDisplayNode *supernode = [[ASTestDisplayNode alloc] init]; [supernode enableSubtreeRasterization]; - ASDisplayNode *subnode = [[ASDisplayNode alloc] init]; + ASDisplayNode *subnode = [[ASTestDisplayNode alloc] init]; ASSetDebugNames(supernode, subnode); UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; [supernode addSubnode:subnode]; @@ -2076,9 +2082,9 @@ static bool stringContainsPointer(NSString *description, id p) { // Underlying issue for: https://github.com/facebook/AsyncDisplayKit/issues/2205 - (void)testThatRasterizedNodesGetInterfaceStateUpdatesWhenAddedToContainerThatIsInHierarchy { - ASDisplayNode *supernode = [[ASDisplayNode alloc] init]; + ASDisplayNode *supernode = [[ASTestDisplayNode alloc] init]; [supernode enableSubtreeRasterization]; - ASDisplayNode *subnode = [[ASDisplayNode alloc] init]; + ASDisplayNode *subnode = [[ASTestDisplayNode alloc] init]; ASSetDebugNames(supernode, subnode); UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; @@ -2192,8 +2198,7 @@ static bool stringContainsPointer(NSString *description, id p) { [node view]; // Node needs to be loaded [node enterInterfaceState:ASInterfaceStatePreload]; - - + XCTAssertTrue((node.interfaceState & ASInterfaceStatePreload) == ASInterfaceStatePreload); XCTAssertTrue((subnode.interfaceState & ASInterfaceStatePreload) == ASInterfaceStatePreload); XCTAssertTrue(node.hasPreloaded); diff --git a/Tests/ASDisplayNodeTestsHelper.h b/Tests/ASDisplayNodeTestsHelper.h index 4473500048..98b4719c67 100644 --- a/Tests/ASDisplayNodeTestsHelper.h +++ b/Tests/ASDisplayNodeTestsHelper.h @@ -28,5 +28,6 @@ BOOL ASDisplayNodeRunRunLoopUntilBlockIsTrue(as_condition_block_t block); void ASDisplayNodeSizeToFitSize(ASDisplayNode *node, CGSize size); void ASDisplayNodeSizeToFitSizeRange(ASDisplayNode *node, ASSizeRange sizeRange); +void ASCATransactionQueueWait(void); ASDISPLAYNODE_EXTERN_C_END diff --git a/Tests/ASDisplayNodeTestsHelper.m b/Tests/ASDisplayNodeTestsHelper.m index 05397b83b1..0a37da1e40 100644 --- a/Tests/ASDisplayNodeTestsHelper.m +++ b/Tests/ASDisplayNodeTestsHelper.m @@ -18,6 +18,7 @@ #import "ASDisplayNodeTestsHelper.h" #import #import +#import #import @@ -62,3 +63,14 @@ void ASDisplayNodeSizeToFitSizeRange(ASDisplayNode *node, ASSizeRange sizeRange) CGSize sizeThatFits = [node layoutThatFits:sizeRange].size; node.bounds = (CGRect){.origin = CGPointZero, .size = sizeThatFits}; } + +void ASCATransactionQueueWait(void) +{ + NSDate *date = [NSDate dateWithTimeIntervalSinceNow:1]; + BOOL whileResult = YES; + while ([date timeIntervalSinceNow] > 0 && + (whileResult = ![[ASCATransactionQueue sharedQueue] isEmpty])) { + [[NSRunLoop currentRunLoop] runUntilDate: + [NSDate dateWithTimeIntervalSinceNow:0.01]]; + } +} diff --git a/Tests/ASRunLoopQueueTests.m b/Tests/ASRunLoopQueueTests.m index ccf15ae7b7..2fc9c04b56 100644 --- a/Tests/ASRunLoopQueueTests.m +++ b/Tests/ASRunLoopQueueTests.m @@ -2,8 +2,8 @@ // ASRunLoopQueueTests.m // Texture // -// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. -// Licensed under the Apache License, Version 2.0 (the "License"); +// Copyright (c) 2017-present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // @@ -12,9 +12,21 @@ #import #import +#import "ASDisplayNodeTestsHelper.h" static NSTimeInterval const kRunLoopRunTime = 0.001; // Allow the RunLoop to run for one millisecond each time. +@interface QueueObject : NSObject +@property (nonatomic, assign) BOOL queueObjectProcessed; +@end + +@implementation QueueObject +- (void)prepareForCATransactionCommit +{ + self.queueObjectProcessed = YES; +} +@end + @interface ASRunLoopQueueTests : XCTestCase @end @@ -157,4 +169,24 @@ static NSTimeInterval const kRunLoopRunTime = 0.001; // Allow the RunLoop to run XCTAssertTrue(queue.isEmpty); } +- (void)testASCATransactionQueueDisable +{ + ASCATransactionQueue *queue = [[ASCATransactionQueue alloc] init]; + [queue disable]; + QueueObject *object = [[QueueObject alloc] init]; + [[ASCATransactionQueue sharedQueue] enqueue:object]; + XCTAssertTrue([queue isEmpty]); + XCTAssertTrue([queue disabled]); +} + +- (void)testASCATransactionQueueProcess +{ + ASCATransactionQueue *queue = [[ASCATransactionQueue alloc] init]; + QueueObject *object = [[QueueObject alloc] init]; + [queue enqueue:object]; + XCTAssertFalse(object.queueObjectProcessed); + ASCATransactionQueueWait(); + XCTAssertTrue(object.queueObjectProcessed); +} + @end diff --git a/Tests/ASVideoNodeTests.m b/Tests/ASVideoNodeTests.m index 278509efe0..96892b176e 100644 --- a/Tests/ASVideoNodeTests.m +++ b/Tests/ASVideoNodeTests.m @@ -21,6 +21,7 @@ #import #import #import +#import "ASDisplayNodeTestsHelper.h" @interface ASVideoNodeTests : XCTestCase { @@ -351,9 +352,9 @@ [_videoNode setInterfaceState:ASInterfaceStateVisible | ASInterfaceStateDisplay | ASInterfaceStatePreload]; [_videoNode prepareToPlayAsset:assetMock withKeys:_requestedKeys]; + ASCATransactionQueueWait(); [_videoNode pause]; _videoNode.shouldBePlaying = YES; - XCTAssertFalse(_videoNode.isPlaying); [_videoNode observeValueForKeyPath:@"playbackLikelyToKeepUp" ofObject:[_videoNode currentItem] change:@{NSKeyValueChangeNewKey : @YES} context:NULL];