Fix merge conflicts

This commit is contained in:
Aaron Schubert 2016-04-21 09:26:40 +01:00
commit b586b9f34b
37 changed files with 454 additions and 369 deletions

View File

@ -251,9 +251,7 @@
509E68661B3AEDD7009B9150 /* CGRect+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E201B376416007741D0 /* CGRect+ASConvenience.m */; }; 509E68661B3AEDD7009B9150 /* CGRect+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E201B376416007741D0 /* CGRect+ASConvenience.m */; };
636EA1A41C7FF4EC00EE152F /* NSArray+Diffing.m in Sources */ = {isa = PBXBuildFile; fileRef = DBC452DA1C5BF64600B16017 /* NSArray+Diffing.m */; }; 636EA1A41C7FF4EC00EE152F /* NSArray+Diffing.m in Sources */ = {isa = PBXBuildFile; fileRef = DBC452DA1C5BF64600B16017 /* NSArray+Diffing.m */; };
636EA1A51C7FF4EF00EE152F /* ASDefaultPlayButton.m in Sources */ = {isa = PBXBuildFile; fileRef = AEB7B0191C5962EA00662EF4 /* ASDefaultPlayButton.m */; }; 636EA1A51C7FF4EF00EE152F /* ASDefaultPlayButton.m in Sources */ = {isa = PBXBuildFile; fileRef = AEB7B0191C5962EA00662EF4 /* ASDefaultPlayButton.m */; };
68355B301CB5799E001D4E68 /* ASImageNode+AnimatedImage.h in Headers */ = {isa = PBXBuildFile; fileRef = 68355B2D1CB5799E001D4E68 /* ASImageNode+AnimatedImage.h */; settings = {ATTRIBUTES = (Public, ); }; };
68355B311CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm in Sources */ = {isa = PBXBuildFile; fileRef = 68355B2E1CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm */; }; 68355B311CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm in Sources */ = {isa = PBXBuildFile; fileRef = 68355B2E1CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm */; };
68355B331CB579AD001D4E68 /* ASImageNode+AnimatedImage.h in Headers */ = {isa = PBXBuildFile; fileRef = 68355B2D1CB5799E001D4E68 /* ASImageNode+AnimatedImage.h */; settings = {ATTRIBUTES = (Public, ); }; };
68355B341CB579B9001D4E68 /* ASImageNode+AnimatedImage.mm in Sources */ = {isa = PBXBuildFile; fileRef = 68355B2E1CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm */; }; 68355B341CB579B9001D4E68 /* ASImageNode+AnimatedImage.mm in Sources */ = {isa = PBXBuildFile; fileRef = 68355B2E1CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm */; };
68355B3A1CB57A5A001D4E68 /* ASPINRemoteImageDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 68355B361CB57A5A001D4E68 /* ASPINRemoteImageDownloader.m */; }; 68355B3A1CB57A5A001D4E68 /* ASPINRemoteImageDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 68355B361CB57A5A001D4E68 /* ASPINRemoteImageDownloader.m */; };
68355B3B1CB57A5A001D4E68 /* ASImageContainerProtocolCategories.h in Headers */ = {isa = PBXBuildFile; fileRef = 68355B371CB57A5A001D4E68 /* ASImageContainerProtocolCategories.h */; settings = {ATTRIBUTES = (Public, ); }; }; 68355B3B1CB57A5A001D4E68 /* ASImageContainerProtocolCategories.h in Headers */ = {isa = PBXBuildFile; fileRef = 68355B371CB57A5A001D4E68 /* ASImageContainerProtocolCategories.h */; settings = {ATTRIBUTES = (Public, ); }; };
@ -767,7 +765,6 @@
4640521B1A3F83C40061C0BA /* ASFlowLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASFlowLayoutController.h; sourceTree = "<group>"; }; 4640521B1A3F83C40061C0BA /* ASFlowLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASFlowLayoutController.h; sourceTree = "<group>"; };
4640521C1A3F83C40061C0BA /* ASFlowLayoutController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASFlowLayoutController.mm; sourceTree = "<group>"; }; 4640521C1A3F83C40061C0BA /* ASFlowLayoutController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASFlowLayoutController.mm; sourceTree = "<group>"; };
4640521D1A3F83C40061C0BA /* ASLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutController.h; sourceTree = "<group>"; }; 4640521D1A3F83C40061C0BA /* ASLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutController.h; sourceTree = "<group>"; };
68355B2D1CB5799E001D4E68 /* ASImageNode+AnimatedImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASImageNode+AnimatedImage.h"; sourceTree = "<group>"; };
68355B2E1CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "ASImageNode+AnimatedImage.mm"; sourceTree = "<group>"; }; 68355B2E1CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "ASImageNode+AnimatedImage.mm"; sourceTree = "<group>"; };
68355B361CB57A5A001D4E68 /* ASPINRemoteImageDownloader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPINRemoteImageDownloader.m; sourceTree = "<group>"; }; 68355B361CB57A5A001D4E68 /* ASPINRemoteImageDownloader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPINRemoteImageDownloader.m; sourceTree = "<group>"; };
68355B371CB57A5A001D4E68 /* ASImageContainerProtocolCategories.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASImageContainerProtocolCategories.h; sourceTree = "<group>"; }; 68355B371CB57A5A001D4E68 /* ASImageContainerProtocolCategories.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASImageContainerProtocolCategories.h; sourceTree = "<group>"; };
@ -1060,7 +1057,6 @@
0587F9BC1A7309ED00AFF0BA /* ASEditableTextNode.mm */, 0587F9BC1A7309ED00AFF0BA /* ASEditableTextNode.mm */,
058D09DD195D050800B7D73C /* ASImageNode.h */, 058D09DD195D050800B7D73C /* ASImageNode.h */,
058D09DE195D050800B7D73C /* ASImageNode.mm */, 058D09DE195D050800B7D73C /* ASImageNode.mm */,
68355B2D1CB5799E001D4E68 /* ASImageNode+AnimatedImage.h */,
68355B2E1CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm */, 68355B2E1CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm */,
0516FA3E1A1563D200B4EBED /* ASMultiplexImageNode.h */, 0516FA3E1A1563D200B4EBED /* ASMultiplexImageNode.h */,
0516FA3F1A1563D200B4EBED /* ASMultiplexImageNode.mm */, 0516FA3F1A1563D200B4EBED /* ASMultiplexImageNode.mm */,
@ -1561,7 +1557,6 @@
257754C31BEE458E00737CA5 /* ASTextNodeTypes.h in Headers */, 257754C31BEE458E00737CA5 /* ASTextNodeTypes.h in Headers */,
9C49C36F1B853957000B0DD5 /* ASStackLayoutable.h in Headers */, 9C49C36F1B853957000B0DD5 /* ASStackLayoutable.h in Headers */,
69E1006D1CA89CB600D88C1B /* ASEnvironmentInternal.h in Headers */, 69E1006D1CA89CB600D88C1B /* ASEnvironmentInternal.h in Headers */,
68355B301CB5799E001D4E68 /* ASImageNode+AnimatedImage.h in Headers */,
AC21EC101B3D0BF600C8B19A /* ASStackLayoutDefines.h in Headers */, AC21EC101B3D0BF600C8B19A /* ASStackLayoutDefines.h in Headers */,
CC7FD9DE1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */, CC7FD9DE1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */,
ACF6ED2F1B17843500DA7C62 /* ASStackLayoutSpec.h in Headers */, ACF6ED2F1B17843500DA7C62 /* ASStackLayoutSpec.h in Headers */,
@ -1677,7 +1672,6 @@
34EFC75F1B701C8600AD841F /* ASInsetLayoutSpec.h in Headers */, 34EFC75F1B701C8600AD841F /* ASInsetLayoutSpec.h in Headers */,
34EFC75D1B701BE900AD841F /* ASInternalHelpers.h in Headers */, 34EFC75D1B701BE900AD841F /* ASInternalHelpers.h in Headers */,
34EFC7671B701CD900AD841F /* ASLayout.h in Headers */, 34EFC7671B701CD900AD841F /* ASLayout.h in Headers */,
68355B331CB579AD001D4E68 /* ASImageNode+AnimatedImage.h in Headers */,
DEC146B71C37A16A004A0EE7 /* ASCollectionInternal.h in Headers */, DEC146B71C37A16A004A0EE7 /* ASCollectionInternal.h in Headers */,
DBDB83951C6E879900D0098C /* ASPagerFlowLayout.h in Headers */, DBDB83951C6E879900D0098C /* ASPagerFlowLayout.h in Headers */,
34EFC7691B701CE100AD841F /* ASLayoutable.h in Headers */, 34EFC7691B701CE100AD841F /* ASLayoutable.h in Headers */,

View File

