mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-10 08:20:16 +00:00
Fix merge conflicts
This commit is contained in:
commit
b586b9f34b
@ -251,9 +251,7 @@
|
||||
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 */; };
|
||||
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 */; };
|
||||
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 */; };
|
||||
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, ); }; };
|
||||
@ -767,7 +765,6 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@ -1060,7 +1057,6 @@
|
||||
0587F9BC1A7309ED00AFF0BA /* ASEditableTextNode.mm */,
|
||||
058D09DD195D050800B7D73C /* ASImageNode.h */,
|
||||
058D09DE195D050800B7D73C /* ASImageNode.mm */,
|
||||
68355B2D1CB5799E001D4E68 /* ASImageNode+AnimatedImage.h */,
|
||||
68355B2E1CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm */,
|
||||
0516FA3E1A1563D200B4EBED /* ASMultiplexImageNode.h */,
|
||||
0516FA3F1A1563D200B4EBED /* ASMultiplexImageNode.mm */,
|
||||
@ -1561,7 +1557,6 @@
|
||||
257754C31BEE458E00737CA5 /* ASTextNodeTypes.h in Headers */,
|
||||
9C49C36F1B853957000B0DD5 /* ASStackLayoutable.h in Headers */,
|
||||
69E1006D1CA89CB600D88C1B /* ASEnvironmentInternal.h in Headers */,
|
||||
68355B301CB5799E001D4E68 /* ASImageNode+AnimatedImage.h in Headers */,
|
||||
AC21EC101B3D0BF600C8B19A /* ASStackLayoutDefines.h in Headers */,
|
||||
CC7FD9DE1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */,
|
||||
ACF6ED2F1B17843500DA7C62 /* ASStackLayoutSpec.h in Headers */,
|
||||
@ -1677,7 +1672,6 @@
|
||||
34EFC75F1B701C8600AD841F /* ASInsetLayoutSpec.h in Headers */,
|
||||
34EFC75D1B701BE900AD841F /* ASInternalHelpers.h in Headers */,
|
||||
34EFC7671B701CD900AD841F /* ASLayout.h in Headers */,
|
||||
68355B331CB579AD001D4E68 /* ASImageNode+AnimatedImage.h in Headers */,
|
||||
DEC146B71C37A16A004A0EE7 /* ASCollectionInternal.h in Headers */,
|
||||
DBDB83951C6E879900D0098C /* ASPagerFlowLayout.h in Headers */,
|
||||
34EFC7691B701CE100AD841F /* ASLayoutable.h in Headers */,
|
||||
|
||||
@ -106,9 +106,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
NSMutableArray *_batchUpdateBlocks;
|
||||
|
||||
BOOL _asyncDataFetchingEnabled;
|
||||
BOOL _asyncDelegateImplementsScrollviewDidScroll;
|
||||
BOOL _asyncDataSourceImplementsConstrainedSizeForNode;
|
||||
BOOL _asyncDataSourceImplementsNodeBlockForItemAtIndexPath;
|
||||
_ASCollectionViewNodeSizeInvalidationContext *_queuedNodeSizeInvalidationContext; // Main thread only
|
||||
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.
|
||||
*/
|
||||
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;
|
||||
@ -333,16 +350,22 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
if (asyncDataSource == nil) {
|
||||
_asyncDataSource = nil;
|
||||
_proxyDataSource = _isDeallocating ? nil : [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self];
|
||||
_asyncDataSourceImplementsConstrainedSizeForNode = NO;
|
||||
_asyncDataSourceImplementsNodeBlockForItemAtIndexPath = NO;
|
||||
|
||||
memset(&_asyncDataSourceFlags, 0, sizeof(_asyncDataSourceFlags));
|
||||
} else {
|
||||
_asyncDataSource = asyncDataSource;
|
||||
_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:
|
||||
ASDisplayNodeAssertTrue(_asyncDataSourceImplementsNodeBlockForItemAtIndexPath || [_asyncDataSource respondsToSelector:@selector(collectionView:nodeForItemAtIndexPath:)]);
|
||||
ASDisplayNodeAssertTrue(_asyncDataSourceFlags.asyncDataSourceNodeBlockForItemAtIndexPath || _asyncDataSourceFlags.asyncDataSourceNodeForItemAtIndexPath);
|
||||
}
|
||||
|
||||
super.dataSource = (id<UICollectionViewDataSource>)_proxyDataSource;
|
||||
@ -363,11 +386,19 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
if (asyncDelegate == nil) {
|
||||
_asyncDelegate = nil;
|
||||
_proxyDelegate = _isDeallocating ? nil : [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self];
|
||||
_asyncDelegateImplementsScrollviewDidScroll = NO;
|
||||
|
||||
memset(&_asyncDelegateFlags, 0, sizeof(_asyncDelegateFlags));
|
||||
} else {
|
||||
_asyncDelegate = asyncDelegate;
|
||||
_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;
|
||||
@ -566,7 +597,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
ASCellNode *cellNode = [cell node];
|
||||
cellNode.scrollView = collectionView;
|
||||
|
||||
if ([_asyncDelegate respondsToSelector:@selector(collectionView:willDisplayNodeForItemAtIndexPath:)]) {
|
||||
if (_asyncDelegateFlags.asyncDelegateCollectionViewWillDisplayNodeForItemAtIndexPath) {
|
||||
[_asyncDelegate collectionView:self willDisplayNodeForItemAtIndexPath:indexPath];
|
||||
}
|
||||
|
||||
@ -586,7 +617,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
|
||||
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.");
|
||||
[_asyncDelegate collectionView:self didEndDisplayingNode:cellNode forItemAtIndexPath:indexPath];
|
||||
}
|
||||
@ -597,7 +628,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
if ([_asyncDelegate respondsToSelector:@selector(collectionView:didEndDisplayingNodeForItemAtIndexPath:)]) {
|
||||
if (_asyncDelegateFlags.asyncDelegateCollectionViewDidEndDisplayingNodeForItemAtIndexPathDeprecated) {
|
||||
[_asyncDelegate collectionView:self didEndDisplayingNodeForItemAtIndexPath:indexPath];
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
@ -620,7 +651,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
inScrollView:scrollView
|
||||
withCellFrame:collectionCell.frame];
|
||||
}
|
||||
if (_asyncDelegateImplementsScrollviewDidScroll) {
|
||||
if (_asyncDelegateFlags.asyncDelegateScrollViewDidScroll) {
|
||||
[_asyncDelegate scrollViewDidScroll:scrollView];
|
||||
}
|
||||
}
|
||||
@ -637,7 +668,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
[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];
|
||||
}
|
||||
}
|
||||
@ -747,8 +778,8 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
- (BOOL)canBatchFetch
|
||||
{
|
||||
// if the delegate does not respond to this method, there is no point in starting to fetch
|
||||
BOOL canFetch = [_asyncDelegate respondsToSelector:@selector(collectionView:willBeginBatchFetchWithContext:)];
|
||||
if (canFetch && [_asyncDelegate respondsToSelector:@selector(shouldBatchFetchForCollectionView:)]) {
|
||||
BOOL canFetch = _asyncDelegateFlags.asyncDelegateCollectionViewWillBeginBatchFetchWithContext;
|
||||
if (canFetch && _asyncDelegateFlags.asyncDelegateShouldBatchFetchForCollectionView) {
|
||||
return [_asyncDelegate shouldBatchFetchForCollectionView:self];
|
||||
} else {
|
||||
return canFetch;
|
||||
@ -788,7 +819,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
- (void)_beginBatchFetching
|
||||
{
|
||||
[_batchContext beginBatchFetching];
|
||||
if ([_asyncDelegate respondsToSelector:@selector(collectionView:willBeginBatchFetchWithContext:)]) {
|
||||
if (_asyncDelegateFlags.asyncDelegateCollectionViewWillBeginBatchFetchWithContext) {
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
[_asyncDelegate collectionView:self willBeginBatchFetchWithContext:_batchContext];
|
||||
});
|
||||
@ -800,7 +831,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
|
||||
- (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if (!_asyncDataSourceImplementsNodeBlockForItemAtIndexPath) {
|
||||
if (!_asyncDataSourceFlags.asyncDataSourceNodeBlockForItemAtIndexPath) {
|
||||
ASCellNode *node = [_asyncDataSource collectionView:self nodeForItemAtIndexPath:indexPath];
|
||||
ASDisplayNodeAssert([node isKindOfClass:ASCellNode.class], @"invalid node class, expected ASCellNode");
|
||||
__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
|
||||
// implement a custom inspector.
|
||||
if (_asyncDataSourceImplementsConstrainedSizeForNode) {
|
||||
if (_asyncDataSourceFlags.asyncDataSourceConstrainedSizeForNode) {
|
||||
constrainedSize = [_asyncDataSource collectionView:self constrainedSizeForNodeAtIndexPath:indexPath];
|
||||
} else {
|
||||
CGSize maxSize = CGSizeEqualToSize(_maxSizeForNodesConstrainedSize, CGSizeZero) ? self.bounds.size : _maxSizeForNodesConstrainedSize;
|
||||
@ -863,7 +894,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
}
|
||||
|
||||
- (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataController {
|
||||
if ([_asyncDataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)]) {
|
||||
if (_asyncDataSourceFlags.asyncDataSourceNumberOfSectionsInCollectionView) {
|
||||
return [_asyncDataSource numberOfSectionsInCollectionView:self];
|
||||
} else {
|
||||
return 1;
|
||||
@ -875,7 +906,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
ASDisplayNodeAssert(!self.asyncDataSourceLocked, @"The data source has already been locked");
|
||||
|
||||
self.asyncDataSourceLocked = YES;
|
||||
if ([_asyncDataSource respondsToSelector:@selector(collectionViewLockDataSource:)]) {
|
||||
if (_asyncDataSourceFlags.asyncDataSourceCollectionViewLockDataSource) {
|
||||
[_asyncDataSource collectionViewLockDataSource:self];
|
||||
}
|
||||
}
|
||||
@ -885,7 +916,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
ASDisplayNodeAssert(self.asyncDataSourceLocked, @"The data source has already been unlocked");
|
||||
|
||||
self.asyncDataSourceLocked = NO;
|
||||
if ([_asyncDataSource respondsToSelector:@selector(collectionViewUnlockDataSource:)]) {
|
||||
if (_asyncDataSourceFlags.asyncDataSourceCollectionViewUnlockDataSource) {
|
||||
[_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
|
||||
}
|
||||
|
||||
[_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:_performingBatchUpdates];
|
||||
if (_performingBatchUpdates) {
|
||||
[_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:YES];
|
||||
[_batchUpdateBlocks addObject:^{
|
||||
[super insertItemsAtIndexPaths:indexPaths];
|
||||
}];
|
||||
} else {
|
||||
[_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:NO];
|
||||
[UIView performWithoutAnimation:^{
|
||||
[super insertItemsAtIndexPaths:indexPaths];
|
||||
[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
|
||||
}
|
||||
|
||||
[_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:_performingBatchUpdates];
|
||||
if (_performingBatchUpdates) {
|
||||
[_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:YES];
|
||||
[_batchUpdateBlocks addObject:^{
|
||||
[super deleteItemsAtIndexPaths:indexPaths];
|
||||
}];
|
||||
} else {
|
||||
[_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:NO];
|
||||
[UIView performWithoutAnimation:^{
|
||||
[super deleteItemsAtIndexPaths:indexPaths];
|
||||
[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
|
||||
}
|
||||
|
||||
[_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:_performingBatchUpdates];
|
||||
if (_performingBatchUpdates) {
|
||||
[_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:YES];
|
||||
[_batchUpdateBlocks addObject:^{
|
||||
[super insertSections:indexSet];
|
||||
}];
|
||||
} else {
|
||||
[_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:NO];
|
||||
[UIView performWithoutAnimation:^{
|
||||
[super insertSections:indexSet];
|
||||
[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
|
||||
}
|
||||
|
||||
[_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:_performingBatchUpdates];
|
||||
if (_performingBatchUpdates) {
|
||||
[_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:YES];
|
||||
[_batchUpdateBlocks addObject:^{
|
||||
[super deleteSections:indexSet];
|
||||
}];
|
||||
} else {
|
||||
[_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:NO];
|
||||
[UIView performWithoutAnimation:^{
|
||||
[super deleteSections:indexSet];
|
||||
[self _scheduleCheckForBatchFetchingForNumberOfChanges:indexSet.count];
|
||||
|
||||
@ -83,6 +83,13 @@ BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL 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)
|
||||
{
|
||||
ASDN::MutexLocker l(node->_propertyLock);
|
||||
@ -954,8 +961,8 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
||||
if (self.layerBacked) {
|
||||
[_pendingViewState applyToLayer:self.layer];
|
||||
} else {
|
||||
BOOL setFrameDirectly = (_flags.synchronous && !_flags.layerBacked);
|
||||
[_pendingViewState applyToView:self.view setFrameDirectly:setFrameDirectly];
|
||||
BOOL specialPropertiesHandling = ASDisplayNodeNeedsSpecialPropertiesHandlingForFlags(_flags);
|
||||
[_pendingViewState applyToView:self.view withSpecialPropertiesHandling:specialPropertiesHandling];
|
||||
}
|
||||
|
||||
[_pendingViewState clearChanges];
|
||||
|
||||
@ -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
|
||||
@ -6,7 +6,7 @@
|
||||
// Copyright © 2016 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ASImageNode+AnimatedImage.h"
|
||||
#import "ASImageNode.h"
|
||||
|
||||
#import "ASAssert.h"
|
||||
#import "ASImageProtocols.h"
|
||||
@ -25,9 +25,12 @@
|
||||
- (void)setAnimatedImage:(id <ASAnimatedImageProtocol>)animatedImage
|
||||
{
|
||||
ASDN::MutexLocker l(_animatedImageLock);
|
||||
if (!ASObjectIsEqual(_animatedImage, animatedImage)) {
|
||||
_animatedImage = animatedImage;
|
||||
if (ASObjectIsEqual(_animatedImage, animatedImage)) {
|
||||
return;
|
||||
}
|
||||
|
||||
_animatedImage = animatedImage;
|
||||
|
||||
if (animatedImage != nil) {
|
||||
__weak ASImageNode *weakSelf = self;
|
||||
if ([animatedImage respondsToSelector:@selector(setCoverImageReadyCallback:)]) {
|
||||
@ -50,7 +53,7 @@
|
||||
|
||||
- (void)setAnimatedImagePaused:(BOOL)animatedImagePaused
|
||||
{
|
||||
ASDN::MutexLocker l(_animatedImagePausedLock);
|
||||
ASDN::MutexLocker l(_animatedImageLock);
|
||||
_animatedImagePaused = animatedImagePaused;
|
||||
ASPerformBlockOnMainThread(^{
|
||||
if (animatedImagePaused) {
|
||||
@ -63,7 +66,7 @@
|
||||
|
||||
- (BOOL)animatedImagePaused
|
||||
{
|
||||
ASDN::MutexLocker l(_animatedImagePausedLock);
|
||||
ASDN::MutexLocker l(_animatedImageLock);
|
||||
return _animatedImagePaused;
|
||||
}
|
||||
|
||||
@ -84,7 +87,7 @@
|
||||
|
||||
- (void)animatedImageFileReady
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
ASPerformBlockOnMainThread(^{
|
||||
[self startAnimating];
|
||||
});
|
||||
}
|
||||
@ -147,18 +150,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)__enterHierarchy
|
||||
{
|
||||
[super __enterHierarchy];
|
||||
[self startAnimating];
|
||||
}
|
||||
|
||||
- (void)__exitHierarchy
|
||||
{
|
||||
[super __exitHierarchy];
|
||||
[self stopAnimating];
|
||||
}
|
||||
|
||||
- (void)displayLinkFired:(CADisplayLink *)displayLink
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
@ -8,6 +8,8 @@
|
||||
|
||||
#import <AsyncDisplayKit/ASControlNode.h>
|
||||
|
||||
#import "ASImageProtocols.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
@ -119,6 +121,22 @@ typedef UIImage * _Nullable (^asimagenode_modification_block_t)(UIImage *image);
|
||||
@property (nonatomic, assign) BOOL isDefaultFocusAppearance;
|
||||
#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
|
||||
|
||||
|
||||
|
||||
@ -16,7 +16,6 @@
|
||||
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Beta.h>
|
||||
#import <AsyncDisplayKit/ASTextNode.h>
|
||||
#import <AsyncDisplayKit/ASImageNode+AnimatedImage.h>
|
||||
#import <AsyncDisplayKit/ASImageNode+AnimatedImagePrivate.h>
|
||||
|
||||
#import "ASImageNode+CGExtras.h"
|
||||
|
||||
@ -221,7 +221,6 @@
|
||||
|
||||
- (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];
|
||||
}
|
||||
|
||||
|
||||
@ -15,7 +15,6 @@
|
||||
#import "ASThread.h"
|
||||
#import "ASInternalHelpers.h"
|
||||
#import "ASImageContainerProtocolCategories.h"
|
||||
#import "ASImageNode+AnimatedImage.h"
|
||||
|
||||
#if PIN_REMOTE_IMAGE
|
||||
#import "ASPINRemoteImageDownloader.h"
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
@end
|
||||
|
||||
@interface ASTableNode ()
|
||||
@property (nonatomic) _ASTablePendingState *pendingState;
|
||||
@property (nonatomic, strong) _ASTablePendingState *pendingState;
|
||||
@end
|
||||
|
||||
@interface ASTableView ()
|
||||
@ -68,7 +68,7 @@
|
||||
|
||||
ASTableView *view = self.view;
|
||||
view.tableNode = self;
|
||||
|
||||
|
||||
if (_pendingState) {
|
||||
_ASTablePendingState *pendingState = _pendingState;
|
||||
self.pendingState = nil;
|
||||
|
||||
@ -112,9 +112,25 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
BOOL _ignoreNodesConstrainedWidthChange;
|
||||
BOOL _queuedNodeHeightUpdate;
|
||||
BOOL _isDeallocating;
|
||||
BOOL _dataSourceImplementsNodeBlockForRowAtIndexPath;
|
||||
BOOL _asyncDelegateImplementsScrollviewDidScroll;
|
||||
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;
|
||||
@ -260,13 +276,20 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
if (asyncDataSource == nil) {
|
||||
_asyncDataSource = nil;
|
||||
_proxyDataSource = _isDeallocating ? nil : [[ASTableViewProxy alloc] initWithTarget:nil interceptor:self];
|
||||
_dataSourceImplementsNodeBlockForRowAtIndexPath = NO;
|
||||
|
||||
memset(&_asyncDataSourceFlags, 0, sizeof(_asyncDataSourceFlags));
|
||||
} else {
|
||||
_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];
|
||||
|
||||
_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;
|
||||
@ -287,11 +310,19 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
if (asyncDelegate == nil) {
|
||||
_asyncDelegate = nil;
|
||||
_proxyDelegate = _isDeallocating ? nil : [[ASTableViewProxy alloc] initWithTarget:nil interceptor:self];
|
||||
_asyncDelegateImplementsScrollviewDidScroll = NO;
|
||||
|
||||
memset(&_asyncDelegateFlags, 0, sizeof(_asyncDelegateFlags));
|
||||
} else {
|
||||
_asyncDelegate = asyncDelegate;
|
||||
_asyncDelegateImplementsScrollviewDidScroll = [_asyncDelegate respondsToSelector:@selector(scrollViewDidScroll:)];
|
||||
_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;
|
||||
@ -584,7 +615,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
ASCellNode *cellNode = [cell node];
|
||||
cellNode.scrollView = tableView;
|
||||
|
||||
if ([_asyncDelegate respondsToSelector:@selector(tableView:willDisplayNodeForRowAtIndexPath:)]) {
|
||||
if (_asyncDelegateFlags.asyncDelegateTableViewWillDisplayNodeForRowAtIndexPath) {
|
||||
[_asyncDelegate tableView:self willDisplayNodeForRowAtIndexPath:indexPath];
|
||||
}
|
||||
|
||||
@ -609,7 +640,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
|
||||
[_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.");
|
||||
[_asyncDelegate tableView:self didEndDisplayingNode:cellNode forRowAtIndexPath:indexPath];
|
||||
}
|
||||
@ -620,7 +651,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
if ([_asyncDelegate respondsToSelector:@selector(tableView:didEndDisplayingNodeForRowAtIndexPath:)]) {
|
||||
if (_asyncDelegateFlags.asyncDelegateTableViewDidEndDisplayingNodeForRowAtIndexPathDeprecated) {
|
||||
[_asyncDelegate tableView:self didEndDisplayingNodeForRowAtIndexPath:indexPath];
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
@ -642,7 +673,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
inScrollView:scrollView
|
||||
withCellFrame:tableCell.frame];
|
||||
}
|
||||
if (_asyncDelegateImplementsScrollviewDidScroll) {
|
||||
if (_asyncDelegateFlags.asyncDelegateScrollViewDidScroll) {
|
||||
[_asyncDelegate scrollViewDidScroll:scrollView];
|
||||
}
|
||||
}
|
||||
@ -659,7 +690,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
[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];
|
||||
}
|
||||
}
|
||||
@ -722,8 +753,8 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
- (BOOL)canBatchFetch
|
||||
{
|
||||
// if the delegate does not respond to this method, there is no point in starting to fetch
|
||||
BOOL canFetch = [_asyncDelegate respondsToSelector:@selector(tableView:willBeginBatchFetchWithContext:)];
|
||||
if (canFetch && [_asyncDelegate respondsToSelector:@selector(shouldBatchFetchForTableView:)]) {
|
||||
BOOL canFetch = _asyncDelegateFlags.asyncDelegateTableViewWillBeginBatchFetchWithContext;
|
||||
if (canFetch && _asyncDelegateFlags.asyncDelegateShouldBatchFetchForTableView) {
|
||||
return [_asyncDelegate shouldBatchFetchForTableView:self];
|
||||
} else {
|
||||
return canFetch;
|
||||
@ -763,7 +794,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
- (void)_beginBatchFetching
|
||||
{
|
||||
[_batchContext beginBatchFetching];
|
||||
if ([_asyncDelegate respondsToSelector:@selector(tableView:willBeginBatchFetchWithContext:)]) {
|
||||
if (_asyncDelegateFlags.asyncDelegateTableViewWillBeginBatchFetchWithContext) {
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
[_asyncDelegate tableView:self willBeginBatchFetchWithContext:_batchContext];
|
||||
});
|
||||
@ -1013,7 +1044,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
|
||||
self.asyncDataSourceLocked = YES;
|
||||
|
||||
if ([_asyncDataSource respondsToSelector:@selector(tableViewLockDataSource:)]) {
|
||||
if (_asyncDataSourceFlags.asyncDataSourceTableViewLockDataSource) {
|
||||
[_asyncDataSource tableViewLockDataSource:self];
|
||||
}
|
||||
}
|
||||
@ -1024,7 +1055,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
|
||||
self.asyncDataSourceLocked = NO;
|
||||
|
||||
if ([_asyncDataSource respondsToSelector:@selector(tableViewUnlockDataSource:)]) {
|
||||
if (_asyncDataSourceFlags.asyncDataSourceTableViewUnlockDataSource) {
|
||||
[_asyncDataSource tableViewUnlockDataSource:self];
|
||||
}
|
||||
}
|
||||
@ -1036,7 +1067,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
|
||||
- (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataController
|
||||
{
|
||||
if ([_asyncDataSource respondsToSelector:@selector(numberOfSectionsInTableView:)]) {
|
||||
if (_asyncDataSourceFlags.asyncDataSourceNumberOfSectionsInTableView) {
|
||||
return [_asyncDataSource numberOfSectionsInTableView:self];
|
||||
} else {
|
||||
return 1; // default section number
|
||||
|
||||
@ -17,15 +17,19 @@ static BOOL ASAssetIsEqual(AVAsset *asset1, AVAsset *asset2) {
|
||||
}
|
||||
|
||||
static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) {
|
||||
if ([videoGravity isEqualToString:AVLayerVideoGravityResizeAspect]) {
|
||||
return UIViewContentModeScaleAspectFit;
|
||||
} else if ([videoGravity isEqual:AVLayerVideoGravityResizeAspectFill]) {
|
||||
if ([videoGravity isEqualToString:AVLayerVideoGravityResizeAspectFill]) {
|
||||
return UIViewContentModeScaleAspectFill;
|
||||
} else {
|
||||
} else if ([videoGravity isEqualToString:AVLayerVideoGravityResize]) {
|
||||
return UIViewContentModeScaleToFill;
|
||||
} else {
|
||||
return UIViewContentModeScaleAspectFit;
|
||||
}
|
||||
}
|
||||
|
||||
static void *ASVideoNodeContext = &ASVideoNodeContext;
|
||||
static NSString * const kPlaybackLikelyToKeepUpKey = @"playbackLikelyToKeepUp";
|
||||
static NSString * const kStatus = @"status";
|
||||
|
||||
@interface ASVideoNode ()
|
||||
{
|
||||
ASDN::RecursiveMutex _videoLock;
|
||||
@ -81,72 +85,60 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) {
|
||||
|
||||
- (ASDisplayNode *)constructPlayerNode
|
||||
{
|
||||
ASDisplayNode * playerNode = [[ASDisplayNode alloc] initWithLayerBlock:^CALayer *{
|
||||
ASDN::MutexLocker l(_videoLock);
|
||||
|
||||
ASVideoNode * __weak weakSelf = self;
|
||||
|
||||
return [[ASDisplayNode alloc] initWithLayerBlock:^CALayer *{
|
||||
AVPlayerLayer *playerLayer = [[AVPlayerLayer alloc] init];
|
||||
if (!_player) {
|
||||
[self constructCurrentPlayerItemFromInitData];
|
||||
_player = [AVPlayer playerWithPlayerItem:_currentPlayerItem];
|
||||
_player.muted = _muted;
|
||||
}
|
||||
playerLayer.player = _player;
|
||||
playerLayer.videoGravity = [self gravity];
|
||||
playerLayer.player = weakSelf.player;
|
||||
playerLayer.videoGravity = weakSelf.gravity;
|
||||
return playerLayer;
|
||||
}];
|
||||
|
||||
return playerNode;
|
||||
}
|
||||
|
||||
- (void)constructCurrentPlayerItemFromInitData
|
||||
- (AVPlayerItem *)constructPlayerItem
|
||||
{
|
||||
ASDN::MutexLocker l(_videoLock);
|
||||
|
||||
ASDisplayNodeAssert(_asset, @"ASVideoNode must be initialized with an AVAsset");
|
||||
[self removePlayerItemObservers];
|
||||
|
||||
AVPlayerItem *playerItem = nil;
|
||||
|
||||
if (_asset) {
|
||||
if ([_asset.tracks count]) {
|
||||
_currentPlayerItem = [[AVPlayerItem alloc] initWithAsset:_asset];
|
||||
} else {
|
||||
_currentPlayerItem = [[AVPlayerItem alloc] initWithURL:((AVURLAsset *)_asset).URL];
|
||||
if (_asset != nil) {
|
||||
if (_asset.tracks.count > 0) {
|
||||
playerItem = [[AVPlayerItem alloc] initWithAsset:_asset];
|
||||
} else if ([_asset isKindOfClass:[AVURLAsset class]]) {
|
||||
playerItem = [[AVPlayerItem alloc] initWithURL:((AVURLAsset *)_asset).URL];
|
||||
}
|
||||
}
|
||||
|
||||
if (_currentPlayerItem) {
|
||||
[[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];
|
||||
}
|
||||
|
||||
return playerItem;
|
||||
}
|
||||
|
||||
- (void)removePlayerItemObservers
|
||||
- (void)addPlayerItemObservers:(AVPlayerItem *)playerItem
|
||||
{
|
||||
ASDN::MutexLocker l(_videoLock);
|
||||
|
||||
if (_currentPlayerItem) {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemFailedToPlayToEndTimeNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemNewErrorLogEntryNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
|
||||
}
|
||||
[playerItem addObserver:self forKeyPath:kStatus options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:ASVideoNodeContext];
|
||||
[playerItem addObserver:self forKeyPath:kPlaybackLikelyToKeepUpKey options:NSKeyValueObservingOptionNew context:ASVideoNodeContext];
|
||||
|
||||
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
|
||||
[notificationCenter addObserver:self selector:@selector(didPlayToEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem];
|
||||
[notificationCenter addObserver:self selector:@selector(errorWhilePlaying:) name:AVPlayerItemFailedToPlayToEndTimeNotification object:playerItem];
|
||||
[notificationCenter addObserver:self selector:@selector(errorWhilePlaying:) name:AVPlayerItemNewErrorLogEntryNotification object:playerItem];
|
||||
}
|
||||
|
||||
- (void)didLoad
|
||||
- (void)removePlayerItemObservers:(AVPlayerItem *)playerItem
|
||||
{
|
||||
[super didLoad];
|
||||
|
||||
ASDN::MutexLocker l(_videoLock);
|
||||
|
||||
if (_shouldBePlaying) {
|
||||
_playerNode = [self constructPlayerNode];
|
||||
[self insertSubnode:_playerNode atIndex:0];
|
||||
} else if (_asset) {
|
||||
[self setPlaceholderImagefromAsset:_asset];
|
||||
@try {
|
||||
[playerItem removeObserver:self forKeyPath:kStatus context:ASVideoNodeContext];
|
||||
[playerItem removeObserver:self forKeyPath:kPlaybackLikelyToKeepUpKey context:ASVideoNodeContext];
|
||||
}
|
||||
@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
|
||||
@ -169,33 +161,51 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) {
|
||||
_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(^{
|
||||
ASDN::MutexLocker l(_videoLock);
|
||||
|
||||
AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:_asset];
|
||||
imageGenerator.appliesPreferredTrackTransform = YES;
|
||||
NSArray *times = @[[NSValue valueWithCMTime:CMTimeMake(0, 1)]];
|
||||
|
||||
[imageGenerator generateCGImagesAsynchronouslyForTimes:times completionHandler:^(CMTime requestedTime, CGImageRef _Nullable image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError * _Nullable error) {
|
||||
|
||||
ASDN::MutexLocker l(_videoLock);
|
||||
|
||||
// Unfortunately it's not possible to generate a preview image for an HTTP live stream asset, so we'll give up here
|
||||
// http://stackoverflow.com/questions/32112205/m3u8-file-avassetimagegenerator-error
|
||||
if (image && _placeholderImageNode.image == nil) {
|
||||
[self setPlaceholderImage:[UIImage imageWithCGImage:image]];
|
||||
}
|
||||
}];
|
||||
|
||||
// Skip the asset image generation if we don't have any tracks available that are capable of supporting it
|
||||
NSArray<AVAssetTrack *>* visualAssetArray = [_asset tracksWithMediaCharacteristic:AVMediaCharacteristicVisual];
|
||||
if (visualAssetArray.count == 0) {
|
||||
completionHandler(nil);
|
||||
return;
|
||||
}
|
||||
|
||||
AVAssetImageGenerator *previewImageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:_asset];
|
||||
previewImageGenerator.appliesPreferredTrackTransform = YES;
|
||||
|
||||
[previewImageGenerator generateCGImagesAsynchronouslyForTimes:@[[NSValue valueWithCMTime:imageTime]]
|
||||
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);
|
||||
|
||||
if (_placeholderImageNode == nil) {
|
||||
if (_placeholderImageNode == nil && image != nil) {
|
||||
_placeholderImageNode = [[ASImageNode alloc] init];
|
||||
_placeholderImageNode.layerBacked = YES;
|
||||
_placeholderImageNode.contentMode = ASContentModeFromVideoGravity(_gravity);
|
||||
@ -203,64 +213,37 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) {
|
||||
|
||||
_placeholderImageNode.image = image;
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
ASPerformBlockOnMainThread(^{
|
||||
ASDN::MutexLocker l(_videoLock);
|
||||
|
||||
[self insertSubnode:_placeholderImageNode atIndex:0];
|
||||
[self setNeedsLayout];
|
||||
if (_placeholderImageNode != nil) {
|
||||
[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
|
||||
{
|
||||
ASDN::MutexLocker l(_videoLock);
|
||||
|
||||
if (object == _currentPlayerItem && [keyPath isEqualToString:@"status"]) {
|
||||
if (_currentPlayerItem.status == AVPlayerItemStatusReadyToPlay) {
|
||||
if ([self.subnodes containsObject:_spinner]) {
|
||||
[_spinner removeFromSupernode];
|
||||
_spinner = nil;
|
||||
}
|
||||
|
||||
|
||||
if (object != _currentPlayerItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
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 (_placeholderImageNode.image == nil) {
|
||||
if (_currentPlayerItem &&
|
||||
_currentPlayerItem.tracks.count > 0 &&
|
||||
_currentPlayerItem.tracks[0].assetTrack &&
|
||||
_currentPlayerItem.tracks[0].assetTrack.asset) {
|
||||
_asset = _currentPlayerItem.tracks[0].assetTrack.asset;
|
||||
[self setPlaceholderImagefromAsset:_asset];
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
[self generatePlaceholderImage];
|
||||
}
|
||||
|
||||
} 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];
|
||||
|
||||
@try {
|
||||
[_currentPlayerItem removeObserver:self forKeyPath:NSStringFromSelector(@selector(status))];
|
||||
}
|
||||
@catch (NSException * __unused exception) {
|
||||
NSLog(@"unnecessary removal in fetch data");
|
||||
}
|
||||
|
||||
{
|
||||
ASDN::MutexLocker l(_videoLock);
|
||||
[self constructCurrentPlayerItemFromInitData];
|
||||
[_currentPlayerItem addObserver:self forKeyPath:NSStringFromSelector(@selector(status)) options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:NULL];
|
||||
|
||||
if (_player) {
|
||||
[_player replaceCurrentItemWithPlayerItem:_currentPlayerItem];
|
||||
|
||||
AVPlayerItem *playerItem = [self constructPlayerItem];
|
||||
self.currentItem = playerItem;
|
||||
|
||||
if (_player != nil) {
|
||||
[_player replaceCurrentItemWithPlayerItem:playerItem];
|
||||
} else {
|
||||
_player = [[AVPlayer alloc] initWithPlayerItem:_currentPlayerItem];
|
||||
_player.muted = _muted;
|
||||
self.player = [AVPlayer playerWithPlayerItem:playerItem];
|
||||
}
|
||||
|
||||
if (_placeholderImageNode.image == nil) {
|
||||
[self generatePlaceholderImage];
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -309,8 +289,10 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) {
|
||||
|
||||
{
|
||||
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);
|
||||
|
||||
if (_shouldAutoplay && _playerNode.isNodeLoaded) {
|
||||
[self play];
|
||||
} else if (_shouldAutoplay) {
|
||||
_shouldBePlaying = YES;
|
||||
}
|
||||
if (isVisible) {
|
||||
if (_playerNode.isNodeLoaded) {
|
||||
if (!_player) {
|
||||
[self constructCurrentPlayerItemFromInitData];
|
||||
_player = [AVPlayer playerWithPlayerItem:_currentPlayerItem];
|
||||
_player.muted = _muted;
|
||||
}
|
||||
((AVPlayerLayer *)_playerNode.layer).player = _player;
|
||||
}
|
||||
|
||||
if (_shouldBePlaying) {
|
||||
if (_shouldBePlaying || _shouldAutoplay) {
|
||||
[self play];
|
||||
}
|
||||
} else if (_shouldBePlaying) {
|
||||
[self pause];
|
||||
_shouldBePlaying = YES;
|
||||
}
|
||||
}
|
||||
|
||||
@ -373,11 +344,14 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) {
|
||||
return;
|
||||
}
|
||||
|
||||
[self clearFetchedData];
|
||||
|
||||
_asset = asset;
|
||||
|
||||
// FIXME: Adopt -setNeedsFetchData when it is available
|
||||
if (self.interfaceState & ASInterfaceStateFetchData) {
|
||||
[self fetchData];
|
||||
|
||||
[self setNeedsDataFetch];
|
||||
|
||||
if (_shouldAutoplay) {
|
||||
[self play];
|
||||
}
|
||||
}
|
||||
|
||||
@ -430,20 +404,15 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) {
|
||||
- (void)play
|
||||
{
|
||||
ASDN::MutexLocker l(_videoLock);
|
||||
|
||||
if (!_spinner) {
|
||||
_spinner = [[ASDisplayNode alloc] initWithViewBlock:^UIView *{
|
||||
UIActivityIndicatorView *spinnnerView = [[UIActivityIndicatorView alloc] init];
|
||||
spinnnerView.color = [UIColor whiteColor];
|
||||
|
||||
return spinnnerView;
|
||||
}];
|
||||
|
||||
if (_player == nil) {
|
||||
[self setNeedsDataFetch];
|
||||
}
|
||||
|
||||
if (!_playerNode) {
|
||||
|
||||
if (_playerNode == nil) {
|
||||
_playerNode = [self constructPlayerNode];
|
||||
|
||||
if ([self.subnodes containsObject:_playButton]) {
|
||||
|
||||
if (_playButton.supernode == self) {
|
||||
[self insertSubnode:_playerNode belowSubnode:_playButton];
|
||||
} else {
|
||||
[self addSubnode:_playerNode];
|
||||
@ -456,9 +425,19 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) {
|
||||
[UIView animateWithDuration:0.15 animations:^{
|
||||
_playButton.alpha = 0.0;
|
||||
}];
|
||||
|
||||
if (![self ready] && _shouldBePlaying && ASInterfaceStateIncludesVisible(self.interfaceState)) {
|
||||
[self addSubnode:_spinner];
|
||||
|
||||
if (![self ready]) {
|
||||
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];
|
||||
}
|
||||
}
|
||||
@ -521,26 +500,7 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) {
|
||||
}
|
||||
}
|
||||
|
||||
- (void)willEnterForeground:(NSNotification *)notification
|
||||
{
|
||||
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
|
||||
#pragma mark - Internal Properties
|
||||
|
||||
- (ASDisplayNode *)spinner
|
||||
{
|
||||
@ -563,7 +523,12 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) {
|
||||
- (void)setCurrentItem:(AVPlayerItem *)currentItem
|
||||
{
|
||||
ASDN::MutexLocker l(_videoLock);
|
||||
|
||||
[self removePlayerItemObservers:_currentPlayerItem];
|
||||
|
||||
_currentPlayerItem = currentItem;
|
||||
|
||||
[self addPlayerItemObservers:currentItem];
|
||||
}
|
||||
|
||||
- (ASDisplayNode *)playerNode
|
||||
@ -582,6 +547,8 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) {
|
||||
{
|
||||
ASDN::MutexLocker l(_videoLock);
|
||||
_player = player;
|
||||
player.muted = _muted;
|
||||
((AVPlayerLayer *)_playerNode.layer).player = player;
|
||||
}
|
||||
|
||||
- (BOOL)shouldBePlaying
|
||||
@ -590,19 +557,18 @@ static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) {
|
||||
return _shouldBePlaying;
|
||||
}
|
||||
|
||||
- (void)setShouldBePlaying:(BOOL)shouldBePlaying
|
||||
{
|
||||
ASDN::MutexLocker l(_videoLock);
|
||||
_shouldBePlaying = shouldBePlaying;
|
||||
}
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[_playButton removeTarget:self action:@selector(tapped) forControlEvents:ASControlNodeEventTouchUpInside];
|
||||
[self removePlayerItemObservers];
|
||||
|
||||
@try {
|
||||
[_currentPlayerItem removeObserver:self forKeyPath:NSStringFromSelector(@selector(status))];
|
||||
}
|
||||
@catch (NSException * __unused exception) {
|
||||
NSLog(@"unnecessary removal in dealloc");
|
||||
}
|
||||
[self removePlayerItemObservers:_currentPlayerItem];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@ -11,7 +11,6 @@
|
||||
|
||||
#import <AsyncDisplayKit/ASControlNode.h>
|
||||
#import <AsyncDisplayKit/ASImageNode.h>
|
||||
#import <AsyncDisplayKit/ASImageNode+AnimatedImage.h>
|
||||
#import <AsyncDisplayKit/ASTextNode.h>
|
||||
#import <AsyncDisplayKit/ASButtonNode.h>
|
||||
#import <AsyncDisplayKit/ASMapNode.h>
|
||||
|
||||
@ -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
|
||||
{
|
||||
ASDisplayNodeAssert(NO, @"This method must be overridden by subclasses.");
|
||||
|
||||
@ -136,21 +136,62 @@ withDownloadIdentifier:(id)downloadIdentifier;
|
||||
|
||||
@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);
|
||||
|
||||
@required
|
||||
|
||||
/**
|
||||
@abstract Return the objects's cover image.
|
||||
*/
|
||||
@property (nonatomic, readonly) UIImage *coverImage;
|
||||
/**
|
||||
@abstract Return a boolean to indicate that the cover image is ready.
|
||||
*/
|
||||
@property (nonatomic, readonly) BOOL coverImageReady;
|
||||
/**
|
||||
@abstract Return the total duration of the animated image's playback.
|
||||
*/
|
||||
@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;
|
||||
/**
|
||||
@abstract Return the total number of loops the animated image should play or 0 to loop infinitely.
|
||||
*/
|
||||
@property (nonatomic, readonly) size_t loopCount;
|
||||
/**
|
||||
@abstract Return the total number of frames in the animated image.
|
||||
*/
|
||||
@property (nonatomic, readonly) size_t frameCount;
|
||||
/**
|
||||
@abstract Return YES when playback is ready to occur.
|
||||
*/
|
||||
@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;
|
||||
|
||||
/**
|
||||
@abstract Return the image at a given index.
|
||||
*/
|
||||
- (CGImageRef)imageAtIndex:(NSUInteger)index;
|
||||
/**
|
||||
@abstract Return the duration at a given index.
|
||||
*/
|
||||
- (CFTimeInterval)durationAtIndex:(NSUInteger)index;
|
||||
/**
|
||||
@abstract Clear any cached data. Called when playback is paused.
|
||||
*/
|
||||
- (void)clearAnimatedImageCache;
|
||||
|
||||
@end
|
||||
|
||||
@ -9,16 +9,10 @@
|
||||
#import "_ASCoreAnimationExtras.h"
|
||||
#import "_ASPendingState.h"
|
||||
#import "ASInternalHelpers.h"
|
||||
#import "ASAssert.h"
|
||||
#import "ASDisplayNodeInternal.h"
|
||||
#import "ASDisplayNodeExtras.h"
|
||||
#import "ASDisplayNode+Subclasses.h"
|
||||
#import "ASDisplayNode+FrameworkPrivate.h"
|
||||
#import "ASDisplayNode+Beta.h"
|
||||
#import "ASEqualityHelpers.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.
|
||||
@ -239,11 +233,11 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNo
|
||||
|
||||
// For classes like ASTableNode, ASCollectionNode, ASScrollNode and similar - make sure UIView gets setFrame:
|
||||
struct ASDisplayNodeFlags flags = _flags;
|
||||
BOOL setFrameDirectly = flags.synchronous && !flags.layerBacked;
|
||||
BOOL specialPropertiesHandling = ASDisplayNodeNeedsSpecialPropertiesHandlingForFlags(flags);
|
||||
|
||||
BOOL nodeLoaded = __loaded(self);
|
||||
BOOL isMainThread = ASDisplayNodeThreadIsMain();
|
||||
if (!setFrameDirectly) {
|
||||
if (!specialPropertiesHandling) {
|
||||
BOOL canReadProperties = isMainThread || !nodeLoaded;
|
||||
if (canReadProperties) {
|
||||
// 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) {
|
||||
CGColorRef oldBackgroundCGColor = _layer.backgroundColor;
|
||||
_layer.backgroundColor = newBackgroundCGColor;
|
||||
|
||||
BOOL specialPropertiesHandling = ASDisplayNodeNeedsSpecialPropertiesHandlingForFlags(_flags);
|
||||
if (specialPropertiesHandling) {
|
||||
_view.backgroundColor = newBackgroundColor;
|
||||
} else {
|
||||
_layer.backgroundColor = newBackgroundCGColor;
|
||||
}
|
||||
|
||||
if (!CGColorEqualToColor(oldBackgroundCGColor, newBackgroundCGColor)) {
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
@ -25,8 +25,10 @@
|
||||
@class _ASDisplayLayer;
|
||||
@class _ASPendingState;
|
||||
@class ASSentinel;
|
||||
struct ASDisplayNodeFlags;
|
||||
|
||||
BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector);
|
||||
BOOL ASDisplayNodeNeedsSpecialPropertiesHandlingForFlags(ASDisplayNodeFlags flags);
|
||||
|
||||
/// Get the pending view state for the node, creating one if needed.
|
||||
_ASPendingState *ASDisplayNodeGetPendingState(ASDisplayNode *node);
|
||||
|
||||
@ -11,7 +11,6 @@
|
||||
@interface ASImageNode ()
|
||||
{
|
||||
ASDN::RecursiveMutex _animatedImageLock;
|
||||
ASDN::RecursiveMutex _animatedImagePausedLock;
|
||||
ASDN::Mutex _displayLinkLock;
|
||||
id <ASAnimatedImageProtocol> _animatedImage;
|
||||
BOOL _animatedImagePaused;
|
||||
|
||||
@ -22,7 +22,7 @@
|
||||
|
||||
- (int32_t)increment
|
||||
{
|
||||
return OSAtomicIncrement32(&_value);
|
||||
return OSAtomicAdd32(1, &_value);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
|
||||
// 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;
|
||||
|
||||
+ (_ASPendingState *)pendingViewStateFromLayer:(CALayer *)layer;
|
||||
|
||||
@ -745,7 +745,7 @@ static UIColor *defaultTintColor = nil;
|
||||
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
|
||||
@ -789,8 +789,15 @@ static UIColor *defaultTintColor = nil;
|
||||
if (flags.setClipsToBounds)
|
||||
view.clipsToBounds = clipsToBounds;
|
||||
|
||||
if (flags.setBackgroundColor)
|
||||
layer.backgroundColor = backgroundColor;
|
||||
if (flags.setBackgroundColor) {
|
||||
// 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)
|
||||
view.tintColor = self.tintColor;
|
||||
@ -907,8 +914,7 @@ static UIColor *defaultTintColor = nil;
|
||||
if (flags.setAccessibilityPath)
|
||||
view.accessibilityPath = accessibilityPath;
|
||||
|
||||
// For classes like ASTableNode, ASCollectionNode, ASScrollNode and similar - make sure UIView gets setFrame:
|
||||
if (flags.setFrame && setFrameDirectly) {
|
||||
if (flags.setFrame && specialPropertiesHandling) {
|
||||
// Frame is only defined when transform is identity because we explicitly diverge from CALayer behavior and define frame without transform
|
||||
#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.
|
||||
|
||||
@ -29,9 +29,9 @@
|
||||
@property (atomic, readonly) ASImageNode *placeholderImageNode;
|
||||
@property (atomic, readwrite) ASDisplayNode *playerNode;
|
||||
@property (atomic, readwrite) AVPlayer *player;
|
||||
@property (atomic, readonly) BOOL shouldBePlaying;
|
||||
@property (atomic, readwrite) BOOL shouldBePlaying;
|
||||
|
||||
- (void)setPlaceholderImage:(UIImage *)image;
|
||||
- (void)setVideoPlaceholderImage:(UIImage *)image;
|
||||
|
||||
@end
|
||||
|
||||
@ -313,28 +313,24 @@
|
||||
XCTAssertTrue(_videoNode.isPlaying);
|
||||
}
|
||||
|
||||
- (void)testBackgroundingAndForegroungingTheAppShouldPauseAndResume
|
||||
- (void)testVideoResumedWhenBufferIsLikelyToKeepUp
|
||||
{
|
||||
_videoNode.asset = _firstAsset;
|
||||
|
||||
[_videoNode didLoad];
|
||||
[_videoNode setInterfaceState:ASInterfaceStateVisible | ASInterfaceStateDisplay | ASInterfaceStateFetchData];
|
||||
[_videoNode play];
|
||||
|
||||
XCTAssertTrue(_videoNode.isPlaying);
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationDidEnterBackgroundNotification object:nil];
|
||||
[_videoNode pause];
|
||||
_videoNode.shouldBePlaying = YES;
|
||||
|
||||
XCTAssertFalse(_videoNode.isPlaying);
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationWillEnterForegroundNotification object:nil];
|
||||
[_videoNode observeValueForKeyPath:@"playbackLikelyToKeepUp" ofObject:[_videoNode currentItem] change:@{NSKeyValueChangeNewKey : @YES} context:NULL];
|
||||
|
||||
XCTAssertTrue(_videoNode.isPlaying);
|
||||
}
|
||||
|
||||
- (void)testSettingVideoGravityChangesPlaceholderContentMode
|
||||
{
|
||||
[_videoNode setPlaceholderImage:[[UIImage alloc] init]];
|
||||
[_videoNode setVideoPlaceholderImage:[[UIImage alloc] init]];
|
||||
XCTAssertEqual(UIViewContentModeScaleAspectFit, _videoNode.placeholderImageNode.contentMode);
|
||||
|
||||
_videoNode.gravity = AVLayerVideoGravityResize;
|
||||
@ -362,4 +358,31 @@
|
||||
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
|
||||
|
||||
@ -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
|
||||
23
examples/AnimatedGIF/ASAnimatedImage/AppDelegate.m
Normal file
23
examples/AnimatedGIF/ASAnimatedImage/AppDelegate.m
Normal 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
|
||||
@ -29,9 +29,4 @@
|
||||
[self.view addSubnode:imageNode];
|
||||
}
|
||||
|
||||
- (void)didReceiveMemoryWarning {
|
||||
[super didReceiveMemoryWarning];
|
||||
// Dispose of any resources that can be recreated.
|
||||
}
|
||||
|
||||
@end
|
||||
Loading…
x
Reference in New Issue
Block a user