@ -106,9 +106,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
NSMutableArray *_batchUpdateBlocks; NSMutableArray *_batchUpdateBlocks;
BOOL _asyncDataFetchingEnabled; BOOL _asyncDataFetchingEnabled;
BOOL _asyncDelegateImplementsScrollviewDidScroll;
BOOL _asyncDataSourceImplementsConstrainedSizeForNode;
BOOL _asyncDataSourceImplementsNodeBlockForItemAtIndexPath;
_ASCollectionViewNodeSizeInvalidationContext *_queuedNodeSizeInvalidationContext; // Main thread only _ASCollectionViewNodeSizeInvalidationContext *_queuedNodeSizeInvalidationContext; // Main thread only
BOOL _isDeallocating; BOOL _isDeallocating;
@ -133,6 +130,26 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
* The collection view never queried your data source before the update to see that it actually had 0 items. * The collection view never queried your data source before the update to see that it actually had 0 items.
*/ */
BOOL _superIsPendingDataLoad; BOOL _superIsPendingDataLoad;
struct {
unsigned int asyncDelegateScrollViewDidScroll:1;
unsigned int asyncDelegateScrollViewWillEndDraggingWithVelocityTargetContentOffset:1;
unsigned int asyncDelegateCollectionViewWillDisplayNodeForItemAtIndexPath:1;
unsigned int asyncDelegateCollectionViewDidEndDisplayingNodeForItemAtIndexPath:1;
unsigned int asyncDelegateCollectionViewDidEndDisplayingNodeForItemAtIndexPathDeprecated:1;
unsigned int asyncDelegateCollectionViewWillBeginBatchFetchWithContext:1;
unsigned int asyncDelegateShouldBatchFetchForCollectionView:1;
} _asyncDelegateFlags;
struct {
unsigned int asyncDataSourceConstrainedSizeForNode:1;
unsigned int asyncDataSourceNodeForItemAtIndexPath:1;
unsigned int asyncDataSourceNodeBlockForItemAtIndexPath:1;
unsigned int asyncDataSourceNumberOfSectionsInCollectionView:1;
unsigned int asyncDataSourceCollectionViewLockDataSource:1;
unsigned int asyncDataSourceCollectionViewUnlockDataSource:1;
unsigned int asyncDataSourceCollectionViewConstrainedSizeForNodeAtIndexPath:1;
} _asyncDataSourceFlags;
} }
@property (atomic, assign) BOOL asyncDataSourceLocked; @property (atomic, assign) BOOL asyncDataSourceLocked;
@ -333,16 +350,22 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
if (asyncDataSource == nil) { if (asyncDataSource == nil) {
_asyncDataSource = nil; _asyncDataSource = nil;
_proxyDataSource = _isDeallocating ? nil : [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self]; _proxyDataSource = _isDeallocating ? nil : [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self];
_asyncDataSourceImplementsConstrainedSizeForNode = NO;
_asyncDataSourceImplementsNodeBlockForItemAtIndexPath = NO; memset(&_asyncDataSourceFlags, 0, sizeof(_asyncDataSourceFlags));
} else { } else {
_asyncDataSource = asyncDataSource; _asyncDataSource = asyncDataSource;
_proxyDataSource = [[ASCollectionViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self]; _proxyDataSource = [[ASCollectionViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self];
_asyncDataSourceImplementsConstrainedSizeForNode = [_asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)];
_asyncDataSourceImplementsNodeBlockForItemAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeBlockForItemAtIndexPath:)]; _asyncDataSourceFlags.asyncDataSourceConstrainedSizeForNode = [_asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)];
_asyncDataSourceFlags.asyncDataSourceNodeForItemAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeForItemAtIndexPath:)];
_asyncDataSourceFlags.asyncDataSourceNodeBlockForItemAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeBlockForItemAtIndexPath:)];
_asyncDataSourceFlags.asyncDataSourceNumberOfSectionsInCollectionView = [_asyncDataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)];
_asyncDataSourceFlags.asyncDataSourceCollectionViewLockDataSource = [_asyncDataSource respondsToSelector:@selector(collectionViewLockDataSource:)];
_asyncDataSourceFlags.asyncDataSourceCollectionViewUnlockDataSource = [_asyncDataSource respondsToSelector:@selector(collectionViewUnlockDataSource:)];
_asyncDataSourceFlags.asyncDataSourceCollectionViewConstrainedSizeForNodeAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)];;
// Data-source must implement collectionView:nodeForItemAtIndexPath: or collectionView:nodeBlockForItemAtIndexPath: // Data-source must implement collectionView:nodeForItemAtIndexPath: or collectionView:nodeBlockForItemAtIndexPath:
ASDisplayNodeAssertTrue(_asyncDataSourceImplementsNodeBlockForItemAtIndexPath || [_asyncDataSource respondsToSelector:@selector(collectionView:nodeForItemAtIndexPath:)]); ASDisplayNodeAssertTrue(_asyncDataSourceFlags.asyncDataSourceNodeBlockForItemAtIndexPath || _asyncDataSourceFlags.asyncDataSourceNodeForItemAtIndexPath);
} }
super.dataSource = (id<UICollectionViewDataSource>)_proxyDataSource; super.dataSource = (id<UICollectionViewDataSource>)_proxyDataSource;
@ -363,11 +386,19 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
if (asyncDelegate == nil) { if (asyncDelegate == nil) {
_asyncDelegate = nil; _asyncDelegate = nil;
_proxyDelegate = _isDeallocating ? nil : [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self]; _proxyDelegate = _isDeallocating ? nil : [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self];
_asyncDelegateImplementsScrollviewDidScroll = NO;
memset(&_asyncDelegateFlags, 0, sizeof(_asyncDelegateFlags));
} else { } else {
_asyncDelegate = asyncDelegate; _asyncDelegate = asyncDelegate;
_proxyDelegate = [[ASCollectionViewProxy alloc] initWithTarget:_asyncDelegate interceptor:self]; _proxyDelegate = [[ASCollectionViewProxy alloc] initWithTarget:_asyncDelegate interceptor:self];
_asyncDelegateImplementsScrollviewDidScroll = ([_asyncDelegate respondsToSelector:@selector(scrollViewDidScroll:)] ? 1 : 0);
_asyncDelegateFlags.asyncDelegateScrollViewDidScroll = [_asyncDelegate respondsToSelector:@selector(scrollViewDidScroll:)];
_asyncDelegateFlags.asyncDelegateScrollViewWillEndDraggingWithVelocityTargetContentOffset = [_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)];
_asyncDelegateFlags.asyncDelegateCollectionViewWillDisplayNodeForItemAtIndexPath = [_asyncDelegate respondsToSelector:@selector(collectionView:willDisplayNodeForItemAtIndexPath:)];
_asyncDelegateFlags.asyncDelegateCollectionViewDidEndDisplayingNodeForItemAtIndexPathDeprecated = [_asyncDelegate respondsToSelector:@selector(collectionView:didEndDisplayingNodeForItemAtIndexPath:)];
_asyncDelegateFlags.asyncDelegateCollectionViewDidEndDisplayingNodeForItemAtIndexPath = [_asyncDelegate respondsToSelector:@selector(collectionView:didEndDisplayingNode:forItemAtIndexPath:)];
_asyncDelegateFlags.asyncDelegateCollectionViewWillBeginBatchFetchWithContext = [_asyncDelegate respondsToSelector:@selector(collectionView:willBeginBatchFetchWithContext:)];
_asyncDelegateFlags.asyncDelegateShouldBatchFetchForCollectionView = [_asyncDelegate respondsToSelector:@selector(shouldBatchFetchForCollectionView:)];
} }
super.delegate = (id<UICollectionViewDelegate>)_proxyDelegate; super.delegate = (id<UICollectionViewDelegate>)_proxyDelegate;
@ -566,7 +597,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
ASCellNode *cellNode = [cell node]; ASCellNode *cellNode = [cell node];
cellNode.scrollView = collectionView; cellNode.scrollView = collectionView;
if ([_asyncDelegate respondsToSelector:@selector(collectionView:willDisplayNodeForItemAtIndexPath:)]) { if (_asyncDelegateFlags.asyncDelegateCollectionViewWillDisplayNodeForItemAtIndexPath) {
[_asyncDelegate collectionView:self willDisplayNodeForItemAtIndexPath:indexPath]; [_asyncDelegate collectionView:self willDisplayNodeForItemAtIndexPath:indexPath];
} }
@ -586,7 +617,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
ASCellNode *cellNode = [cell node]; ASCellNode *cellNode = [cell node];
if ([_asyncDelegate respondsToSelector:@selector(collectionView:didEndDisplayingNode:forItemAtIndexPath:)]) { if (_asyncDelegateFlags.asyncDelegateCollectionViewDidEndDisplayingNodeForItemAtIndexPath) {
ASDisplayNodeAssertNotNil(cellNode, @"Expected node associated with removed cell not to be nil."); ASDisplayNodeAssertNotNil(cellNode, @"Expected node associated with removed cell not to be nil.");
[_asyncDelegate collectionView:self didEndDisplayingNode:cellNode forItemAtIndexPath:indexPath]; [_asyncDelegate collectionView:self didEndDisplayingNode:cellNode forItemAtIndexPath:indexPath];
} }
@ -597,7 +628,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
#pragma clang diagnostic push #pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations" #pragma clang diagnostic ignored "-Wdeprecated-declarations"
if ([_asyncDelegate respondsToSelector:@selector(collectionView:didEndDisplayingNodeForItemAtIndexPath:)]) { if (_asyncDelegateFlags.asyncDelegateCollectionViewDidEndDisplayingNodeForItemAtIndexPathDeprecated) {
[_asyncDelegate collectionView:self didEndDisplayingNodeForItemAtIndexPath:indexPath]; [_asyncDelegate collectionView:self didEndDisplayingNodeForItemAtIndexPath:indexPath];
} }
#pragma clang diagnostic pop #pragma clang diagnostic pop
@ -620,7 +651,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
inScrollView:scrollView inScrollView:scrollView
withCellFrame:collectionCell.frame]; withCellFrame:collectionCell.frame];
} }
if (_asyncDelegateImplementsScrollviewDidScroll) { if (_asyncDelegateFlags.asyncDelegateScrollViewDidScroll) {
[_asyncDelegate scrollViewDidScroll:scrollView]; [_asyncDelegate scrollViewDidScroll:scrollView];
} }
} }
@ -637,7 +668,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
[self _beginBatchFetchingIfNeededWithScrollView:self forScrollDirection:[self scrollDirection] contentOffset:*targetContentOffset]; [self _beginBatchFetchingIfNeededWithScrollView:self forScrollDirection:[self scrollDirection] contentOffset:*targetContentOffset];
} }
if ([_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) { if (_asyncDelegateFlags.asyncDelegateScrollViewWillEndDraggingWithVelocityTargetContentOffset) {
[_asyncDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset]; [_asyncDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset];
} }
} }
@ -747,8 +778,8 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (BOOL)canBatchFetch - (BOOL)canBatchFetch
{ {
// if the delegate does not respond to this method, there is no point in starting to fetch // if the delegate does not respond to this method, there is no point in starting to fetch
BOOL canFetch = [_asyncDelegate respondsToSelector:@selector(collectionView:willBeginBatchFetchWithContext:)]; BOOL canFetch = _asyncDelegateFlags.asyncDelegateCollectionViewWillBeginBatchFetchWithContext;
if (canFetch && [_asyncDelegate respondsToSelector:@selector(shouldBatchFetchForCollectionView:)]) { if (canFetch && _asyncDelegateFlags.asyncDelegateShouldBatchFetchForCollectionView) {
return [_asyncDelegate shouldBatchFetchForCollectionView:self]; return [_asyncDelegate shouldBatchFetchForCollectionView:self];
} else { } else {
return canFetch; return canFetch;
@ -788,7 +819,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (void)_beginBatchFetching - (void)_beginBatchFetching
{ {
[_batchContext beginBatchFetching]; [_batchContext beginBatchFetching];
if ([_asyncDelegate respondsToSelector:@selector(collectionView:willBeginBatchFetchWithContext:)]) { if (_asyncDelegateFlags.asyncDelegateCollectionViewWillBeginBatchFetchWithContext) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[_asyncDelegate collectionView:self willBeginBatchFetchWithContext:_batchContext]; [_asyncDelegate collectionView:self willBeginBatchFetchWithContext:_batchContext];
}); });
@ -800,7 +831,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath - (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath
{ {
if (!_asyncDataSourceImplementsNodeBlockForItemAtIndexPath) { if (!_asyncDataSourceFlags.asyncDataSourceNodeBlockForItemAtIndexPath) {
ASCellNode *node = [_asyncDataSource collectionView:self nodeForItemAtIndexPath:indexPath]; ASCellNode *node = [_asyncDataSource collectionView:self nodeForItemAtIndexPath:indexPath];
ASDisplayNodeAssert([node isKindOfClass:ASCellNode.class], @"invalid node class, expected ASCellNode"); ASDisplayNodeAssert([node isKindOfClass:ASCellNode.class], @"invalid node class, expected ASCellNode");
__weak __typeof__(self) weakSelf = self; __weak __typeof__(self) weakSelf = self;
@ -842,7 +873,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
// TODO: Move this logic into the flow layout inspector. Create a simple inspector for non-flow layouts that don't // TODO: Move this logic into the flow layout inspector. Create a simple inspector for non-flow layouts that don't
// implement a custom inspector. // implement a custom inspector.
if (_asyncDataSourceImplementsConstrainedSizeForNode) { if (_asyncDataSourceFlags.asyncDataSourceConstrainedSizeForNode) {
constrainedSize = [_asyncDataSource collectionView:self constrainedSizeForNodeAtIndexPath:indexPath]; constrainedSize = [_asyncDataSource collectionView:self constrainedSizeForNodeAtIndexPath:indexPath];
} else { } else {
CGSize maxSize = CGSizeEqualToSize(_maxSizeForNodesConstrainedSize, CGSizeZero) ? self.bounds.size : _maxSizeForNodesConstrainedSize; CGSize maxSize = CGSizeEqualToSize(_maxSizeForNodesConstrainedSize, CGSizeZero) ? self.bounds.size : _maxSizeForNodesConstrainedSize;
@ -863,7 +894,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
} }
- (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataController { - (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataController {
if ([_asyncDataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)]) { if (_asyncDataSourceFlags.asyncDataSourceNumberOfSectionsInCollectionView) {
return [_asyncDataSource numberOfSectionsInCollectionView:self]; return [_asyncDataSource numberOfSectionsInCollectionView:self];
} else { } else {
return 1; return 1;
@ -875,7 +906,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
ASDisplayNodeAssert(!self.asyncDataSourceLocked, @"The data source has already been locked"); ASDisplayNodeAssert(!self.asyncDataSourceLocked, @"The data source has already been locked");
self.asyncDataSourceLocked = YES; self.asyncDataSourceLocked = YES;
if ([_asyncDataSource respondsToSelector:@selector(collectionViewLockDataSource:)]) { if (_asyncDataSourceFlags.asyncDataSourceCollectionViewLockDataSource) {
[_asyncDataSource collectionViewLockDataSource:self]; [_asyncDataSource collectionViewLockDataSource:self];
} }
} }
@ -885,7 +916,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
ASDisplayNodeAssert(self.asyncDataSourceLocked, @"The data source has already been unlocked"); ASDisplayNodeAssert(self.asyncDataSourceLocked, @"The data source has already been unlocked");
self.asyncDataSourceLocked = NO; self.asyncDataSourceLocked = NO;
if ([_asyncDataSource respondsToSelector:@selector(collectionViewUnlockDataSource:)]) { if (_asyncDataSourceFlags.asyncDataSourceCollectionViewUnlockDataSource) {
[_asyncDataSource collectionViewUnlockDataSource:self]; [_asyncDataSource collectionViewUnlockDataSource:self];
} }
} }
@ -1002,13 +1033,12 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
} }
[_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:_performingBatchUpdates];
if (_performingBatchUpdates) { if (_performingBatchUpdates) {
[_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:YES];
[_batchUpdateBlocks addObject:^{ [_batchUpdateBlocks addObject:^{
[super insertItemsAtIndexPaths:indexPaths]; [super insertItemsAtIndexPaths:indexPaths];
}]; }];
} else { } else {
[_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:NO];
[UIView performWithoutAnimation:^{ [UIView performWithoutAnimation:^{
[super insertItemsAtIndexPaths:indexPaths]; [super insertItemsAtIndexPaths:indexPaths];
[self _scheduleCheckForBatchFetchingForNumberOfChanges:indexPaths.count]; [self _scheduleCheckForBatchFetchingForNumberOfChanges:indexPaths.count];
@ -1023,13 +1053,12 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
} }
[_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:_performingBatchUpdates];
if (_performingBatchUpdates) { if (_performingBatchUpdates) {
[_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:YES];
[_batchUpdateBlocks addObject:^{ [_batchUpdateBlocks addObject:^{
[super deleteItemsAtIndexPaths:indexPaths]; [super deleteItemsAtIndexPaths:indexPaths];
}]; }];
} else { } else {
[_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:NO];
[UIView performWithoutAnimation:^{ [UIView performWithoutAnimation:^{
[super deleteItemsAtIndexPaths:indexPaths]; [super deleteItemsAtIndexPaths:indexPaths];
[self _scheduleCheckForBatchFetchingForNumberOfChanges:indexPaths.count]; [self _scheduleCheckForBatchFetchingForNumberOfChanges:indexPaths.count];
@ -1044,13 +1073,12 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
} }
[_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:_performingBatchUpdates];
if (_performingBatchUpdates) { if (_performingBatchUpdates) {
[_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:YES];
[_batchUpdateBlocks addObject:^{ [_batchUpdateBlocks addObject:^{
[super insertSections:indexSet]; [super insertSections:indexSet];
}]; }];
} else { } else {
[_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:NO];
[UIView performWithoutAnimation:^{ [UIView performWithoutAnimation:^{
[super insertSections:indexSet]; [super insertSections:indexSet];
[self _scheduleCheckForBatchFetchingForNumberOfChanges:indexSet.count]; [self _scheduleCheckForBatchFetchingForNumberOfChanges:indexSet.count];
@ -1065,13 +1093,12 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
} }
[_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:_performingBatchUpdates];
if (_performingBatchUpdates) { if (_performingBatchUpdates) {
[_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:YES];
[_batchUpdateBlocks addObject:^{ [_batchUpdateBlocks addObject:^{
[super deleteSections:indexSet]; [super deleteSections:indexSet];
}]; }];
} else { } else {
[_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:NO];
[UIView performWithoutAnimation:^{ [UIView performWithoutAnimation:^{
[super deleteSections:indexSet]; [super deleteSections:indexSet];
[self _scheduleCheckForBatchFetchingForNumberOfChanges:indexSet.count]; [self _scheduleCheckForBatchFetchingForNumberOfChanges:indexSet.count];

View File

@ -83,6 +83,13 @@ BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector)
return ASSubclassOverridesSelector([ASDisplayNode class], subclass, selector); return ASSubclassOverridesSelector([ASDisplayNode class], subclass, selector);
} }
// For classes like ASTableNode, ASCollectionNode, ASScrollNode and similar - we have to be sure to set certain properties
// like setFrame: and setBackgroundColor: directly to the UIView and not apply it to the layer only.
BOOL ASDisplayNodeNeedsSpecialPropertiesHandlingForFlags(ASDisplayNodeFlags flags)
{
return flags.synchronous && !flags.layerBacked;
}
_ASPendingState *ASDisplayNodeGetPendingState(ASDisplayNode *node) _ASPendingState *ASDisplayNodeGetPendingState(ASDisplayNode *node)
{ {
ASDN::MutexLocker l(node->_propertyLock); ASDN::MutexLocker l(node->_propertyLock);
@ -954,8 +961,8 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
if (self.layerBacked) { if (self.layerBacked) {
[_pendingViewState applyToLayer:self.layer]; [_pendingViewState applyToLayer:self.layer];
} else { } else {
BOOL setFrameDirectly = (_flags.synchronous && !_flags.layerBacked); BOOL specialPropertiesHandling = ASDisplayNodeNeedsSpecialPropertiesHandlingForFlags(_flags);
[_pendingViewState applyToView:self.view setFrameDirectly:setFrameDirectly]; [_pendingViewState applyToView:self.view withSpecialPropertiesHandling:specialPropertiesHandling];
} }
[_pendingViewState clearChanges]; [_pendingViewState clearChanges];

View File

@ -1,15 +0,0 @@
//
// ASImageNode+AnimatedImage.h
// AsyncDisplayKit
//
// Created by Garrett Moon on 3/22/16.
// Copyright © 2016 Facebook. All rights reserved.
//
#import "ASImageNode.h"
#import "ASImageProtocols.h"
@interface ASImageNode (AnimatedImage)
@property (atomic, assign) BOOL animatedImagePaused;
@property (nullable, atomic, strong) id <ASAnimatedImageProtocol> animatedImage;
@end

View File

@ -6,7 +6,7 @@
// Copyright © 2016 Facebook. All rights reserved. // Copyright © 2016 Facebook. All rights reserved.
// //
#import "ASImageNode+AnimatedImage.h" #import "ASImageNode.h"
#import "ASAssert.h" #import "ASAssert.h"
#import "ASImageProtocols.h" #import "ASImageProtocols.h"
@ -25,9 +25,12 @@
- (void)setAnimatedImage:(id <ASAnimatedImageProtocol>)animatedImage - (void)setAnimatedImage:(id <ASAnimatedImageProtocol>)animatedImage
{ {
ASDN::MutexLocker l(_animatedImageLock); ASDN::MutexLocker l(_animatedImageLock);
if (!ASObjectIsEqual(_animatedImage, animatedImage)) { if (ASObjectIsEqual(_animatedImage, animatedImage)) {
_animatedImage = animatedImage; return;
} }
_animatedImage = animatedImage;
if (animatedImage != nil) { if (animatedImage != nil) {
__weak ASImageNode *weakSelf = self; __weak ASImageNode *weakSelf = self;
if ([animatedImage respondsToSelector:@selector(setCoverImageReadyCallback:)]) { if ([animatedImage respondsToSelector:@selector(setCoverImageReadyCallback:)]) {
@ -50,7 +53,7 @@
- (void)setAnimatedImagePaused:(BOOL)animatedImagePaused - (void)setAnimatedImagePaused:(BOOL)animatedImagePaused
{ {
ASDN::MutexLocker l(_animatedImagePausedLock); ASDN::MutexLocker l(_animatedImageLock);
_animatedImagePaused = animatedImagePaused; _animatedImagePaused = animatedImagePaused;
ASPerformBlockOnMainThread(^{ ASPerformBlockOnMainThread(^{
if (animatedImagePaused) { if (animatedImagePaused) {
@ -63,7 +66,7 @@
- (BOOL)animatedImagePaused - (BOOL)animatedImagePaused
{ {
ASDN::MutexLocker l(_animatedImagePausedLock); ASDN::MutexLocker l(_animatedImageLock);
return _animatedImagePaused; return _animatedImagePaused;
} }
@ -84,7 +87,7 @@
- (void)animatedImageFileReady - (void)animatedImageFileReady
{ {
dispatch_async(dispatch_get_main_queue(), ^{ ASPerformBlockOnMainThread(^{
[self startAnimating]; [self startAnimating];
}); });
} }
@ -147,18 +150,6 @@
} }
} }
- (void)__enterHierarchy
{
[super __enterHierarchy];
[self startAnimating];
}
- (void)__exitHierarchy
{
[super __exitHierarchy];
[self stopAnimating];
}
- (void)displayLinkFired:(CADisplayLink *)displayLink - (void)displayLinkFired:(CADisplayLink *)displayLink
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();

View File

@ -8,6 +8,8 @@
#import <AsyncDisplayKit/ASControlNode.h> #import <AsyncDisplayKit/ASControlNode.h>
#import "ASImageProtocols.h"
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
/** /**
@ -119,6 +121,22 @@ typedef UIImage * _Nullable (^asimagenode_modification_block_t)(UIImage *image);
@property (nonatomic, assign) BOOL isDefaultFocusAppearance; @property (nonatomic, assign) BOOL isDefaultFocusAppearance;
#endif #endif
/**
* @abstract The animated image to playback
*
* @discussion Set this to an object which conforms to ASAnimatedImageProtocol
* to have the ASImageNode playback an animated image.
*/
@property (nullable, atomic, strong) id <ASAnimatedImageProtocol> animatedImage;
/**
* @abstract Pause the playback of an animated image.
*
* @discussion Set to YES to pause playback of an animated image and NO to resume
* playback.
*/
@property (atomic, assign) BOOL animatedImagePaused;
@end @end

View File

@ -16,7 +16,6 @@
#import <AsyncDisplayKit/ASDisplayNodeExtras.h> #import <AsyncDisplayKit/ASDisplayNodeExtras.h>
#import <AsyncDisplayKit/ASDisplayNode+Beta.h> #import <AsyncDisplayKit/ASDisplayNode+Beta.h>
#import <AsyncDisplayKit/ASTextNode.h> #import <AsyncDisplayKit/ASTextNode.h>
#import <AsyncDisplayKit/ASImageNode+AnimatedImage.h>
#import <AsyncDisplayKit/ASImageNode+AnimatedImagePrivate.h> #import <AsyncDisplayKit/ASImageNode+AnimatedImagePrivate.h>
#import "ASImageNode+CGExtras.h" #import "ASImageNode+CGExtras.h"

View File

@ -221,7 +221,6 @@
- (void)setUpSnapshotter - (void)setUpSnapshotter
{ {
ASDisplayNodeAssert(!CGSizeEqualToSize(CGSizeZero, self.calculatedSize), @"self.calculatedSize can not be zero. Make sure that you are setting a preferredFrameSize or wrapping ASMapNode in a ASRatioLayoutSpec or similar.");
_snapshotter = [[MKMapSnapshotter alloc] initWithOptions:self.options]; _snapshotter = [[MKMapSnapshotter alloc] initWithOptions:self.options];
} }

View File

@ -15,7 +15,6 @@
#import "ASThread.h" #import "ASThread.h"
#import "ASInternalHelpers.h" #import "ASInternalHelpers.h"
#import "ASImageContainerProtocolCategories.h" #import "ASImageContainerProtocolCategories.h"
#import "ASImageNode+AnimatedImage.h"
#if PIN_REMOTE_IMAGE #if PIN_REMOTE_IMAGE
#import "ASPINRemoteImageDownloader.h" #import "ASPINRemoteImageDownloader.h"

View File

@ -20,7 +20,7 @@
@end @end
@interface ASTableNode () @interface ASTableNode ()
@property (nonatomic) _ASTablePendingState *pendingState; @property (nonatomic, strong) _ASTablePendingState *pendingState;
@end @end
@interface ASTableView () @interface ASTableView ()
@ -68,7 +68,7 @@
ASTableView *view = self.view; ASTableView *view = self.view;
view.tableNode = self; view.tableNode = self;
if (_pendingState) { if (_pendingState) {
_ASTablePendingState *pendingState = _pendingState; _ASTablePendingState *pendingState = _pendingState;
self.pendingState = nil; self.pendingState = nil;

View File

@ -112,9 +112,25 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
BOOL _ignoreNodesConstrainedWidthChange; BOOL _ignoreNodesConstrainedWidthChange;
BOOL _queuedNodeHeightUpdate; BOOL _queuedNodeHeightUpdate;
BOOL _isDeallocating; BOOL _isDeallocating;
BOOL _dataSourceImplementsNodeBlockForRowAtIndexPath;
BOOL _asyncDelegateImplementsScrollviewDidScroll;
NSMutableSet *_cellsForVisibilityUpdates; NSMutableSet *_cellsForVisibilityUpdates;
struct {
unsigned int asyncDelegateScrollViewDidScroll:1;
unsigned int asyncDelegateTableViewWillDisplayNodeForRowAtIndexPath:1;
unsigned int asyncDelegateTableViewDidEndDisplayingNodeForRowAtIndexPath:1;
unsigned int asyncDelegateTableViewDidEndDisplayingNodeForRowAtIndexPathDeprecated:1;
unsigned int asyncDelegateScrollViewWillEndDraggingWithVelocityTargetContentOffset:1;
unsigned int asyncDelegateTableViewWillBeginBatchFetchWithContext:1;
unsigned int asyncDelegateShouldBatchFetchForTableView:1;
} _asyncDelegateFlags;
struct {
unsigned int asyncDataSourceNumberOfSectionsInTableView:1;
unsigned int asyncDataSourceTableViewNodeBlockForRowAtIndexPath:1;
unsigned int asyncDataSourceTableViewNodeForRowAtIndexPath:1;
unsigned int asyncDataSourceTableViewLockDataSource:1;
unsigned int asyncDataSourceTableViewUnlockDataSource:1;
} _asyncDataSourceFlags;
} }
@property (atomic, assign) BOOL asyncDataSourceLocked; @property (atomic, assign) BOOL asyncDataSourceLocked;
@ -260,13 +276,20 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
if (asyncDataSource == nil) { if (asyncDataSource == nil) {
_asyncDataSource = nil; _asyncDataSource = nil;
_proxyDataSource = _isDeallocating ? nil : [[ASTableViewProxy alloc] initWithTarget:nil interceptor:self]; _proxyDataSource = _isDeallocating ? nil : [[ASTableViewProxy alloc] initWithTarget:nil interceptor:self];
_dataSourceImplementsNodeBlockForRowAtIndexPath = NO;
memset(&_asyncDataSourceFlags, 0, sizeof(_asyncDataSourceFlags));
} else { } else {
_asyncDataSource = asyncDataSource; _asyncDataSource = asyncDataSource;
_dataSourceImplementsNodeBlockForRowAtIndexPath = [_asyncDataSource respondsToSelector:@selector(tableView:nodeBlockForRowAtIndexPath:)];
// Data source must implement tableView:nodeBlockForRowAtIndexPath: or tableView:nodeForRowAtIndexPath:
ASDisplayNodeAssertTrue(_dataSourceImplementsNodeBlockForRowAtIndexPath || [_asyncDataSource respondsToSelector:@selector(tableView:nodeForRowAtIndexPath:)]);
_proxyDataSource = [[ASTableViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self]; _proxyDataSource = [[ASTableViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self];
_asyncDataSourceFlags.asyncDataSourceNumberOfSectionsInTableView = [_asyncDataSource respondsToSelector:@selector(numberOfSectionsInTableView:)];
_asyncDataSourceFlags.asyncDataSourceTableViewNodeForRowAtIndexPath = [_asyncDataSource respondsToSelector:@selector(tableView:nodeForRowAtIndexPath:)];
_asyncDataSourceFlags.asyncDataSourceTableViewNodeBlockForRowAtIndexPath = [_asyncDataSource respondsToSelector:@selector(tableView:nodeBlockForRowAtIndexPath:)];
_asyncDataSourceFlags.asyncDataSourceTableViewLockDataSource = [_asyncDataSource respondsToSelector:@selector(tableViewLockDataSource:)];
_asyncDataSourceFlags.asyncDataSourceTableViewUnlockDataSource = [_asyncDataSource respondsToSelector:@selector(tableViewUnlockDataSource:)];
// Data source must implement tableView:nodeBlockForRowAtIndexPath: or tableView:nodeForRowAtIndexPath:
ASDisplayNodeAssertTrue(_asyncDataSourceFlags.asyncDataSourceTableViewNodeBlockForRowAtIndexPath || _asyncDataSourceFlags.asyncDataSourceTableViewNodeForRowAtIndexPath);
} }
super.dataSource = (id<UITableViewDataSource>)_proxyDataSource; super.dataSource = (id<UITableViewDataSource>)_proxyDataSource;
@ -287,11 +310,19 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
if (asyncDelegate == nil) { if (asyncDelegate == nil) {
_asyncDelegate = nil; _asyncDelegate = nil;
_proxyDelegate = _isDeallocating ? nil : [[ASTableViewProxy alloc] initWithTarget:nil interceptor:self]; _proxyDelegate = _isDeallocating ? nil : [[ASTableViewProxy alloc] initWithTarget:nil interceptor:self];
_asyncDelegateImplementsScrollviewDidScroll = NO;
memset(&_asyncDelegateFlags, 0, sizeof(_asyncDelegateFlags));
} else { } else {
_asyncDelegate = asyncDelegate; _asyncDelegate = asyncDelegate;
_asyncDelegateImplementsScrollviewDidScroll = [_asyncDelegate respondsToSelector:@selector(scrollViewDidScroll:)];
_proxyDelegate = [[ASTableViewProxy alloc] initWithTarget:_asyncDelegate interceptor:self]; _proxyDelegate = [[ASTableViewProxy alloc] initWithTarget:_asyncDelegate interceptor:self];
_asyncDelegateFlags.asyncDelegateScrollViewDidScroll = [_asyncDelegate respondsToSelector:@selector(scrollViewDidScroll:)];
_asyncDelegateFlags.asyncDelegateTableViewWillDisplayNodeForRowAtIndexPath = [_asyncDelegate respondsToSelector:@selector(tableView:willDisplayNodeForRowAtIndexPath:)];
_asyncDelegateFlags.asyncDelegateTableViewDidEndDisplayingNodeForRowAtIndexPath = [_asyncDelegate respondsToSelector:@selector(tableView:didEndDisplayingNode:forRowAtIndexPath:)];
_asyncDelegateFlags.asyncDelegateTableViewDidEndDisplayingNodeForRowAtIndexPathDeprecated = [_asyncDelegate respondsToSelector:@selector(tableView:didEndDisplayingNodeForRowAtIndexPath:)];
_asyncDelegateFlags.asyncDelegateScrollViewWillEndDraggingWithVelocityTargetContentOffset = [_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)];
_asyncDelegateFlags.asyncDelegateTableViewWillBeginBatchFetchWithContext = [_asyncDelegate respondsToSelector:@selector(tableView:willBeginBatchFetchWithContext:)];
_asyncDelegateFlags.asyncDelegateShouldBatchFetchForTableView = [_asyncDelegate respondsToSelector:@selector(shouldBatchFetchForTableView:)];
} }
super.delegate = (id<UITableViewDelegate>)_proxyDelegate; super.delegate = (id<UITableViewDelegate>)_proxyDelegate;
@ -584,7 +615,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
ASCellNode *cellNode = [cell node]; ASCellNode *cellNode = [cell node];
cellNode.scrollView = tableView; cellNode.scrollView = tableView;
if ([_asyncDelegate respondsToSelector:@selector(tableView:willDisplayNodeForRowAtIndexPath:)]) { if (_asyncDelegateFlags.asyncDelegateTableViewWillDisplayNodeForRowAtIndexPath) {
[_asyncDelegate tableView:self willDisplayNodeForRowAtIndexPath:indexPath]; [_asyncDelegate tableView:self willDisplayNodeForRowAtIndexPath:indexPath];
} }
@ -609,7 +640,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
[_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:[self scrollDirection]]; [_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:[self scrollDirection]];
if ([_asyncDelegate respondsToSelector:@selector(tableView:didEndDisplayingNode:forRowAtIndexPath:)]) { if (_asyncDelegateFlags.asyncDelegateTableViewDidEndDisplayingNodeForRowAtIndexPath) {
ASDisplayNodeAssertNotNil(cellNode, @"Expected node associated with removed cell not to be nil."); ASDisplayNodeAssertNotNil(cellNode, @"Expected node associated with removed cell not to be nil.");
[_asyncDelegate tableView:self didEndDisplayingNode:cellNode forRowAtIndexPath:indexPath]; [_asyncDelegate tableView:self didEndDisplayingNode:cellNode forRowAtIndexPath:indexPath];
} }
@ -620,7 +651,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
#pragma clang diagnostic push #pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations" #pragma clang diagnostic ignored "-Wdeprecated-declarations"
if ([_asyncDelegate respondsToSelector:@selector(tableView:didEndDisplayingNodeForRowAtIndexPath:)]) { if (_asyncDelegateFlags.asyncDelegateTableViewDidEndDisplayingNodeForRowAtIndexPathDeprecated) {
[_asyncDelegate tableView:self didEndDisplayingNodeForRowAtIndexPath:indexPath]; [_asyncDelegate tableView:self didEndDisplayingNodeForRowAtIndexPath:indexPath];
} }
#pragma clang diagnostic pop #pragma clang diagnostic pop
@ -642,7 +673,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
inScrollView:scrollView inScrollView:scrollView
withCellFrame:tableCell.frame]; withCellFrame:tableCell.frame];
} }
if (_asyncDelegateImplementsScrollviewDidScroll) { if (_asyncDelegateFlags.asyncDelegateScrollViewDidScroll) {
[_asyncDelegate scrollViewDidScroll:scrollView]; [_asyncDelegate scrollViewDidScroll:scrollView];
} }
} }
@ -659,7 +690,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
[self _beginBatchFetchingIfNeededWithScrollView:self forScrollDirection:[self scrollDirection] contentOffset:*targetContentOffset]; [self _beginBatchFetchingIfNeededWithScrollView:self forScrollDirection:[self scrollDirection] contentOffset:*targetContentOffset];
} }
if ([_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) { if (_asyncDelegateFlags.asyncDelegateScrollViewWillEndDraggingWithVelocityTargetContentOffset) {
[_asyncDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset]; [_asyncDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset];
} }
} }
@ -722,8 +753,8 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
- (BOOL)canBatchFetch - (BOOL)canBatchFetch
{ {
// if the delegate does not respond to this method, there is no point in starting to fetch // if the delegate does not respond to this method, there is no point in starting to fetch
BOOL canFetch = [_asyncDelegate respondsToSelector:@selector(tableView:willBeginBatchFetchWithContext:)]; BOOL canFetch = _asyncDelegateFlags.asyncDelegateTableViewWillBeginBatchFetchWithContext;
if (canFetch && [_asyncDelegate respondsToSelector:@selector(shouldBatchFetchForTableView:)]) { if (canFetch && _asyncDelegateFlags.asyncDelegateShouldBatchFetchForTableView) {
return [_asyncDelegate shouldBatchFetchForTableView:self]; return [_asyncDelegate shouldBatchFetchForTableView:self];
} else { } else {
return canFetch; return canFetch;
@ -763,7 +794,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
- (void)_beginBatchFetching - (void)_beginBatchFetching
{ {
[_batchContext beginBatchFetching]; [_batchContext beginBatchFetching];
if ([_asyncDelegate respondsToSelector:@selector(tableView:willBeginBatchFetchWithContext:)]) { if (_asyncDelegateFlags.asyncDelegateTableViewWillBeginBatchFetchWithContext) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[_asyncDelegate tableView:self willBeginBatchFetchWithContext:_batchContext]; [_asyncDelegate tableView:self willBeginBatchFetchWithContext:_batchContext];
}); });
@ -1013,7 +1044,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
self.asyncDataSourceLocked = YES; self.asyncDataSourceLocked = YES;
if ([_asyncDataSource respondsToSelector:@selector(tableViewLockDataSource:)]) { if (_asyncDataSourceFlags.asyncDataSourceTableViewLockDataSource) {
[_asyncDataSource tableViewLockDataSource:self]; [_asyncDataSource tableViewLockDataSource:self];
} }
} }
@ -1024,7 +1055,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
self.asyncDataSourceLocked = NO; self.asyncDataSourceLocked = NO;
if ([_asyncDataSource respondsToSelector:@selector(tableViewUnlockDataSource:)]) { if (_asyncDataSourceFlags.asyncDataSourceTableViewUnlockDataSource) {
[_asyncDataSource tableViewUnlockDataSource:self]; [_asyncDataSource tableViewUnlockDataSource:self];
} }
} }
@ -1036,7 +1067,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
- (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataController - (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataController
{ {
if ([_asyncDataSource respondsToSelector:@selector(numberOfSectionsInTableView:)]) { if (_asyncDataSourceFlags.asyncDataSourceNumberOfSectionsInTableView) {
return [_asyncDataSource numberOfSectionsInTableView:self]; return [_asyncDataSource numberOfSectionsInTableView:self];
} else { } else {
return 1; // default section number return 1; // default section number

View File

@ -17,15 +17,19 @@ static BOOL ASAssetIsEqual(AVAsset *asset1, AVAsset *asset2) {
} }
static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) {
if ([videoGravity isEqualToString:AVLayerVideoGravityResizeAspect]) { if ([videoGravity isEqualToString:AVLayerVideoGravityResizeAspectFill]) {
return UIViewContentModeScaleAspectFit;
} else if ([videoGravity isEqual:AVLayerVideoGravityResizeAspectFill]) {
return UIViewContentModeScaleAspectFill; return UIViewContentModeScaleAspectFill;
} else { } else if ([videoGravity isEqualToString:AVLayerVideoGravityResize]) {
return UIViewContentModeScaleToFill; return UIViewContentModeScaleToFill;
} else {
return UIViewContentModeScaleAspectFit;
} }
} }
static void *ASVideoNodeContext = &ASVideoNodeContext;
static NSString * const kPlaybackLikelyToKeepUpKey = @"playbackLikelyToKeepUp";
static NSString * const kStatus = @"status";
@interface ASVideoNode () @interface ASVideoNode ()
{ {
ASDN::RecursiveMutex _videoLock; ASDN::RecursiveMutex _videoLock;
@ -81,72 +85,60 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) {
- (ASDisplayNode *)constructPlayerNode - (ASDisplayNode *)constructPlayerNode
{ {
ASDisplayNode * playerNode = [[ASDisplayNode alloc] initWithLayerBlock:^CALayer *{ ASVideoNode * __weak weakSelf = self;
ASDN::MutexLocker l(_videoLock);
return [[ASDisplayNode alloc] initWithLayerBlock:^CALayer *{
AVPlayerLayer *playerLayer = [[AVPlayerLayer alloc] init]; AVPlayerLayer *playerLayer = [[AVPlayerLayer alloc] init];
if (!_player) { playerLayer.player = weakSelf.player;
[self constructCurrentPlayerItemFromInitData]; playerLayer.videoGravity = weakSelf.gravity;
_player = [AVPlayer playerWithPlayerItem:_currentPlayerItem];
_player.muted = _muted;
}
playerLayer.player = _player;
playerLayer.videoGravity = [self gravity];
return playerLayer; return playerLayer;
}]; }];
return playerNode;
} }
- (void)constructCurrentPlayerItemFromInitData - (AVPlayerItem *)constructPlayerItem
{ {
ASDN::MutexLocker l(_videoLock); ASDN::MutexLocker l(_videoLock);
ASDisplayNodeAssert(_asset, @"ASVideoNode must be initialized with an AVAsset"); ASDisplayNodeAssert(_asset, @"ASVideoNode must be initialized with an AVAsset");
[self removePlayerItemObservers];
AVPlayerItem *playerItem = nil;
if (_asset) { if (_asset != nil) {
if ([_asset.tracks count]) { if (_asset.tracks.count > 0) {
_currentPlayerItem = [[AVPlayerItem alloc] initWithAsset:_asset]; playerItem = [[AVPlayerItem alloc] initWithAsset:_asset];
} else { } else if ([_asset isKindOfClass:[AVURLAsset class]]) {
_currentPlayerItem = [[AVPlayerItem alloc] initWithURL:((AVURLAsset *)_asset).URL]; playerItem = [[AVPlayerItem alloc] initWithURL:((AVURLAsset *)_asset).URL];
} }
} }
if (_currentPlayerItem) { return playerItem;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didPlayToEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:_currentPlayerItem];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(errorWhilePlaying:) name:AVPlayerItemFailedToPlayToEndTimeNotification object:_currentPlayerItem];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(errorWhilePlaying:) name:AVPlayerItemNewErrorLogEntryNotification object:_currentPlayerItem];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
}
} }
- (void)removePlayerItemObservers - (void)addPlayerItemObservers:(AVPlayerItem *)playerItem
{ {
ASDN::MutexLocker l(_videoLock); [playerItem addObserver:self forKeyPath:kStatus options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:ASVideoNodeContext];
[playerItem addObserver:self forKeyPath:kPlaybackLikelyToKeepUpKey options:NSKeyValueObservingOptionNew context:ASVideoNodeContext];
if (_currentPlayerItem) {
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:nil]; NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemFailedToPlayToEndTimeNotification object:nil]; [notificationCenter addObserver:self selector:@selector(didPlayToEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem];
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemNewErrorLogEntryNotification object:nil]; [notificationCenter addObserver:self selector:@selector(errorWhilePlaying:) name:AVPlayerItemFailedToPlayToEndTimeNotification object:playerItem];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil]; [notificationCenter addObserver:self selector:@selector(errorWhilePlaying:) name:AVPlayerItemNewErrorLogEntryNotification object:playerItem];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
}
} }
- (void)didLoad - (void)removePlayerItemObservers:(AVPlayerItem *)playerItem
{ {
[super didLoad]; @try {
[playerItem removeObserver:self forKeyPath:kStatus context:ASVideoNodeContext];
ASDN::MutexLocker l(_videoLock); [playerItem removeObserver:self forKeyPath:kPlaybackLikelyToKeepUpKey context:ASVideoNodeContext];
if (_shouldBePlaying) {
_playerNode = [self constructPlayerNode];
[self insertSubnode:_playerNode atIndex:0];
} else if (_asset) {
[self setPlaceholderImagefromAsset:_asset];
} }
@catch (NSException * __unused exception) {
NSLog(@"Unnecessary KVO removal");
}
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem];
[notificationCenter removeObserver:self name:AVPlayerItemFailedToPlayToEndTimeNotification object:playerItem];
[notificationCenter removeObserver:self name:AVPlayerItemNewErrorLogEntryNotification object:playerItem];
} }
- (void)layout - (void)layout
@ -169,33 +161,51 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) {
_spinner.position = CGPointMake(bounds.size.width/2, bounds.size.height/2); _spinner.position = CGPointMake(bounds.size.width/2, bounds.size.height/2);
} }
- (void)setPlaceholderImagefromAsset:(AVAsset*)asset - (void)generatePlaceholderImage
{
ASVideoNode * __weak weakSelf = self;
AVAsset * __weak asset = self.asset;
[self imageAtTime:kCMTimeZero completionHandler:^(UIImage *image) {
ASPerformBlockOnMainThread(^{
// Ensure the asset hasn't changed since the image request was made
if (ASAssetIsEqual(weakSelf.asset, asset)) {
[weakSelf setVideoPlaceholderImage:image];
}
});
}];
}
- (void)imageAtTime:(CMTime)imageTime completionHandler:(void(^)(UIImage *image))completionHandler
{ {
ASPerformBlockOnBackgroundThread(^{ ASPerformBlockOnBackgroundThread(^{
ASDN::MutexLocker l(_videoLock); ASDN::MutexLocker l(_videoLock);
AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:_asset]; // Skip the asset image generation if we don't have any tracks available that are capable of supporting it
imageGenerator.appliesPreferredTrackTransform = YES; NSArray<AVAssetTrack *>* visualAssetArray = [_asset tracksWithMediaCharacteristic:AVMediaCharacteristicVisual];
NSArray *times = @[[NSValue valueWithCMTime:CMTimeMake(0, 1)]]; if (visualAssetArray.count == 0) {
completionHandler(nil);
[imageGenerator generateCGImagesAsynchronouslyForTimes:times completionHandler:^(CMTime requestedTime, CGImageRef _Nullable image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError * _Nullable error) { return;
}
ASDN::MutexLocker l(_videoLock);
AVAssetImageGenerator *previewImageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:_asset];
// Unfortunately it's not possible to generate a preview image for an HTTP live stream asset, so we'll give up here previewImageGenerator.appliesPreferredTrackTransform = YES;
// http://stackoverflow.com/questions/32112205/m3u8-file-avassetimagegenerator-error
if (image && _placeholderImageNode.image == nil) { [previewImageGenerator generateCGImagesAsynchronouslyForTimes:@[[NSValue valueWithCMTime:imageTime]]
[self setPlaceholderImage:[UIImage imageWithCGImage:image]]; completionHandler:^(CMTime requestedTime, CGImageRef image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError *error) {
} if (error != nil && result != AVAssetImageGeneratorCancelled) {
}]; NSLog(@"Asset preview image generation failed with error: %@", error);
}
completionHandler(image ? [UIImage imageWithCGImage:image] : nil);
}];
}); });
} }
- (void)setPlaceholderImage:(UIImage *)image - (void)setVideoPlaceholderImage:(UIImage *)image
{ {
ASDN::MutexLocker l(_videoLock); ASDN::MutexLocker l(_videoLock);
if (_placeholderImageNode == nil) { if (_placeholderImageNode == nil && image != nil) {
_placeholderImageNode = [[ASImageNode alloc] init]; _placeholderImageNode = [[ASImageNode alloc] init];
_placeholderImageNode.layerBacked = YES; _placeholderImageNode.layerBacked = YES;
_placeholderImageNode.contentMode = ASContentModeFromVideoGravity(_gravity); _placeholderImageNode.contentMode = ASContentModeFromVideoGravity(_gravity);
@ -203,64 +213,37 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) {
_placeholderImageNode.image = image; _placeholderImageNode.image = image;
dispatch_async(dispatch_get_main_queue(), ^{ ASPerformBlockOnMainThread(^{
ASDN::MutexLocker l(_videoLock); ASDN::MutexLocker l(_videoLock);
[self insertSubnode:_placeholderImageNode atIndex:0]; if (_placeholderImageNode != nil) {
[self setNeedsLayout]; [self insertSubnode:_placeholderImageNode atIndex:0];
[self setNeedsLayout];
}
}); });
} }
- (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState
{
[super interfaceStateDidChange:newState fromState:oldState];
BOOL nowVisible = ASInterfaceStateIncludesVisible(newState);
BOOL wasVisible = ASInterfaceStateIncludesVisible(oldState);
ASDN::MutexLocker l(_videoLock);
if (!nowVisible) {
if (wasVisible) {
if (_shouldBePlaying) {
[self pause];
_shouldBePlaying = YES;
}
[(UIActivityIndicatorView *)_spinner.view stopAnimating];
[_spinner removeFromSupernode];
}
} else {
if (_shouldBePlaying) {
[self play];
}
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{ {
ASDN::MutexLocker l(_videoLock); ASDN::MutexLocker l(_videoLock);
if (object == _currentPlayerItem && [keyPath isEqualToString:@"status"]) { if (object != _currentPlayerItem) {
if (_currentPlayerItem.status == AVPlayerItemStatusReadyToPlay) { return;
if ([self.subnodes containsObject:_spinner]) { }
[_spinner removeFromSupernode];
_spinner = nil; if ([keyPath isEqualToString:kStatus]) {
} if ([change[NSKeyValueChangeNewKey] integerValue] == AVPlayerItemStatusReadyToPlay) {
[_spinner removeFromSupernode];
_spinner = nil;
// If we don't yet have a placeholder image update it now that we should have data available for it // If we don't yet have a placeholder image update it now that we should have data available for it
if (_placeholderImageNode.image == nil) { if (_placeholderImageNode.image == nil) {
if (_currentPlayerItem && [self generatePlaceholderImage];
_currentPlayerItem.tracks.count > 0 &&
_currentPlayerItem.tracks[0].assetTrack &&
_currentPlayerItem.tracks[0].assetTrack.asset) {
_asset = _currentPlayerItem.tracks[0].assetTrack.asset;
[self setPlaceholderImagefromAsset:_asset];
[self setNeedsLayout];
}
} }
}
} else if (_currentPlayerItem.status == AVPlayerItemStatusFailed) { } else if ([keyPath isEqualToString:kPlaybackLikelyToKeepUpKey]) {
if (_shouldBePlaying && [change[NSKeyValueChangeNewKey] boolValue] == true && ASInterfaceStateIncludesVisible(self.interfaceState)) {
[self play]; // autoresume after buffer catches up
} }
} }
} }
@ -282,23 +265,20 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) {
{ {
[super fetchData]; [super fetchData];
@try {
[_currentPlayerItem removeObserver:self forKeyPath:NSStringFromSelector(@selector(status))];
}
@catch (NSException * __unused exception) {
NSLog(@"unnecessary removal in fetch data");
}
{ {
ASDN::MutexLocker l(_videoLock); ASDN::MutexLocker l(_videoLock);
[self constructCurrentPlayerItemFromInitData];
[_currentPlayerItem addObserver:self forKeyPath:NSStringFromSelector(@selector(status)) options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:NULL]; AVPlayerItem *playerItem = [self constructPlayerItem];
self.currentItem = playerItem;
if (_player) {
[_player replaceCurrentItemWithPlayerItem:_currentPlayerItem]; if (_player != nil) {
[_player replaceCurrentItemWithPlayerItem:playerItem];
} else { } else {
_player = [[AVPlayer alloc] initWithPlayerItem:_currentPlayerItem]; self.player = [AVPlayer playerWithPlayerItem:playerItem];
_player.muted = _muted; }
if (_placeholderImageNode.image == nil) {
[self generatePlaceholderImage];
} }
} }
} }
@ -309,8 +289,10 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) {
{ {
ASDN::MutexLocker l(_videoLock); ASDN::MutexLocker l(_videoLock);
((AVPlayerLayer *)_playerNode.layer).player = nil;
_player = nil; self.player = nil;
self.currentItem = nil;
_placeholderImageNode.image = nil;
} }
} }
@ -320,24 +302,13 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) {
ASDN::MutexLocker l(_videoLock); ASDN::MutexLocker l(_videoLock);
if (_shouldAutoplay && _playerNode.isNodeLoaded) {
[self play];
} else if (_shouldAutoplay) {
_shouldBePlaying = YES;
}
if (isVisible) { if (isVisible) {
if (_playerNode.isNodeLoaded) { if (_shouldBePlaying || _shouldAutoplay) {
if (!_player) {
[self constructCurrentPlayerItemFromInitData];
_player = [AVPlayer playerWithPlayerItem:_currentPlayerItem];
_player.muted = _muted;
}
((AVPlayerLayer *)_playerNode.layer).player = _player;
}
if (_shouldBePlaying) {
[self play]; [self play];
} }
} else if (_shouldBePlaying) {
[self pause];
_shouldBePlaying = YES;
} }
} }
@ -373,11 +344,14 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) {
return; return;
} }
[self clearFetchedData];
_asset = asset; _asset = asset;
// FIXME: Adopt -setNeedsFetchData when it is available [self setNeedsDataFetch];
if (self.interfaceState & ASInterfaceStateFetchData) {
[self fetchData]; if (_shouldAutoplay) {
[self play];
} }
} }
@ -430,20 +404,15 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) {
- (void)play - (void)play
{ {
ASDN::MutexLocker l(_videoLock); ASDN::MutexLocker l(_videoLock);
if (!_spinner) { if (_player == nil) {
_spinner = [[ASDisplayNode alloc] initWithViewBlock:^UIView *{ [self setNeedsDataFetch];
UIActivityIndicatorView *spinnnerView = [[UIActivityIndicatorView alloc] init];
spinnnerView.color = [UIColor whiteColor];
return spinnnerView;
}];
} }
if (!_playerNode) { if (_playerNode == nil) {
_playerNode = [self constructPlayerNode]; _playerNode = [self constructPlayerNode];
if ([self.subnodes containsObject:_playButton]) { if (_playButton.supernode == self) {
[self insertSubnode:_playerNode belowSubnode:_playButton]; [self insertSubnode:_playerNode belowSubnode:_playButton];
} else { } else {
[self addSubnode:_playerNode]; [self addSubnode:_playerNode];
@ -456,9 +425,19 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) {
[UIView animateWithDuration:0.15 animations:^{ [UIView animateWithDuration:0.15 animations:^{
_playButton.alpha = 0.0; _playButton.alpha = 0.0;
}]; }];
if (![self ready] && _shouldBePlaying && ASInterfaceStateIncludesVisible(self.interfaceState)) { if (![self ready]) {
[self addSubnode:_spinner]; if (!_spinner) {
_spinner = [[ASDisplayNode alloc] initWithViewBlock:^UIView *{
UIActivityIndicatorView *spinnnerView = [[UIActivityIndicatorView alloc] init];
spinnnerView.color = [UIColor whiteColor];
return spinnnerView;
}];
[self addSubnode:_spinner];
}
[(UIActivityIndicatorView *)_spinner.view startAnimating]; [(UIActivityIndicatorView *)_spinner.view startAnimating];
} }
} }
@ -521,26 +500,7 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) {
} }
} }
- (void)willEnterForeground:(NSNotification *)notification #pragma mark - Internal Properties
{
ASDN::MutexLocker l(_videoLock);
if (_shouldBePlaying) {
[self play];
}
}
- (void)didEnterBackground:(NSNotification *)notification
{
ASDN::MutexLocker l(_videoLock);
if (_shouldBePlaying) {
[self pause];
_shouldBePlaying = YES;
}
}
#pragma mark - Property Accessors for Tests
- (ASDisplayNode *)spinner - (ASDisplayNode *)spinner
{ {
@ -563,7 +523,12 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) {
- (void)setCurrentItem:(AVPlayerItem *)currentItem - (void)setCurrentItem:(AVPlayerItem *)currentItem
{ {
ASDN::MutexLocker l(_videoLock); ASDN::MutexLocker l(_videoLock);
[self removePlayerItemObservers:_currentPlayerItem];
_currentPlayerItem = currentItem; _currentPlayerItem = currentItem;
[self addPlayerItemObservers:currentItem];
} }
- (ASDisplayNode *)playerNode - (ASDisplayNode *)playerNode
@ -582,6 +547,8 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) {
{ {
ASDN::MutexLocker l(_videoLock); ASDN::MutexLocker l(_videoLock);
_player = player; _player = player;
player.muted = _muted;
((AVPlayerLayer *)_playerNode.layer).player = player;
} }
- (BOOL)shouldBePlaying - (BOOL)shouldBePlaying
@ -590,19 +557,18 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) {
return _shouldBePlaying; return _shouldBePlaying;
} }
- (void)setShouldBePlaying:(BOOL)shouldBePlaying
{
ASDN::MutexLocker l(_videoLock);
_shouldBePlaying = shouldBePlaying;
}
#pragma mark - Lifecycle #pragma mark - Lifecycle
- (void)dealloc - (void)dealloc
{ {
[_playButton removeTarget:self action:@selector(tapped) forControlEvents:ASControlNodeEventTouchUpInside]; [_playButton removeTarget:self action:@selector(tapped) forControlEvents:ASControlNodeEventTouchUpInside];
[self removePlayerItemObservers]; [self removePlayerItemObservers:_currentPlayerItem];
@try {
[_currentPlayerItem removeObserver:self forKeyPath:NSStringFromSelector(@selector(status))];
}
@catch (NSException * __unused exception) {
NSLog(@"unnecessary removal in dealloc");
}
} }
@end @end

View File

@ -11,7 +11,6 @@
#import <AsyncDisplayKit/ASControlNode.h> #import <AsyncDisplayKit/ASControlNode.h>
#import <AsyncDisplayKit/ASImageNode.h> #import <AsyncDisplayKit/ASImageNode.h>
#import <AsyncDisplayKit/ASImageNode+AnimatedImage.h>
#import <AsyncDisplayKit/ASTextNode.h> #import <AsyncDisplayKit/ASTextNode.h>
#import <AsyncDisplayKit/ASButtonNode.h> #import <AsyncDisplayKit/ASButtonNode.h>
#import <AsyncDisplayKit/ASMapNode.h> #import <AsyncDisplayKit/ASMapNode.h>

View File

@ -131,6 +131,31 @@
} }
} }
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
// Check for a compiled definition for the selector
NSMethodSignature *methodSignature = nil;
if ([self interceptsSelector:aSelector]) {
methodSignature = [[_interceptor class] instanceMethodSignatureForSelector:aSelector];
} else {
methodSignature = [[_target class] instanceMethodSignatureForSelector:aSelector];
}
// Unfortunately, in order to get this object to work properly, the use of a method which creates an NSMethodSignature
// from a C string. -methodSignatureForSelector is called when a compiled definition for the selector cannot be found.
// This is the place where we have to create our own dud NSMethodSignature. This is necessary because if this method
// returns nil, a selector not found exception is raised. The string argument to -signatureWithObjCTypes: outlines
// the return type and arguments to the message. To return a dud NSMethodSignature, pretty much any signature will
// suffice. Since the -forwardInvocation call will do nothing if the delegate does not respond to the selector,
// the dud NSMethodSignature simply gets us around the exception.
return methodSignature ?: [NSMethodSignature signatureWithObjCTypes:"@^v^c"];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
// If we are down here this means _interceptor and _target where nil. Just don't do anything to prevent a crash
}
- (BOOL)interceptsSelector:(SEL)selector - (BOOL)interceptsSelector:(SEL)selector
{ {
ASDisplayNodeAssert(NO, @"This method must be overridden by subclasses."); ASDisplayNodeAssert(NO, @"This method must be overridden by subclasses.");

View File

@ -136,21 +136,62 @@ withDownloadIdentifier:(id)downloadIdentifier;
@protocol ASAnimatedImageProtocol <NSObject> @protocol ASAnimatedImageProtocol <NSObject>
/**
@abstract Should be called when the objects cover image is ready.
@param coverImageReadyCallback a block which receives the cover image.
*/
@property (nonatomic, strong, readwrite) void (^coverImageReadyCallback)(UIImage *coverImage); @property (nonatomic, strong, readwrite) void (^coverImageReadyCallback)(UIImage *coverImage);
@required @required
/**
@abstract Return the objects's cover image.
*/
@property (nonatomic, readonly) UIImage *coverImage; @property (nonatomic, readonly) UIImage *coverImage;
/**
@abstract Return a boolean to indicate that the cover image is ready.
*/
@property (nonatomic, readonly) BOOL coverImageReady; @property (nonatomic, readonly) BOOL coverImageReady;
/**
@abstract Return the total duration of the animated image's playback.
*/
@property (nonatomic, readonly) CFTimeInterval totalDuration; @property (nonatomic, readonly) CFTimeInterval totalDuration;
/**
@abstract Return the interval at which playback should occur. Will be set to a CADisplayLink's frame interval.
*/
@property (nonatomic, readonly) NSUInteger frameInterval; @property (nonatomic, readonly) NSUInteger frameInterval;
/**
@abstract Return the total number of loops the animated image should play or 0 to loop infinitely.
*/
@property (nonatomic, readonly) size_t loopCount; @property (nonatomic, readonly) size_t loopCount;
/**
@abstract Return the total number of frames in the animated image.
*/
@property (nonatomic, readonly) size_t frameCount; @property (nonatomic, readonly) size_t frameCount;
/**
@abstract Return YES when playback is ready to occur.
*/
@property (nonatomic, readonly) BOOL playbackReady; @property (nonatomic, readonly) BOOL playbackReady;
/**
@abstract Return any error that has occured. Playback will be paused if this returns non-nil.
*/
@property (nonatomic, readonly) NSError *error;
/**
@abstract Should be called when playback is ready.
*/
@property (nonatomic, strong, readwrite) dispatch_block_t playbackReadyCallback; @property (nonatomic, strong, readwrite) dispatch_block_t playbackReadyCallback;
/**
@abstract Return the image at a given index.
*/
- (CGImageRef)imageAtIndex:(NSUInteger)index; - (CGImageRef)imageAtIndex:(NSUInteger)index;
/**
@abstract Return the duration at a given index.
*/
- (CFTimeInterval)durationAtIndex:(NSUInteger)index; - (CFTimeInterval)durationAtIndex:(NSUInteger)index;
/**
@abstract Clear any cached data. Called when playback is paused.
*/
- (void)clearAnimatedImageCache; - (void)clearAnimatedImageCache;
@end @end

View File

@ -9,16 +9,10 @@
#import "_ASCoreAnimationExtras.h" #import "_ASCoreAnimationExtras.h"
#import "_ASPendingState.h" #import "_ASPendingState.h"
#import "ASInternalHelpers.h" #import "ASInternalHelpers.h"
#import "ASAssert.h"
#import "ASDisplayNodeInternal.h" #import "ASDisplayNodeInternal.h"
#import "ASDisplayNodeExtras.h"
#import "ASDisplayNode+Subclasses.h" #import "ASDisplayNode+Subclasses.h"
#import "ASDisplayNode+FrameworkPrivate.h" #import "ASDisplayNode+FrameworkPrivate.h"
#import "ASDisplayNode+Beta.h"
#import "ASEqualityHelpers.h"
#import "ASPendingStateController.h" #import "ASPendingStateController.h"
#import "ASThread.h"
#import "ASTextNode.h"
/** /**
* The following macros are conveniences to help in the common tasks related to the bridging that ASDisplayNode does to UIView and CALayer. * The following macros are conveniences to help in the common tasks related to the bridging that ASDisplayNode does to UIView and CALayer.
@ -239,11 +233,11 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNo
// For classes like ASTableNode, ASCollectionNode, ASScrollNode and similar - make sure UIView gets setFrame: // For classes like ASTableNode, ASCollectionNode, ASScrollNode and similar - make sure UIView gets setFrame:
struct ASDisplayNodeFlags flags = _flags; struct ASDisplayNodeFlags flags = _flags;
BOOL setFrameDirectly = flags.synchronous && !flags.layerBacked; BOOL specialPropertiesHandling = ASDisplayNodeNeedsSpecialPropertiesHandlingForFlags(flags);
BOOL nodeLoaded = __loaded(self); BOOL nodeLoaded = __loaded(self);
BOOL isMainThread = ASDisplayNodeThreadIsMain(); BOOL isMainThread = ASDisplayNodeThreadIsMain();
if (!setFrameDirectly) { if (!specialPropertiesHandling) {
BOOL canReadProperties = isMainThread || !nodeLoaded; BOOL canReadProperties = isMainThread || !nodeLoaded;
if (canReadProperties) { if (canReadProperties) {
// We don't have to set frame directly, and we can read current properties. // We don't have to set frame directly, and we can read current properties.
@ -582,7 +576,14 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNo
if (shouldApply) { if (shouldApply) {
CGColorRef oldBackgroundCGColor = _layer.backgroundColor; CGColorRef oldBackgroundCGColor = _layer.backgroundColor;
_layer.backgroundColor = newBackgroundCGColor;
BOOL specialPropertiesHandling = ASDisplayNodeNeedsSpecialPropertiesHandlingForFlags(_flags);
if (specialPropertiesHandling) {
_view.backgroundColor = newBackgroundColor;
} else {
_layer.backgroundColor = newBackgroundCGColor;
}
if (!CGColorEqualToColor(oldBackgroundCGColor, newBackgroundCGColor)) { if (!CGColorEqualToColor(oldBackgroundCGColor, newBackgroundCGColor)) {
[self setNeedsDisplay]; [self setNeedsDisplay];
} }

View File

@ -25,8 +25,10 @@
@class _ASDisplayLayer; @class _ASDisplayLayer;
@class _ASPendingState; @class _ASPendingState;
@class ASSentinel; @class ASSentinel;
struct ASDisplayNodeFlags;
BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector); BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector);
BOOL ASDisplayNodeNeedsSpecialPropertiesHandlingForFlags(ASDisplayNodeFlags flags);
/// Get the pending view state for the node, creating one if needed. /// Get the pending view state for the node, creating one if needed.
_ASPendingState *ASDisplayNodeGetPendingState(ASDisplayNode *node); _ASPendingState *ASDisplayNodeGetPendingState(ASDisplayNode *node);

View File

@ -11,7 +11,6 @@
@interface ASImageNode () @interface ASImageNode ()
{ {
ASDN::RecursiveMutex _animatedImageLock; ASDN::RecursiveMutex _animatedImageLock;
ASDN::RecursiveMutex _animatedImagePausedLock;
ASDN::Mutex _displayLinkLock; ASDN::Mutex _displayLinkLock;
id <ASAnimatedImageProtocol> _animatedImage; id <ASAnimatedImageProtocol> _animatedImage;
BOOL _animatedImagePaused; BOOL _animatedImagePaused;

View File

@ -22,7 +22,7 @@
- (int32_t)increment - (int32_t)increment
{ {
return OSAtomicIncrement32(&_value); return OSAtomicAdd32(1, &_value);
} }
@end @end

View File

@ -24,7 +24,7 @@
// Supports all of the properties included in the ASDisplayNodeViewProperties protocol // Supports all of the properties included in the ASDisplayNodeViewProperties protocol
- (void)applyToView:(UIView *)view setFrameDirectly:(BOOL)setFrameDirectly; - (void)applyToView:(UIView *)view withSpecialPropertiesHandling:(BOOL)setFrameDirectly;
- (void)applyToLayer:(CALayer *)layer; - (void)applyToLayer:(CALayer *)layer;
+ (_ASPendingState *)pendingViewStateFromLayer:(CALayer *)layer; + (_ASPendingState *)pendingViewStateFromLayer:(CALayer *)layer;

View File

@ -745,7 +745,7 @@ static UIColor *defaultTintColor = nil;
ASPendingStateApplyMetricsToLayer(self, layer); ASPendingStateApplyMetricsToLayer(self, layer);
} }
- (void)applyToView:(UIView *)view setFrameDirectly:(BOOL)setFrameDirectly - (void)applyToView:(UIView *)view withSpecialPropertiesHandling:(BOOL)specialPropertiesHandling
{ {
/* /*
Use our convenience setters blah here instead of layer.blah Use our convenience setters blah here instead of layer.blah
@ -789,8 +789,15 @@ static UIColor *defaultTintColor = nil;
if (flags.setClipsToBounds) if (flags.setClipsToBounds)
view.clipsToBounds = clipsToBounds; view.clipsToBounds = clipsToBounds;
if (flags.setBackgroundColor) if (flags.setBackgroundColor) {
layer.backgroundColor = backgroundColor; // We have to make sure certain nodes get the background color call directly set
if (specialPropertiesHandling) {
view.backgroundColor = [UIColor colorWithCGColor:backgroundColor];
} else {
// Set the background color to the layer as in the UIView bridge we use this value as background color
layer.backgroundColor = backgroundColor;
}
}
if (flags.setTintColor) if (flags.setTintColor)
view.tintColor = self.tintColor; view.tintColor = self.tintColor;
@ -907,8 +914,7 @@ static UIColor *defaultTintColor = nil;
if (flags.setAccessibilityPath) if (flags.setAccessibilityPath)
view.accessibilityPath = accessibilityPath; view.accessibilityPath = accessibilityPath;
// For classes like ASTableNode, ASCollectionNode, ASScrollNode and similar - make sure UIView gets setFrame: if (flags.setFrame && specialPropertiesHandling) {
if (flags.setFrame && setFrameDirectly) {
// Frame is only defined when transform is identity because we explicitly diverge from CALayer behavior and define frame without transform // Frame is only defined when transform is identity because we explicitly diverge from CALayer behavior and define frame without transform
#if DEBUG #if DEBUG
// Checking if the transform is identity is expensive, so disable when unnecessary. We have assertions on in Release, so DEBUG is the only way I know of. // Checking if the transform is identity is expensive, so disable when unnecessary. We have assertions on in Release, so DEBUG is the only way I know of.

View File

@ -29,9 +29,9 @@
@property (atomic, readonly) ASImageNode *placeholderImageNode; @property (atomic, readonly) ASImageNode *placeholderImageNode;
@property (atomic, readwrite) ASDisplayNode *playerNode; @property (atomic, readwrite) ASDisplayNode *playerNode;
@property (atomic, readwrite) AVPlayer *player; @property (atomic, readwrite) AVPlayer *player;
@property (atomic, readonly) BOOL shouldBePlaying; @property (atomic, readwrite) BOOL shouldBePlaying;
- (void)setPlaceholderImage:(UIImage *)image; - (void)setVideoPlaceholderImage:(UIImage *)image;
@end @end
@ -313,28 +313,24 @@
XCTAssertTrue(_videoNode.isPlaying); XCTAssertTrue(_videoNode.isPlaying);
} }
- (void)testBackgroundingAndForegroungingTheAppShouldPauseAndResume - (void)testVideoResumedWhenBufferIsLikelyToKeepUp
{ {
_videoNode.asset = _firstAsset; _videoNode.asset = _firstAsset;
[_videoNode didLoad];
[_videoNode setInterfaceState:ASInterfaceStateVisible | ASInterfaceStateDisplay | ASInterfaceStateFetchData]; [_videoNode setInterfaceState:ASInterfaceStateVisible | ASInterfaceStateDisplay | ASInterfaceStateFetchData];
[_videoNode play]; [_videoNode pause];
_videoNode.shouldBePlaying = YES;
XCTAssertTrue(_videoNode.isPlaying);
[[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationDidEnterBackgroundNotification object:nil];
XCTAssertFalse(_videoNode.isPlaying); XCTAssertFalse(_videoNode.isPlaying);
[[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationWillEnterForegroundNotification object:nil]; [_videoNode observeValueForKeyPath:@"playbackLikelyToKeepUp" ofObject:[_videoNode currentItem] change:@{NSKeyValueChangeNewKey : @YES} context:NULL];
XCTAssertTrue(_videoNode.isPlaying); XCTAssertTrue(_videoNode.isPlaying);
} }
- (void)testSettingVideoGravityChangesPlaceholderContentMode - (void)testSettingVideoGravityChangesPlaceholderContentMode
{ {
[_videoNode setPlaceholderImage:[[UIImage alloc] init]]; [_videoNode setVideoPlaceholderImage:[[UIImage alloc] init]];
XCTAssertEqual(UIViewContentModeScaleAspectFit, _videoNode.placeholderImageNode.contentMode); XCTAssertEqual(UIViewContentModeScaleAspectFit, _videoNode.placeholderImageNode.contentMode);
_videoNode.gravity = AVLayerVideoGravityResize; _videoNode.gravity = AVLayerVideoGravityResize;
@ -362,4 +358,31 @@
XCTAssertNotEqual(_videoNode, firstButton.supernode); XCTAssertNotEqual(_videoNode, firstButton.supernode);
} }
- (void)testChangingAssetsChangesPlaceholderImage
{
UIImage *firstImage = [[UIImage alloc] init];
_videoNode.asset = _firstAsset;
[_videoNode setVideoPlaceholderImage:firstImage];
XCTAssertEqual(firstImage, _videoNode.placeholderImageNode.image);
_videoNode.asset = _secondAsset;
XCTAssertNotEqual(firstImage, _videoNode.placeholderImageNode.image);
}
- (void)testClearingFetchedContentShouldClearAssetData
{
_videoNode.asset = _firstAsset;
[_videoNode fetchData];
[_videoNode setVideoPlaceholderImage:[[UIImage alloc] init]];
XCTAssertNotNil(_videoNode.player);
XCTAssertNotNil(_videoNode.currentItem);
XCTAssertNotNil(_videoNode.placeholderImageNode.image);
[_videoNode clearFetchedData];
XCTAssertNil(_videoNode.player);
XCTAssertNil(_videoNode.currentItem);
XCTAssertNil(_videoNode.placeholderImageNode.image);
}
@end @end

View File

@ -1,45 +0,0 @@
//
// AppDelegate.m
// ASAnimatedImage
//
// Created by Garrett Moon on 3/22/16.
// Copyright © 2016 Facebook, Inc. All rights reserved.
//
#import "AppDelegate.h"
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
- (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
@end

View File

@ -0,0 +1,23 @@
//
// AppDelegate.m
// ASAnimatedImage
//
// Created by Garrett Moon on 3/22/16.
// Copyright © 2016 Facebook, Inc. All rights reserved.
//
#import "AppDelegate.h"
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
return YES;
}
@end

View File

@ -29,9 +29,4 @@
[self.view addSubnode:imageNode]; [self.view addSubnode:imageNode];
} }
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end @end