mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2026-01-07 21:44:59 +00:00
Merge branch 'master' into update-objc
Conflicts: AsyncDisplayKit/ASDisplayNode+Subclasses.h AsyncDisplayKit/ASMultiplexImageNode.h AsyncDisplayKit/ASViewController.h AsyncDisplayKit/Details/ASDataController.h
This commit is contained in:
@@ -15,6 +15,29 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*/
|
||||
@interface ASCellNode : ASDisplayNode
|
||||
|
||||
/**
|
||||
* @abstract When enabled, ensures that the cell is completely displayed before allowed onscreen.
|
||||
*
|
||||
* @default NO
|
||||
* @discussion Normally, ASCellNodes are preloaded and have finished display before they are onscreen.
|
||||
* However, if the Table or Collection's rangeTuningParameters are set to small values (or 0),
|
||||
* or if the user is scrolling rapidly on a slow device, it is possible for a cell's display to
|
||||
* be incomplete when it becomes visible.
|
||||
*
|
||||
* In this case, normally placeholder states are shown and scrolling continues uninterrupted.
|
||||
* The finished, drawn content is then shown as soon as it is ready.
|
||||
*
|
||||
* With this property set to YES, the main thread will be blocked until display is complete for
|
||||
* the cell. This is more similar to UIKit, and in fact makes AsyncDisplayKit scrolling visually
|
||||
* indistinguishible from UIKit's, except being faster.
|
||||
*
|
||||
* Using this option does not eliminate all of the performance advantages of AsyncDisplayKit.
|
||||
* Normally, a cell has been preloading and is almost done when it reaches the screen, so the
|
||||
* blocking time is very short. If the rangeTuningParameters are set to 0, still this option
|
||||
* outperforms UIKit: while the main thread is waiting, subnode display executes concurrently.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL neverShowPlaceholders;
|
||||
|
||||
/*
|
||||
* ASTableView uses these properties when configuring UITableViewCells that host ASCellNodes.
|
||||
*/
|
||||
|
||||
@@ -213,6 +213,15 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*/
|
||||
- (ASCellNode *)nodeForItemAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
/**
|
||||
* Similar to -indexPathForCell:.
|
||||
*
|
||||
* @param cellNode a cellNode part of the table view
|
||||
*
|
||||
* @returns an indexPath for this cellNode
|
||||
*/
|
||||
- (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode;
|
||||
|
||||
/**
|
||||
* Similar to -visibleCells.
|
||||
*
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
#import "UICollectionViewLayout+ASConvenience.h"
|
||||
#import "ASInternalHelpers.h"
|
||||
|
||||
// FIXME: Temporary nonsense import until method names are finalized and exposed
|
||||
#import "ASDisplayNode+Subclasses.h"
|
||||
|
||||
const static NSUInteger kASCollectionViewAnimationNone = UITableViewRowAnimationNone;
|
||||
|
||||
|
||||
@@ -234,7 +237,8 @@ static BOOL _isInterceptedSelector(SEL sel)
|
||||
|
||||
- (void)setDataSource:(id<UICollectionViewDataSource>)dataSource
|
||||
{
|
||||
ASDisplayNodeAssert(NO, @"ASCollectionView uses asyncDataSource, not UICollectionView's dataSource property.");
|
||||
// UIKit can internally generate a call to this method upon changing the asyncDataSource; only assert for non-nil.
|
||||
ASDisplayNodeAssert(dataSource == nil, @"ASCollectionView uses asyncDataSource, not UICollectionView's dataSource property.");
|
||||
}
|
||||
|
||||
- (void)setDelegate:(id<UICollectionViewDelegate>)delegate
|
||||
@@ -314,6 +318,16 @@ static BOOL _isInterceptedSelector(SEL sel)
|
||||
return [[_dataController nodeAtIndexPath:indexPath] calculatedSize];
|
||||
}
|
||||
|
||||
- (ASCellNode *)nodeForItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
return [_dataController nodeAtIndexPath:indexPath];
|
||||
}
|
||||
|
||||
- (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode
|
||||
{
|
||||
return [_dataController indexPathForNode:cellNode];
|
||||
}
|
||||
|
||||
- (NSArray *)visibleNodes
|
||||
{
|
||||
NSArray *indexPaths = [self indexPathsForVisibleItems];
|
||||
@@ -391,11 +405,6 @@ static BOOL _isInterceptedSelector(SEL sel)
|
||||
[_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:kASCollectionViewAnimationNone];
|
||||
}
|
||||
|
||||
- (ASCellNode *)nodeForItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
return [_dataController nodeAtIndexPath:indexPath];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Intercepted selectors.
|
||||
|
||||
@@ -489,6 +498,11 @@ static BOOL _isInterceptedSelector(SEL sel)
|
||||
if ([_asyncDelegate respondsToSelector:@selector(collectionView:willDisplayNodeForItemAtIndexPath:)]) {
|
||||
[_asyncDelegate collectionView:self willDisplayNodeForItemAtIndexPath:indexPath];
|
||||
}
|
||||
|
||||
ASCellNode *cellNode = [self nodeForItemAtIndexPath:indexPath];
|
||||
if (cellNode.neverShowPlaceholders) {
|
||||
[cellNode recursivelyEnsureDisplay];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
|
||||
@@ -431,9 +431,41 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
// This method has proven helpful in a few rare scenarios, similar to a category extension on UIView,
|
||||
// but it's considered private API for now and its use should not be encouraged.
|
||||
- (nullable ASDisplayNode *)_supernodeWithClass:(Class)supernodeClass;
|
||||
|
||||
// The two methods below will eventually be exposed, but their names are subject to change.
|
||||
/**
|
||||
* @abstract Ensure that all rendering is complete for this node and its descendents.
|
||||
*
|
||||
* @discussion Calling this method on the main thread after a node is added to the view heirarchy will ensure that
|
||||
* placeholder states are never visible to the user. It is used by ASTableView, ASCollectionView, and ASViewController
|
||||
* to implement their respective ".neverShowPlaceholders" option.
|
||||
*
|
||||
* If all nodes have layer.contents set and/or their layer does not have -needsDisplay set, the method will return immediately.
|
||||
*
|
||||
* This method is capable of handling a mixed set of nodes, with some not having started display, some in progress on an
|
||||
* asynchronous display operation, and some already finished.
|
||||
*
|
||||
* In order to guarantee against deadlocks, this method should only be called on the main thread.
|
||||
* It may block on the private queue, [_ASDisplayLayer displayQueue]
|
||||
*/
|
||||
- (void)recursivelyEnsureDisplay;
|
||||
|
||||
/**
|
||||
* @abstract Allows a node to bypass all ensureDisplay passes. Defaults to NO.
|
||||
*
|
||||
* @discussion Nodes that are expensive to draw and expected to have placeholder even with
|
||||
* .neverShowPlaceholders enabled should set this to YES.
|
||||
*
|
||||
* ASImageNode uses the default of NO, as it is often used for UI images that are expected to synchronize with ensureDisplay.
|
||||
*
|
||||
* ASNetworkImageNode and ASMultiplexImageNode set this to YES, because they load data from a database or server,
|
||||
* and are expected to support a placeholder state given that display is often blocked on slow data fetching.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL shouldBypassEnsureDisplay;
|
||||
|
||||
@end
|
||||
|
||||
#define ASDisplayNodeAssertThreadAffinity(viewNode) ASDisplayNodeAssert(!viewNode || ASDisplayNodeThreadIsMain() || !(viewNode).nodeLoaded, @"Incorrect display node thread affinity")
|
||||
#define ASDisplayNodeCAssertThreadAffinity(viewNode) ASDisplayNodeCAssert(!viewNode || ASDisplayNodeThreadIsMain() || !(viewNode).nodeLoaded, @"Incorrect display node thread affinity")
|
||||
#define ASDisplayNodeAssertThreadAffinity(viewNode) ASDisplayNodeAssert(!viewNode || ASDisplayNodeThreadIsMain() || !(viewNode).nodeLoaded, @"Incorrect display node thread affinity - this method should not be called off the main thread after the ASDisplayNode's view or layer have been created")
|
||||
#define ASDisplayNodeCAssertThreadAffinity(viewNode) ASDisplayNodeCAssert(!viewNode || ASDisplayNodeThreadIsMain() || !(viewNode).nodeLoaded, @"Incorrect display node thread affinity - this method should not be called off the main thread after the ASDisplayNode's view or layer have been created")
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#import <objc/runtime.h>
|
||||
|
||||
#import "_ASAsyncTransaction.h"
|
||||
#import "_ASAsyncTransactionContainer+Private.h"
|
||||
#import "_ASPendingState.h"
|
||||
#import "_ASDisplayView.h"
|
||||
#import "_ASScopeTimer.h"
|
||||
@@ -671,25 +672,20 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
||||
// This is the root node. Trigger a full measurement pass on *current* thread. Old constrained size is re-used.
|
||||
[self measureWithSizeRange:oldConstrainedSize];
|
||||
|
||||
CGSize oldSize = self.bounds.size;
|
||||
CGRect oldBounds = self.bounds;
|
||||
CGSize oldSize = oldBounds.size;
|
||||
CGSize newSize = _layout.size;
|
||||
|
||||
if (! CGSizeEqualToSize(oldSize, newSize)) {
|
||||
CGRect bounds = self.bounds;
|
||||
bounds.size = newSize;
|
||||
self.bounds = bounds;
|
||||
self.bounds = (CGRect){ oldBounds.origin, newSize };
|
||||
|
||||
// Frame's origin must be preserved. Since it is computed from bounds size, anchorPoint
|
||||
// and position (see frame setter in ASDisplayNode+UIViewBridge), position needs to be adjusted.
|
||||
BOOL useLayer = (_layer && ASDisplayNodeThreadIsMain());
|
||||
CGPoint anchorPoint = (useLayer ? _layer.anchorPoint : self.anchorPoint);
|
||||
CGPoint oldPosition = (useLayer ? _layer.position : self.position);
|
||||
|
||||
CGPoint anchorPoint = self.anchorPoint;
|
||||
CGPoint oldPosition = self.position;
|
||||
CGFloat xDelta = (newSize.width - oldSize.width) * anchorPoint.x;
|
||||
CGFloat yDelta = (newSize.height - oldSize.height) * anchorPoint.y;
|
||||
CGPoint newPosition = CGPointMake(oldPosition.x + xDelta, oldPosition.y + yDelta);
|
||||
|
||||
useLayer ? _layer.position = newPosition : self.position = newPosition;
|
||||
self.position = CGPointMake(oldPosition.x + xDelta, oldPosition.y + yDelta);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1415,6 +1411,68 @@ static NSInteger incrementIfFound(NSInteger i) {
|
||||
[_placeholderLayer removeFromSuperlayer];
|
||||
}
|
||||
|
||||
void recursivelyEnsureDisplayForLayer(CALayer *layer)
|
||||
{
|
||||
// This recursion must handle layers in various states:
|
||||
// 1. Just added to hierarchy, CA hasn't yet called -display
|
||||
// 2. Previously in a hierarchy (such as a working window owned by an Intelligent Preloading class, like ASTableView / ASCollectionView / ASViewController)
|
||||
// 3. Has no content to display at all
|
||||
// Specifically for case 1), we need to explicitly trigger a -display call now.
|
||||
// Otherwise, there is no opportunity to block the main thread after CoreAnimation's transaction commit
|
||||
// (even a runloop observer at a late call order will not stop the next frame from compositing, showing placeholders).
|
||||
|
||||
ASDisplayNode *node = [layer asyncdisplaykit_node];
|
||||
if (!layer.contents && [node _implementsDisplay]) {
|
||||
// For layers that do get displayed here, this immediately kicks off the work on the concurrent -[_ASDisplayLayer displayQueue].
|
||||
// At the same time, it creates an associated _ASAsyncTransaction, which we can use to block on display completion. See ASDisplayNode+AsyncDisplay.mm.
|
||||
[layer displayIfNeeded];
|
||||
}
|
||||
|
||||
// Kick off the recursion first, so that all necessary display calls are sent and the displayQueue is full of parallelizable work.
|
||||
for (CALayer *sublayer in layer.sublayers) {
|
||||
recursivelyEnsureDisplayForLayer(sublayer);
|
||||
}
|
||||
|
||||
// As the recursion unwinds, verify each transaction is complete and block if it is not.
|
||||
// While blocking on one transaction, others may be completing concurrently, so it doesn't matter which blocks first.
|
||||
BOOL waitUntilComplete = (!node.shouldBypassEnsureDisplay);
|
||||
if (waitUntilComplete) {
|
||||
for (_ASAsyncTransaction *transaction in [layer.asyncdisplaykit_asyncLayerTransactions copy]) {
|
||||
// Even if none of the layers have had a chance to start display earlier, they will still be allowed to saturate a multicore CPU while blocking main.
|
||||
// This significantly reduces time on the main thread relative to UIKit.
|
||||
[transaction waitUntilComplete];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)recursivelyEnsureDisplay
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
ASDisplayNodeAssert(self.isNodeLoaded, @"Node must have layer or view loaded to use -recursivelyEnsureDisplay");
|
||||
ASDisplayNodeAssert(self.inHierarchy && (self.isLayerBacked || self.view.window != nil), @"Node must be in a hierarchy to use -recursivelyEnsureDisplay");
|
||||
|
||||
CALayer *layer = self.layer;
|
||||
// -layoutIfNeeded is recursive, and even walks up to superlayers to check if they need layout,
|
||||
// so we should call it outside of starting the recursion below. If our own layer is not marked
|
||||
// as dirty, we can assume layout has run on this subtree before.
|
||||
if ([layer needsLayout]) {
|
||||
[layer layoutIfNeeded];
|
||||
}
|
||||
recursivelyEnsureDisplayForLayer(layer);
|
||||
}
|
||||
|
||||
- (void)setShouldBypassEnsureDisplay:(BOOL)shouldBypassEnsureDisplay
|
||||
{
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
_flags.shouldBypassEnsureDisplay = shouldBypassEnsureDisplay;
|
||||
}
|
||||
|
||||
- (BOOL)shouldBypassEnsureDisplay
|
||||
{
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
return _flags.shouldBypassEnsureDisplay;
|
||||
}
|
||||
|
||||
#pragma mark - For Subclasses
|
||||
|
||||
- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize
|
||||
@@ -1484,6 +1542,7 @@ static NSInteger incrementIfFound(NSInteger i) {
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
return _preferredFrameSize;
|
||||
}
|
||||
|
||||
- (UIImage *)placeholderImage
|
||||
{
|
||||
return nil;
|
||||
|
||||
@@ -12,7 +12,10 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol ASEditableTextNodeDelegate;
|
||||
|
||||
/// @abstract ASEditableTextNode implements a node that supports text editing.
|
||||
/**
|
||||
@abstract Implements a node that supports text editing.
|
||||
@discussion Does not support layer backing.
|
||||
*/
|
||||
@interface ASEditableTextNode : ASDisplayNode
|
||||
|
||||
// @abstract The text node's delegate, which must conform to the <ASEditableTextNodeDelegate> protocol.
|
||||
|
||||
@@ -143,6 +143,7 @@
|
||||
_textKitComponents.textView.accessibilityHint = _placeholderTextKitComponents.textStorage.string;
|
||||
configureTextView(_textKitComponents.textView);
|
||||
[self.view addSubview:_textKitComponents.textView];
|
||||
[self _updateDisplayingPlaceholder];
|
||||
}
|
||||
|
||||
- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize
|
||||
@@ -188,6 +189,12 @@
|
||||
_placeholderTextKitComponents.textView.opaque = opaque;
|
||||
}
|
||||
|
||||
- (void)setLayerBacked:(BOOL)layerBacked
|
||||
{
|
||||
ASDisplayNodeAssert(!layerBacked, @"Cannot set layerBacked to YES on ASEditableTextNode – instances must be view-backed in order to ensure touch events can be passed to the internal UITextView during editing.");
|
||||
[super setLayerBacked:layerBacked];
|
||||
}
|
||||
|
||||
#pragma mark - Configuration
|
||||
@synthesize delegate = _delegate;
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#import <AsyncDisplayKit/ASImageNode.h>
|
||||
#import <AsyncDisplayKit/ASImageProtocols.h>
|
||||
|
||||
#import <Photos/Photos.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol ASMultiplexImageNodeDelegate;
|
||||
@@ -100,6 +102,13 @@ typedef NS_ENUM(NSUInteger, ASMultiplexImageNodeErrorCode) {
|
||||
*/
|
||||
@property (nullable, nonatomic, readonly) ASImageIdentifier displayedImageIdentifier;
|
||||
|
||||
/**
|
||||
* @abstract The image manager that this image node should use when requesting images from the Photos framework. If this is `nil` (the default), then `PHImageManager.defaultManager` is used.
|
||||
|
||||
* @see `+[NSURL URLWithAssetLocalIdentifier:targetSize:contentMode:options:]` below.
|
||||
*/
|
||||
@property (nonatomic, strong) PHImageManager *imageManager;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -197,13 +206,34 @@ didFinishDownloadingImageWithIdentifier:(ASImageIdentifier)imageIdentifier
|
||||
* @abstract An image URL for the specified identifier.
|
||||
* @param imageNode The sender.
|
||||
* @param imageIdentifier The identifier for the image that will be downloaded.
|
||||
* @discussion Supported URLs include assets-library, Photo framework URLs (ph://), HTTP, HTTPS, and FTP URLs. If the
|
||||
* image is already available to the data source, it should be provided via <[ASMultiplexImageNodeDataSource
|
||||
* @discussion Supported URLs include HTTP, HTTPS, AssetsLibrary, and FTP URLs as well as Photos framework URLs (see note).
|
||||
*
|
||||
* If the image is already available to the data source, it should be provided via <[ASMultiplexImageNodeDataSource
|
||||
* multiplexImageNode:imageForImageIdentifier:]> instead.
|
||||
* @returns An NSURL for the image identified by `imageIdentifier`, or nil if none is available.
|
||||
* @return An NSURL for the image identified by `imageIdentifier`, or nil if none is available.
|
||||
* @see `+[NSURL URLWithAssetLocalIdentifier:targetSize:contentMode:options:]` below.
|
||||
*/
|
||||
- (nullable NSURL *)multiplexImageNode:(ASMultiplexImageNode *)imageNode URLForImageIdentifier:(ASImageIdentifier)imageIdentifier;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
#pragma mark -
|
||||
|
||||
@interface NSURL (ASPhotosFrameworkURLs)
|
||||
|
||||
/**
|
||||
* @abstract Create an NSURL that specifies an image from the Photos framework.
|
||||
*
|
||||
* @discussion When implementing `-multiplexImageNode:URLForImageIdentifier:`, you can return a URL
|
||||
* created by this method and the image node will attempt to load the image from the Photos framework.
|
||||
* @note The `synchronous` flag in `options` is ignored.
|
||||
* @note The `Opportunistic` delivery mode is not supported and will be treated as `HighQualityFormat`.
|
||||
*/
|
||||
+ (NSURL *)URLWithAssetLocalIdentifier:(NSString *)assetLocalIdentifier
|
||||
targetSize:(CGSize)targetSize
|
||||
contentMode:(PHImageContentMode)contentMode
|
||||
options:(PHImageRequestOptions *)options;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -18,6 +18,7 @@
|
||||
#import "ASBaseDefines.h"
|
||||
#import "ASDisplayNode+Subclasses.h"
|
||||
#import "ASLog.h"
|
||||
#import "ASPhotosFrameworkImageRequest.h"
|
||||
|
||||
#if !AS_IOS8_SDK_OR_LATER
|
||||
#error ASMultiplexImageNode can be used on iOS 7, but must be linked against the iOS 8 SDK.
|
||||
@@ -26,8 +27,6 @@
|
||||
NSString *const ASMultiplexImageNodeErrorDomain = @"ASMultiplexImageNodeErrorDomain";
|
||||
|
||||
static NSString *const kAssetsLibraryURLScheme = @"assets-library";
|
||||
static NSString *const kPHAssetURLScheme = @"ph";
|
||||
static NSString *const kPHAssetURLPrefix = @"ph://";
|
||||
|
||||
/**
|
||||
@abstract Signature for the block to be performed after an image has loaded.
|
||||
@@ -120,14 +119,14 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
|
||||
- (void)_loadALAssetWithIdentifier:(id)imageIdentifier URL:(NSURL *)assetURL completion:(void (^)(UIImage *image, NSError *error))completionBlock;
|
||||
|
||||
/**
|
||||
@abstract Loads the image corresponding to the given assetURL from the Photos framework.
|
||||
@abstract Loads the image corresponding to the given image request from the Photos framework.
|
||||
@param imageIdentifier The identifier for the image to be loaded. May not be nil.
|
||||
@param assetURL The photos framework URL (e.g., "ph://identifier") of the image to load, from PHAsset. May not be nil.
|
||||
@param request The photos image request to load. May not be nil.
|
||||
@param completionBlock The block to be performed when the image has been loaded, if possible. May not be nil.
|
||||
@param image The image that was loaded. May be nil if no image could be downloaded.
|
||||
@param error An error describing why the load failed, if it failed; nil otherwise.
|
||||
*/
|
||||
- (void)_loadPHAssetWithIdentifier:(id)imageIdentifier URL:(NSURL *)assetURL completion:(void (^)(UIImage *image, NSError *error))completionBlock;
|
||||
- (void)_loadPHAssetWithRequest:(ASPhotosFrameworkImageRequest *)request identifier:(id)imageIdentifier completion:(void (^)(UIImage *image, NSError *error))completionBlock;
|
||||
|
||||
/**
|
||||
@abstract Downloads the image corresponding to the given imageIdentifier from the given URL.
|
||||
@@ -158,6 +157,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
|
||||
|
||||
_cache = cache;
|
||||
_downloader = downloader;
|
||||
self.shouldBypassEnsureDisplay = YES;
|
||||
|
||||
return self;
|
||||
}
|
||||
@@ -456,8 +456,8 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
|
||||
}];
|
||||
}
|
||||
// Likewise, if it's a iOS 8 Photo asset, we need to fetch it accordingly.
|
||||
else if (AS_AT_LEAST_IOS8 && [[nextImageURL scheme] isEqualToString:kPHAssetURLScheme]) {
|
||||
[self _loadPHAssetWithIdentifier:nextImageIdentifier URL:nextImageURL completion:^(UIImage *image, NSError *error) {
|
||||
else if (ASPhotosFrameworkImageRequest *request = [ASPhotosFrameworkImageRequest requestWithURL:nextImageURL]) {
|
||||
[self _loadPHAssetWithRequest:request identifier:nextImageIdentifier completion:^(UIImage *image, NSError *error) {
|
||||
ASMultiplexImageNodeCLogDebug(@"[%p] Acquired next image (%@) from Photos Framework", weakSelf, nextImageIdentifier);
|
||||
finishedLoadingBlock(image, nextImageIdentifier, error);
|
||||
}];
|
||||
@@ -511,38 +511,48 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)_loadPHAssetWithIdentifier:(id)imageIdentifier URL:(NSURL *)assetURL completion:(void (^)(UIImage *image, NSError *error))completionBlock
|
||||
- (void)_loadPHAssetWithRequest:(ASPhotosFrameworkImageRequest *)request identifier:(id)imageIdentifier completion:(void (^)(UIImage *image, NSError *error))completionBlock
|
||||
{
|
||||
ASDisplayNodeAssert(AS_AT_LEAST_IOS8, @"PhotosKit is unavailable on iOS 7.");
|
||||
ASDisplayNodeAssertNotNil(imageIdentifier, @"imageIdentifier is required");
|
||||
ASDisplayNodeAssertNotNil(assetURL, @"assetURL is required");
|
||||
ASDisplayNodeAssertNotNil(request, @"request is required");
|
||||
ASDisplayNodeAssertNotNil(completionBlock, @"completionBlock is required");
|
||||
|
||||
// Get the PHAsset itself.
|
||||
ASDisplayNodeAssertTrue([[assetURL absoluteString] hasPrefix:kPHAssetURLPrefix]);
|
||||
NSString *assetIdentifier = [[assetURL absoluteString] substringFromIndex:[kPHAssetURLPrefix length]];
|
||||
PHFetchResult *assetFetchResult = [PHAsset fetchAssetsWithLocalIdentifiers:@[assetIdentifier] options:nil];
|
||||
if ([assetFetchResult count] == 0) {
|
||||
// Error.
|
||||
completionBlock(nil, nil);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the best image we can.
|
||||
PHAsset *imageAsset = [assetFetchResult firstObject];
|
||||
|
||||
PHImageRequestOptions *requestOptions = [[PHImageRequestOptions alloc] init];
|
||||
requestOptions.version = PHImageRequestOptionsVersionCurrent;
|
||||
requestOptions.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
|
||||
requestOptions.resizeMode = PHImageRequestOptionsResizeModeNone;
|
||||
|
||||
[[PHImageManager defaultManager] requestImageForAsset:imageAsset
|
||||
targetSize:CGSizeMake(2048.0, 2048.0) // Ideally we would use PHImageManagerMaximumSize and kill the options, but we get back nil when requesting images of video assets. rdar://18447788
|
||||
contentMode:PHImageContentModeDefault
|
||||
options:requestOptions
|
||||
resultHandler:^(UIImage *image, NSDictionary *info) {
|
||||
completionBlock(image, info[PHImageErrorKey]);
|
||||
}];
|
||||
|
||||
// This is sometimes called on main but there's no reason to stay there
|
||||
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
|
||||
// Get the PHAsset itself.
|
||||
PHFetchResult *assetFetchResult = [PHAsset fetchAssetsWithLocalIdentifiers:@[request.assetIdentifier] options:nil];
|
||||
if ([assetFetchResult count] == 0) {
|
||||
// Error.
|
||||
completionBlock(nil, nil);
|
||||
return;
|
||||
}
|
||||
|
||||
PHAsset *imageAsset = [assetFetchResult firstObject];
|
||||
PHImageRequestOptions *options = [request.options copy];
|
||||
|
||||
// We don't support opportunistic delivery – one request, one image.
|
||||
if (options.deliveryMode == PHImageRequestOptionsDeliveryModeOpportunistic) {
|
||||
options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
|
||||
}
|
||||
|
||||
if (options.deliveryMode == PHImageRequestOptionsDeliveryModeHighQualityFormat) {
|
||||
// Without this flag the result will be delivered on the main queue, which is pointless
|
||||
// But synchronous -> HighQualityFormat so we only use it if high quality format is specified
|
||||
options.synchronous = YES;
|
||||
}
|
||||
|
||||
PHImageManager *imageManager = self.imageManager ?: PHImageManager.defaultManager;
|
||||
[imageManager requestImageForAsset:imageAsset targetSize:request.targetSize contentMode:request.contentMode options:options resultHandler:^(UIImage *image, NSDictionary *info) {
|
||||
if (NSThread.isMainThread) {
|
||||
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
|
||||
completionBlock(image, info[PHImageErrorKey]);
|
||||
});
|
||||
} else {
|
||||
completionBlock(image, info[PHImageErrorKey]);
|
||||
}
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)_fetchImageWithIdentifierFromCache:(id)imageIdentifier URL:(NSURL *)imageURL completion:(void (^)(UIImage *image))completionBlock
|
||||
@@ -642,3 +652,16 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation NSURL (ASPhotosFrameworkURLs)
|
||||
|
||||
+ (NSURL *)URLWithAssetLocalIdentifier:(NSString *)assetLocalIdentifier targetSize:(CGSize)targetSize contentMode:(PHImageContentMode)contentMode options:(PHImageRequestOptions *)options
|
||||
{
|
||||
ASPhotosFrameworkImageRequest *request = [[ASPhotosFrameworkImageRequest alloc] initWithAssetIdentifier:assetLocalIdentifier];
|
||||
request.options = options;
|
||||
request.contentMode = contentMode;
|
||||
request.targetSize = targetSize;
|
||||
return request.url;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -44,6 +44,7 @@
|
||||
_cache = cache;
|
||||
_downloader = downloader;
|
||||
_shouldCacheImage = YES;
|
||||
self.shouldBypassEnsureDisplay = YES;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -28,8 +28,28 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*/
|
||||
@interface ASTableView : UITableView
|
||||
|
||||
@property (nonatomic, weak) id<ASTableViewDataSource> asyncDataSource;
|
||||
@property (nonatomic, weak) id<ASTableViewDelegate> asyncDelegate; // must not be nil
|
||||
@property (nonatomic, weak) id<ASTableViewDataSource> asyncDataSource;
|
||||
|
||||
/**
|
||||
* Initializer.
|
||||
*
|
||||
* @param frame A rectangle specifying the initial location and size of the table view in its superview’s coordinates.
|
||||
* The frame of the table view changes as table cells are added and deleted.
|
||||
*
|
||||
* @param style A constant that specifies the style of the table view. See UITableViewStyle for descriptions of valid constants.
|
||||
*
|
||||
* @param asyncDataFetchingEnabled This option is reserved for future use, and currently a no-op.
|
||||
*
|
||||
* @discussion If asyncDataFetching is enabled, the `ASTableView` will fetch data through `tableView:numberOfRowsInSection:` and
|
||||
* `tableView:nodeForRowAtIndexPath:` in async mode from background thread. Otherwise, the methods will be invoked synchronically
|
||||
* from calling thread.
|
||||
* Enabling asyncDataFetching could avoid blocking main thread for `ASCellNode` allocation, which is frequently reported issue for
|
||||
* large scale data. On another hand, the application code need take the responsibility to avoid data inconsistence. Specifically,
|
||||
* we will lock the data source through `tableViewLockDataSource`, and unlock it by `tableViewUnlockDataSource` after the data fetching.
|
||||
* The application should not update the data source while the data source is locked, to keep data consistence.
|
||||
*/
|
||||
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style asyncDataFetching:(BOOL)asyncDataFetchingEnabled;
|
||||
|
||||
/**
|
||||
* Tuning parameters for a range.
|
||||
@@ -51,26 +71,6 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*/
|
||||
- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType;
|
||||
|
||||
/**
|
||||
* Initializer.
|
||||
*
|
||||
* @param frame A rectangle specifying the initial location and size of the table view in its superview’s coordinates.
|
||||
* The frame of the table view changes as table cells are added and deleted.
|
||||
*
|
||||
* @param style A constant that specifies the style of the table view. See UITableViewStyle for descriptions of valid constants.
|
||||
*
|
||||
* @param asyncDataFetchingEnabled Enable the data fetching in async mode.
|
||||
*
|
||||
* @discussion If asyncDataFetching is enabled, the `ASTableView` will fetch data through `tableView:numberOfRowsInSection:` and
|
||||
* `tableView:nodeForRowAtIndexPath:` in async mode from background thread. Otherwise, the methods will be invoked synchronically
|
||||
* from calling thread.
|
||||
* Enabling asyncDataFetching could avoid blocking main thread for `ASCellNode` allocation, which is frequently reported issue for
|
||||
* large scale data. On another hand, the application code need take the responsibility to avoid data inconsistence. Specifically,
|
||||
* we will lock the data source through `tableViewLockDataSource`, and unlock it by `tableViewUnlockDataSource` after the data fetching.
|
||||
* The application should not update the data source while the data source is locked, to keep data consistence.
|
||||
*/
|
||||
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style asyncDataFetching:(BOOL)asyncDataFetchingEnabled;
|
||||
|
||||
/**
|
||||
* The number of screens left to scroll before the delegate -tableView:beginBatchFetchingWithContext: is called.
|
||||
*
|
||||
@@ -228,6 +228,15 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*/
|
||||
- (ASCellNode *)nodeForRowAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
/**
|
||||
* Similar to -indexPathForCell:.
|
||||
*
|
||||
* @param cellNode a cellNode part of the table view
|
||||
*
|
||||
* @returns an indexPath for this cellNode
|
||||
*/
|
||||
- (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode;
|
||||
|
||||
/**
|
||||
* Similar to -visibleCells.
|
||||
*
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
#import "ASInternalHelpers.h"
|
||||
#import "ASLayout.h"
|
||||
|
||||
// FIXME: Temporary nonsense import until method names are finalized and exposed
|
||||
#import "ASDisplayNode+Subclasses.h"
|
||||
|
||||
//#define LOG(...) NSLog(__VA_ARGS__)
|
||||
#define LOG(...)
|
||||
|
||||
@@ -259,7 +262,8 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) {
|
||||
|
||||
- (void)setDataSource:(id<UITableViewDataSource>)dataSource
|
||||
{
|
||||
ASDisplayNodeAssert(NO, @"ASTableView uses asyncDataSource, not UITableView's dataSource property.");
|
||||
// UIKit can internally generate a call to this method upon changing the asyncDataSource; only assert for non-nil.
|
||||
ASDisplayNodeAssert(dataSource == nil, @"ASTableView uses asyncDataSource, not UITableView's dataSource property.");
|
||||
}
|
||||
|
||||
- (void)setDelegate:(id<UITableViewDelegate>)delegate
|
||||
@@ -345,6 +349,11 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) {
|
||||
return [_dataController nodeAtIndexPath:indexPath];
|
||||
}
|
||||
|
||||
- (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode
|
||||
{
|
||||
return [_dataController indexPathForNode:cellNode];
|
||||
}
|
||||
|
||||
- (NSArray *)visibleNodes
|
||||
{
|
||||
NSArray *indexPaths = [self indexPathsForVisibleRows];
|
||||
@@ -563,6 +572,11 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) {
|
||||
if ([_asyncDelegate respondsToSelector:@selector(tableView:willDisplayNodeForRowAtIndexPath:)]) {
|
||||
[_asyncDelegate tableView:self willDisplayNodeForRowAtIndexPath:indexPath];
|
||||
}
|
||||
|
||||
ASCellNode *cellNode = [self nodeForRowAtIndexPath:indexPath];
|
||||
if (cellNode.neverShowPlaceholders) {
|
||||
[cellNode recursivelyEnsureDisplay];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath*)indexPath
|
||||
|
||||
@@ -15,6 +15,11 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@property (nonatomic, strong, readonly) ASDisplayNode *node;
|
||||
|
||||
// AsyncDisplayKit 2.0 BETA: This property is still being tested, but it allows
|
||||
// blocking as a view controller becomes visible to ensure no placeholders flash onscreen.
|
||||
// Refer to examples/SynchronousConcurrency, AsyncViewController.m
|
||||
@property (nonatomic, assign) BOOL neverShowPlaceholders;
|
||||
|
||||
- (instancetype)initWithNode:(ASDisplayNode *)node;
|
||||
|
||||
@end
|
||||
|
||||
@@ -10,7 +10,13 @@
|
||||
#import "ASAssert.h"
|
||||
#import "ASDimension.h"
|
||||
|
||||
// FIXME: Temporary nonsense import until method names are finalized and exposed
|
||||
#import "ASDisplayNode+Subclasses.h"
|
||||
|
||||
@implementation ASViewController
|
||||
{
|
||||
BOOL _ensureDisplayed;
|
||||
}
|
||||
|
||||
- (instancetype)initWithNode:(ASDisplayNode *)node
|
||||
{
|
||||
@@ -33,15 +39,25 @@
|
||||
|
||||
- (void)viewWillLayoutSubviews
|
||||
{
|
||||
[super viewWillLayoutSubviews];
|
||||
CGSize viewSize = self.view.bounds.size;
|
||||
ASSizeRange constrainedSize = ASSizeRangeMake(viewSize, viewSize);
|
||||
[_node measureWithSizeRange:constrainedSize];
|
||||
[super viewWillLayoutSubviews];
|
||||
}
|
||||
|
||||
- (void)viewDidLayoutSubviews
|
||||
{
|
||||
if (_ensureDisplayed && self.neverShowPlaceholders) {
|
||||
_ensureDisplayed = NO;
|
||||
[self.node recursivelyEnsureDisplay];
|
||||
}
|
||||
[super viewDidLayoutSubviews];
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated
|
||||
{
|
||||
[super viewWillAppear:animated];
|
||||
_ensureDisplayed = YES;
|
||||
[_node recursivelyFetchData];
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#import <AsyncDisplayKit/ASBasicImageDownloader.h>
|
||||
#import <AsyncDisplayKit/ASMultiplexImageNode.h>
|
||||
#import <AsyncDisplayKit/ASNetworkImageNode.h>
|
||||
#import <AsyncDisplayKit/ASPhotosFrameworkImageRequest.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASTableView.h>
|
||||
#import <AsyncDisplayKit/ASCollectionView.h>
|
||||
|
||||
@@ -175,6 +175,8 @@ typedef NSUInteger ASDataControllerAnimationOptions;
|
||||
|
||||
- (nullable ASCellNode *)nodeAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
- (nullable NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode;
|
||||
|
||||
- (NSArray<ASCellNode *> *)nodesAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths;
|
||||
|
||||
- (NSArray<NSArray <ASCellNode *> *> *)completedNodes; // This provides efficient access to the entire _completedNodes multidimensional array.
|
||||
|
||||
@@ -24,7 +24,8 @@ const static NSUInteger kASDataControllerSizingCountPerProcessor = 5;
|
||||
static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
|
||||
@interface ASDataController () {
|
||||
NSMutableArray *_completedNodes; // Main thread only. External data access can immediately query this.
|
||||
NSMutableArray *_externalCompletedNodes; // Main thread only. External data access can immediately query this if available.
|
||||
NSMutableArray *_completedNodes; // Main thread only. External data access can immediately query this if _externalCompletedNodes is unavailable.
|
||||
NSMutableArray *_editingNodes; // Modified on _editingTransactionQueue only. Updates propogated to _completedNodes.
|
||||
|
||||
NSMutableArray *_pendingEditCommandBlocks; // To be run on the main thread. Handles begin/endUpdates tracking.
|
||||
@@ -104,31 +105,27 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
}
|
||||
|
||||
dispatch_group_t layoutGroup = dispatch_group_create();
|
||||
|
||||
ASSizeRange *nodeBoundSizes = (ASSizeRange *)malloc(sizeof(ASSizeRange) * nodes.count);
|
||||
for (NSUInteger j = 0; j < nodes.count && j < indexPaths.count; j += kASDataControllerSizingCountPerProcessor) {
|
||||
NSArray *subIndexPaths = [indexPaths subarrayWithRange:NSMakeRange(j, MIN(kASDataControllerSizingCountPerProcessor, indexPaths.count - j))];
|
||||
NSInteger batchCount = MIN(kASDataControllerSizingCountPerProcessor, indexPaths.count - j);
|
||||
|
||||
//TODO: There should be a fast-path that avoids all of this object creation.
|
||||
NSMutableArray *nodeBoundSizes = [[NSMutableArray alloc] initWithCapacity:kASDataControllerSizingCountPerProcessor];
|
||||
[subIndexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
|
||||
ASSizeRange constrainedSize = [_dataSource dataController:self constrainedSizeForNodeAtIndexPath:indexPath];
|
||||
[nodeBoundSizes addObject:[NSValue valueWithBytes:&constrainedSize objCType:@encode(ASSizeRange)]];
|
||||
}];
|
||||
for (NSUInteger k = j; k < j + batchCount; k++) {
|
||||
nodeBoundSizes[k] = [_dataSource dataController:self constrainedSizeForNodeAtIndexPath:indexPaths[k]];
|
||||
}
|
||||
|
||||
dispatch_group_async(layoutGroup, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
[subIndexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
|
||||
ASCellNode *node = nodes[j + idx];
|
||||
ASSizeRange constrainedSize;
|
||||
[nodeBoundSizes[idx] getValue:&constrainedSize];
|
||||
for (NSUInteger k = j; k < j + batchCount; k++) {
|
||||
ASCellNode *node = nodes[k];
|
||||
ASSizeRange constrainedSize = nodeBoundSizes[k];
|
||||
[node measureWithSizeRange:constrainedSize];
|
||||
node.frame = CGRectMake(0.0f, 0.0f, node.calculatedSize.width, node.calculatedSize.height);
|
||||
}];
|
||||
node.frame = CGRectMake(0, 0, node.calculatedSize.width, node.calculatedSize.height);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Block the _editingTransactionQueue from executing a new edit transaction until layout is done & _editingNodes array is updated.
|
||||
dispatch_group_wait(layoutGroup, DISPATCH_TIME_FOREVER);
|
||||
|
||||
free(nodeBoundSizes);
|
||||
// Insert finished nodes into data storage
|
||||
[self _insertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
|
||||
}
|
||||
@@ -347,6 +344,10 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
|
||||
[_editingTransactionQueue addOperationWithBlock:^{
|
||||
ASDisplayNodePerformBlockOnMainThread(^{
|
||||
// Deep copy _completedNodes to _externalCompletedNodes.
|
||||
// Any external queries from now on will be done on _externalCompletedNodes, to guarantee data consistency with the delegate.
|
||||
_externalCompletedNodes = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(_completedNodes);
|
||||
|
||||
LOG(@"endUpdatesWithCompletion - begin updates call to delegate");
|
||||
[_delegate dataControllerBeginUpdates:self];
|
||||
});
|
||||
@@ -363,6 +364,9 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
|
||||
[_editingTransactionQueue addOperationWithBlock:^{
|
||||
ASDisplayNodePerformBlockOnMainThread(^{
|
||||
// Now that the transaction is done, _completedNodes can be accessed externally again.
|
||||
_externalCompletedNodes = nil;
|
||||
|
||||
LOG(@"endUpdatesWithCompletion - calling delegate end");
|
||||
[_delegate dataController:self endUpdatesAnimated:animated completion:completion];
|
||||
});
|
||||
@@ -617,31 +621,51 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
- (NSUInteger)numberOfSections
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
return [_completedNodes count];
|
||||
return [[self completedNodes] count];
|
||||
}
|
||||
|
||||
- (NSUInteger)numberOfRowsInSection:(NSUInteger)section
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
return [_completedNodes[section] count];
|
||||
return [[self completedNodes][section] count];
|
||||
}
|
||||
|
||||
- (ASCellNode *)nodeAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
return _completedNodes[indexPath.section][indexPath.row];
|
||||
return [self completedNodes][indexPath.section][indexPath.row];
|
||||
}
|
||||
|
||||
- (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode;
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
NSArray *nodes = [self completedNodes];
|
||||
NSUInteger numberOfNodes = nodes.count;
|
||||
|
||||
// Loop through each section to look for the cellNode
|
||||
for (NSUInteger i = 0; i < numberOfNodes; i++) {
|
||||
NSArray *sectionNodes = nodes[i];
|
||||
NSUInteger cellIndex = [sectionNodes indexOfObjectIdenticalTo:cellNode];
|
||||
if (cellIndex != NSNotFound) {
|
||||
return [NSIndexPath indexPathForRow:cellIndex inSection:i];
|
||||
}
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSArray *)nodesAtIndexPaths:(NSArray *)indexPaths
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
return ASFindElementsInMultidimensionalArrayAtIndexPaths(_completedNodes, [indexPaths sortedArrayUsingSelector:@selector(compare:)]);
|
||||
return ASFindElementsInMultidimensionalArrayAtIndexPaths((NSMutableArray *)[self completedNodes], [indexPaths sortedArrayUsingSelector:@selector(compare:)]);
|
||||
}
|
||||
|
||||
/// Returns nodes that can be queried externally. _externalCompletedNodes is used if available, _completedNodes otherwise.
|
||||
- (NSArray *)completedNodes
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
return _completedNodes;
|
||||
return _externalCompletedNodes != nil ? _externalCompletedNodes : _completedNodes;
|
||||
}
|
||||
|
||||
#pragma mark - Dealloc
|
||||
|
||||
66
AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.h
Normal file
66
AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.h
Normal file
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// ASPhotosFrameworkImageRequest.h
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Adlai Holler on 9/25/15.
|
||||
// Copyright © 2015 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <Photos/Photos.h>
|
||||
|
||||
// NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
extern NSString *const ASPhotosURLScheme;
|
||||
|
||||
/**
|
||||
@abstract Use ASPhotosFrameworkImageRequest to encapsulate all the information needed to request an image from
|
||||
the Photos framework and store it in a URL.
|
||||
*/
|
||||
@interface ASPhotosFrameworkImageRequest : NSObject <NSCopying>
|
||||
|
||||
- (instancetype)initWithAssetIdentifier:(NSString *)assetIdentifier NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/**
|
||||
@return A new image request deserialized from `url`, or nil if `url` is not a valid photos URL.
|
||||
*/
|
||||
+ (/*nullable*/ ASPhotosFrameworkImageRequest *)requestWithURL:(NSURL *)url;
|
||||
|
||||
/**
|
||||
@abstract The asset identifier for this image request provided during initialization.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSString *assetIdentifier;
|
||||
|
||||
/**
|
||||
@abstract The target size for this image request. Defaults to `PHImageManagerMaximumSize`.
|
||||
*/
|
||||
@property (nonatomic) CGSize targetSize;
|
||||
|
||||
/**
|
||||
@abstract The content mode for this image request. Defaults to `PHImageContentModeDefault`.
|
||||
|
||||
@see `PHImageManager`
|
||||
*/
|
||||
@property (nonatomic) PHImageContentMode contentMode;
|
||||
|
||||
/**
|
||||
@abstract The options specified for this request. Default value is the result of `[PHImageRequestOptions new]`.
|
||||
|
||||
@discussion Some properties of this object are ignored when converting this request into a URL.
|
||||
As of iOS SDK 9.0, these properties are `progressHandler` and `synchronous`.
|
||||
*/
|
||||
@property (nonatomic, strong) PHImageRequestOptions *options;
|
||||
|
||||
/**
|
||||
@return A new URL converted from this request.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSURL *url;
|
||||
|
||||
/**
|
||||
@return `YES` if `object` is an equivalent image request, `NO` otherwise.
|
||||
*/
|
||||
- (BOOL)isEqual:(id)object;
|
||||
|
||||
@end
|
||||
|
||||
// NS_ASSUME_NONNULL_END
|
||||
161
AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.m
Normal file
161
AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.m
Normal file
@@ -0,0 +1,161 @@
|
||||
//
|
||||
// ASPhotosFrameworkImageRequest.m
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Adlai Holler on 9/25/15.
|
||||
// Copyright © 2015 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ASPhotosFrameworkImageRequest.h"
|
||||
#import "ASBaseDefines.h"
|
||||
#import "ASAvailability.h"
|
||||
|
||||
NSString *const ASPhotosURLScheme = @"ph";
|
||||
|
||||
static NSString *const _ASPhotosURLQueryKeyWidth = @"width";
|
||||
static NSString *const _ASPhotosURLQueryKeyHeight = @"height";
|
||||
|
||||
// value is PHImageContentMode value
|
||||
static NSString *const _ASPhotosURLQueryKeyContentMode = @"contentmode";
|
||||
|
||||
// value is PHImageRequestOptionsResizeMode value
|
||||
static NSString *const _ASPhotosURLQueryKeyResizeMode = @"resizemode";
|
||||
|
||||
// value is PHImageRequestOptionsDeliveryMode value
|
||||
static NSString *const _ASPhotosURLQueryKeyDeliveryMode = @"deliverymode";
|
||||
|
||||
// value is PHImageRequestOptionsVersion value
|
||||
static NSString *const _ASPhotosURLQueryKeyVersion = @"version";
|
||||
|
||||
// value is 0 or 1
|
||||
static NSString *const _ASPhotosURLQueryKeyAllowNetworkAccess = @"network";
|
||||
|
||||
static NSString *const _ASPhotosURLQueryKeyCropOriginX = @"crop_x";
|
||||
static NSString *const _ASPhotosURLQueryKeyCropOriginY = @"crop_y";
|
||||
static NSString *const _ASPhotosURLQueryKeyCropWidth = @"crop_w";
|
||||
static NSString *const _ASPhotosURLQueryKeyCropHeight = @"crop_h";
|
||||
|
||||
@implementation ASPhotosFrameworkImageRequest
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
ASDISPLAYNODE_NOT_DESIGNATED_INITIALIZER();
|
||||
self = [self initWithAssetIdentifier:@""];
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (instancetype)initWithAssetIdentifier:(NSString *)assetIdentifier
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_assetIdentifier = assetIdentifier;
|
||||
_options = [PHImageRequestOptions new];
|
||||
_contentMode = PHImageContentModeDefault;
|
||||
_targetSize = PHImageManagerMaximumSize;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark NSCopying
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
ASPhotosFrameworkImageRequest *copy = [[ASPhotosFrameworkImageRequest alloc] initWithAssetIdentifier:self.assetIdentifier];
|
||||
copy.options = [self.options copy];
|
||||
copy.targetSize = self.targetSize;
|
||||
copy.contentMode = self.contentMode;
|
||||
return copy;
|
||||
}
|
||||
|
||||
#pragma mark Converting to URL
|
||||
|
||||
- (NSURL *)url
|
||||
{
|
||||
NSURLComponents *comp = [NSURLComponents new];
|
||||
comp.scheme = ASPhotosURLScheme;
|
||||
comp.host = _assetIdentifier;
|
||||
NSMutableArray *queryItems = [NSMutableArray arrayWithObjects:
|
||||
[NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyWidth value:@(_targetSize.width).stringValue],
|
||||
[NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyHeight value:@(_targetSize.height).stringValue],
|
||||
[NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyVersion value:@(_options.version).stringValue],
|
||||
[NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyContentMode value:@(_contentMode).stringValue],
|
||||
[NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyAllowNetworkAccess value:@(_options.networkAccessAllowed).stringValue],
|
||||
[NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyResizeMode value:@(_options.resizeMode).stringValue],
|
||||
[NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyDeliveryMode value:@(_options.deliveryMode).stringValue]
|
||||
, nil];
|
||||
|
||||
CGRect cropRect = _options.normalizedCropRect;
|
||||
if (!CGRectIsEmpty(cropRect)) {
|
||||
[queryItems addObjectsFromArray:@[
|
||||
[NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyCropOriginX value:@(cropRect.origin.x).stringValue],
|
||||
[NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyCropOriginY value:@(cropRect.origin.y).stringValue],
|
||||
[NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyCropWidth value:@(cropRect.size.width).stringValue],
|
||||
[NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyCropHeight value:@(cropRect.size.height).stringValue]
|
||||
]];
|
||||
}
|
||||
comp.queryItems = queryItems;
|
||||
return comp.URL;
|
||||
}
|
||||
|
||||
#pragma mark Converting from URL
|
||||
|
||||
+ (ASPhotosFrameworkImageRequest *)requestWithURL:(NSURL *)url
|
||||
{
|
||||
// not a photos URL or iOS < 8
|
||||
if (![url.scheme isEqualToString:ASPhotosURLScheme] || !AS_AT_LEAST_IOS8) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSURLComponents *comp = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
|
||||
|
||||
ASPhotosFrameworkImageRequest *request = [[ASPhotosFrameworkImageRequest alloc] initWithAssetIdentifier:url.host];
|
||||
|
||||
CGRect cropRect = CGRectZero;
|
||||
CGSize targetSize = PHImageManagerMaximumSize;
|
||||
for (NSURLQueryItem *item in comp.queryItems) {
|
||||
if ([_ASPhotosURLQueryKeyAllowNetworkAccess isEqualToString:item.name]) {
|
||||
request.options.networkAccessAllowed = item.value.boolValue;
|
||||
} else if ([_ASPhotosURLQueryKeyWidth isEqualToString:item.name]) {
|
||||
targetSize.width = item.value.doubleValue;
|
||||
} else if ([_ASPhotosURLQueryKeyHeight isEqualToString:item.name]) {
|
||||
targetSize.height = item.value.doubleValue;
|
||||
} else if ([_ASPhotosURLQueryKeyContentMode isEqualToString:item.name]) {
|
||||
request.contentMode = (PHImageContentMode)item.value.integerValue;
|
||||
} else if ([_ASPhotosURLQueryKeyVersion isEqualToString:item.name]) {
|
||||
request.options.version = (PHImageRequestOptionsVersion)item.value.integerValue;
|
||||
} else if ([_ASPhotosURLQueryKeyCropOriginX isEqualToString:item.name]) {
|
||||
cropRect.origin.x = item.value.doubleValue;
|
||||
} else if ([_ASPhotosURLQueryKeyCropOriginY isEqualToString:item.name]) {
|
||||
cropRect.origin.y = item.value.doubleValue;
|
||||
} else if ([_ASPhotosURLQueryKeyCropWidth isEqualToString:item.name]) {
|
||||
cropRect.size.width = item.value.doubleValue;
|
||||
} else if ([_ASPhotosURLQueryKeyCropHeight isEqualToString:item.name]) {
|
||||
cropRect.size.height = item.value.doubleValue;
|
||||
} else if ([_ASPhotosURLQueryKeyResizeMode isEqualToString:item.name]) {
|
||||
request.options.resizeMode = (PHImageRequestOptionsResizeMode)item.value.integerValue;
|
||||
} else if ([_ASPhotosURLQueryKeyDeliveryMode isEqualToString:item.name]) {
|
||||
request.options.deliveryMode = (PHImageRequestOptionsDeliveryMode)item.value.integerValue;
|
||||
}
|
||||
}
|
||||
request.targetSize = targetSize;
|
||||
request.options.normalizedCropRect = cropRect;
|
||||
return request;
|
||||
}
|
||||
|
||||
#pragma mark NSObject
|
||||
|
||||
- (BOOL)isEqual:(id)object
|
||||
{
|
||||
if (![object isKindOfClass:ASPhotosFrameworkImageRequest.class]) {
|
||||
return NO;
|
||||
}
|
||||
ASPhotosFrameworkImageRequest *other = object;
|
||||
return [other.assetIdentifier isEqualToString:self.assetIdentifier] &&
|
||||
other.contentMode == self.contentMode &&
|
||||
CGSizeEqualToSize(other.targetSize, self.targetSize) &&
|
||||
CGRectEqualToRect(other.options.normalizedCropRect, self.options.normalizedCropRect) &&
|
||||
other.options.resizeMode == self.options.resizeMode &&
|
||||
other.options.version == self.options.version;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -26,6 +26,7 @@ typedef NS_ENUM(NSUInteger, ASAsyncTransactionState) {
|
||||
ASAsyncTransactionStateOpen = 0,
|
||||
ASAsyncTransactionStateCommitted,
|
||||
ASAsyncTransactionStateCanceled,
|
||||
ASAsyncTransactionStateComplete
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -54,6 +55,13 @@ typedef NS_ENUM(NSUInteger, ASAsyncTransactionState) {
|
||||
- (id)initWithCallbackQueue:(dispatch_queue_t)callbackQueue
|
||||
completionBlock:(asyncdisplaykit_async_transaction_completion_block_t)completionBlock;
|
||||
|
||||
/**
|
||||
@summary Block the main thread until the transaction is complete, including callbacks.
|
||||
|
||||
@desc This must be called on the main thread.
|
||||
*/
|
||||
- (void)waitUntilComplete;
|
||||
|
||||
/**
|
||||
The dispatch queue that the completion blocks will be called on.
|
||||
*/
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
#import "_ASAsyncTransaction.h"
|
||||
|
||||
#import "_ASAsyncTransactionGroup.h"
|
||||
#import "ASAssert.h"
|
||||
|
||||
@interface ASDisplayNodeAsyncTransactionOperation : NSObject
|
||||
@@ -40,6 +40,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"<ASDisplayNodeAsyncTransactionOperation: %p - value = %@", self, self.value];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _ASAsyncTransaction
|
||||
@@ -140,7 +145,7 @@
|
||||
ASDisplayNodeAssertMainThread();
|
||||
ASDisplayNodeAssert(_state == ASAsyncTransactionStateOpen, @"You cannot double-commit a transaction");
|
||||
_state = ASAsyncTransactionStateCommitted;
|
||||
|
||||
|
||||
if ([_operations count] == 0) {
|
||||
// Fast path: if a transaction was opened, but no operations were added, execute completion block synchronously.
|
||||
if (_completionBlock) {
|
||||
@@ -148,18 +153,59 @@
|
||||
}
|
||||
} else {
|
||||
ASDisplayNodeAssert(_group != NULL, @"If there are operations, dispatch group should have been created");
|
||||
|
||||
dispatch_group_notify(_group, _callbackQueue, ^{
|
||||
BOOL isCanceled = (_state == ASAsyncTransactionStateCanceled);
|
||||
for (ASDisplayNodeAsyncTransactionOperation *operation in _operations) {
|
||||
[operation callAndReleaseCompletionBlock:isCanceled];
|
||||
}
|
||||
if (_completionBlock) {
|
||||
_completionBlock(self, isCanceled);
|
||||
}
|
||||
// _callbackQueue is the main queue in current practice (also asserted in -waitUntilComplete).
|
||||
// This code should be reviewed before taking on significantly different use cases.
|
||||
ASDisplayNodeAssertMainThread();
|
||||
[self completeTransaction];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (void)completeTransaction
|
||||
{
|
||||
if (_state != ASAsyncTransactionStateComplete) {
|
||||
BOOL isCanceled = (_state == ASAsyncTransactionStateCanceled);
|
||||
for (ASDisplayNodeAsyncTransactionOperation *operation in _operations) {
|
||||
[operation callAndReleaseCompletionBlock:isCanceled];
|
||||
}
|
||||
|
||||
// Always set _state to Complete, even if we were cancelled, to block any extraneous
|
||||
// calls to this method that may have been scheduled for the next runloop
|
||||
// (e.g. if we needed to force one in this runloop with -waitUntilComplete, but another was already scheduled)
|
||||
_state = ASAsyncTransactionStateComplete;
|
||||
|
||||
if (_completionBlock) {
|
||||
_completionBlock(self, isCanceled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)waitUntilComplete
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
if (_state != ASAsyncTransactionStateComplete) {
|
||||
if (_group) {
|
||||
ASDisplayNodeAssertTrue(_callbackQueue == dispatch_get_main_queue());
|
||||
dispatch_group_wait(_group, DISPATCH_TIME_FOREVER);
|
||||
|
||||
// At this point, the asynchronous operation may have completed, but the runloop
|
||||
// observer has not committed the batch of transactions we belong to. It's important to
|
||||
// commit ourselves via the group to avoid double-committing the transaction.
|
||||
// This is only necessary when forcing display work to complete before allowing the runloop
|
||||
// to continue, e.g. in the implementation of -[ASDisplayNode recursivelyEnsureDisplay].
|
||||
if (_state == ASAsyncTransactionStateOpen) {
|
||||
[_ASAsyncTransactionGroup commit];
|
||||
ASDisplayNodeAssert(_state != ASAsyncTransactionStateOpen, @"Transaction should not be open after committing group");
|
||||
}
|
||||
// If we needed to commit the group above, -completeTransaction may have already been run.
|
||||
// It is designed to accomodate this by checking _state to ensure it is not complete.
|
||||
[self completeTransaction];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Helper Methods
|
||||
|
||||
@@ -174,4 +220,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"<_ASAsyncTransaction: %p - _state = %lu, _group = %@, _operations = %@>", self, (unsigned long)_state, _group, _operations];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
@interface _ASAsyncTransactionGroup : NSObject
|
||||
/// The main transaction group is scheduled to commit on every tick of the main runloop.
|
||||
+ (instancetype)mainTransactionGroup;
|
||||
+ (void)commit;
|
||||
|
||||
/// Add a transaction container to be committed.
|
||||
/// @param containerLayer A layer containing a transaction to be commited. May or may not be a container layer.
|
||||
|
||||
@@ -95,6 +95,11 @@ static void _transactionGroupRunLoopObserverCallback(CFRunLoopObserverRef observ
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)commit
|
||||
{
|
||||
[[_ASAsyncTransactionGroup mainTransactionGroup] commit];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static void _transactionGroupRunLoopObserverCallback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
|
||||
|
||||
@@ -98,6 +98,7 @@ static Class gDefaultLayoutOptionsClass = nil;
|
||||
self.spacingBefore = layoutOptions.spacingBefore;
|
||||
self.flexGrow = layoutOptions.flexGrow;
|
||||
self.flexShrink = layoutOptions.flexShrink;
|
||||
self.alignSelf = layoutOptions.alignSelf;
|
||||
|
||||
self.ascender = layoutOptions.ascender;
|
||||
self.descender = layoutOptions.descender;
|
||||
|
||||
@@ -58,18 +58,14 @@
|
||||
[sublayouts addObject:sublayout];
|
||||
}
|
||||
|
||||
if (isnan(size.width) || size.width >= FLT_MAX - FLT_EPSILON) {
|
||||
size.width = constrainedSize.min.width;
|
||||
for (ASLayout *sublayout in sublayouts) {
|
||||
size.width = MAX(size.width, sublayout.position.x + sublayout.size.width);
|
||||
}
|
||||
size.width = constrainedSize.min.width;
|
||||
for (ASLayout *sublayout in sublayouts) {
|
||||
size.width = MAX(size.width, sublayout.position.x + sublayout.size.width);
|
||||
}
|
||||
|
||||
if (isnan(size.height) || size.height >= FLT_MAX - FLT_EPSILON) {
|
||||
size.height = constrainedSize.min.height;
|
||||
for (ASLayout *sublayout in sublayouts) {
|
||||
size.height = MAX(size.height, sublayout.position.y + sublayout.size.height);
|
||||
}
|
||||
size.height = constrainedSize.min.height;
|
||||
for (ASLayout *sublayout in sublayouts) {
|
||||
size.height = MAX(size.height, sublayout.position.y + sublayout.size.height);
|
||||
}
|
||||
|
||||
return [ASLayout layoutWithLayoutableObject:self
|
||||
|
||||
@@ -94,6 +94,8 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync,
|
||||
// Capture these outside the display block so they are retained.
|
||||
UIColor *backgroundColor = self.backgroundColor;
|
||||
CGRect bounds = self.bounds;
|
||||
CGFloat cornerRadius = self.cornerRadius;
|
||||
BOOL clipsToBounds = self.clipsToBounds;
|
||||
|
||||
CGRect frame;
|
||||
|
||||
@@ -129,7 +131,7 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync,
|
||||
CGContextTranslateCTM(context, frame.origin.x, frame.origin.y);
|
||||
|
||||
//support cornerRadius
|
||||
if (rasterizingFromAscendent && self.cornerRadius && self.clipsToBounds) {
|
||||
if (rasterizingFromAscendent && cornerRadius && clipsToBounds) {
|
||||
[[UIBezierPath bezierPathWithRoundedRect:bounds cornerRadius:self.cornerRadius] addClip];
|
||||
}
|
||||
|
||||
@@ -308,16 +310,14 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync,
|
||||
return BOOL(displaySentinelValue != displaySentinel.value);
|
||||
};
|
||||
|
||||
// If we're participating in an ancestor's asyncTransaction, find it here
|
||||
ASDisplayNodeAssert(_layer, @"Expect _layer to be not nil");
|
||||
CALayer *containerLayer = _layer.asyncdisplaykit_parentTransactionContainer ?: _layer;
|
||||
_ASAsyncTransaction *transaction = containerLayer.asyncdisplaykit_asyncTransaction;
|
||||
|
||||
// Set up displayBlock to call either display or draw on the delegate and return a UIImage contents
|
||||
asyncdisplaykit_async_transaction_operation_block_t displayBlock = [self _displayBlockWithAsynchronous:asynchronously isCancelledBlock:isCancelledBlock rasterizing:NO];
|
||||
|
||||
if (!displayBlock) {
|
||||
return;
|
||||
}
|
||||
|
||||
ASDisplayNodeAssert(_layer, @"Expect _layer to be not nil");
|
||||
|
||||
// This block is called back on the main thread after rendering at the completion of the current async transaction, or immediately if !asynchronously
|
||||
asyncdisplaykit_async_transaction_operation_completion_block_t completionBlock = ^(id<NSObject> value, BOOL canceled){
|
||||
@@ -335,16 +335,27 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync,
|
||||
}
|
||||
};
|
||||
|
||||
if (displayBlock != NULL) {
|
||||
// Call willDisplay immediately in either case
|
||||
[self willDisplayAsyncLayer:self.asyncLayer];
|
||||
// Call willDisplay immediately in either case
|
||||
[self willDisplayAsyncLayer:self.asyncLayer];
|
||||
|
||||
if (asynchronously) {
|
||||
[transaction addOperationWithBlock:displayBlock queue:[_ASDisplayLayer displayQueue] completion:completionBlock];
|
||||
} else {
|
||||
UIImage *contents = (UIImage *)displayBlock();
|
||||
completionBlock(contents, NO);
|
||||
}
|
||||
if (asynchronously) {
|
||||
// Async rendering operations are contained by a transaction, which allows them to proceed and concurrently
|
||||
// while synchronizing the final application of the results to the layer's contents property (completionBlock).
|
||||
|
||||
// First, look to see if we are expected to join a parent's transaction container.
|
||||
CALayer *containerLayer = _layer.asyncdisplaykit_parentTransactionContainer ?: _layer;
|
||||
|
||||
// In the case that a transaction does not yet exist (such as for an individual node outside of a container),
|
||||
// this call will allocate the transaction and add it to _ASAsyncTransactionGroup.
|
||||
// It will automatically commit the transaction at the end of the runloop.
|
||||
_ASAsyncTransaction *transaction = containerLayer.asyncdisplaykit_asyncTransaction;
|
||||
|
||||
// Adding this displayBlock operation to the transaction will start it IMMEDIATELY.
|
||||
// The only function of the transaction commit is to gate the calling of the completionBlock.
|
||||
[transaction addOperationWithBlock:displayBlock queue:[_ASDisplayLayer displayQueue] completion:completionBlock];
|
||||
} else {
|
||||
UIImage *contents = (UIImage *)displayBlock();
|
||||
completionBlock(contents, NO);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
#define _messageToLayer(layerSelector) __loaded ? [_layer layerSelector] : [self.pendingViewState layerSelector]
|
||||
|
||||
/**
|
||||
* This category implements certainly frequently-used properties and methods of UIView and CALayer so that ASDisplayNode clients can just call the view/layer methods on the node,
|
||||
* This category implements certain frequently-used properties and methods of UIView and CALayer so that ASDisplayNode clients can just call the view/layer methods on the node,
|
||||
* with minimal loss in performance. Unlike UIView and CALayer methods, these can be called from a non-main thread until the view or layer is created.
|
||||
* This allows text sizing in -calculateSizeThatFits: (essentially a simplified layout) to happen off the main thread
|
||||
* without any CALayer or UIView actually existing while still being able to set and read properties from ASDisplayNode instances.
|
||||
@@ -122,7 +122,7 @@
|
||||
// Frame is only defined when transform is identity.
|
||||
#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.
|
||||
ASDisplayNodeAssert(CATransform3DIsIdentity(self.transform), @"Must be an identity transform");
|
||||
ASDisplayNodeAssert(CATransform3DIsIdentity(self.transform), @"-[ASDisplayNode frame] - self.transform must be identity in order to use the frame property. (From Apple's UIView documentation: If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.)");
|
||||
#endif
|
||||
|
||||
CGPoint position = self.position;
|
||||
@@ -140,7 +140,7 @@
|
||||
// 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.
|
||||
ASDisplayNodeAssert(CATransform3DIsIdentity(self.transform), @"Must be an identity transform");
|
||||
ASDisplayNodeAssert(CATransform3DIsIdentity(self.transform), @"-[ASDisplayNode setFrame:] - self.transform must be identity in order to set the frame property. (From Apple's UIView documentation: If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.)");
|
||||
#endif
|
||||
|
||||
BOOL useLayer = (_layer && ASDisplayNodeThreadIsMain());
|
||||
|
||||
@@ -80,6 +80,7 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) {
|
||||
unsigned layerBacked:1;
|
||||
unsigned displaysAsynchronously:1;
|
||||
unsigned shouldRasterizeDescendants:1;
|
||||
unsigned shouldBypassEnsureDisplay:1;
|
||||
unsigned displaySuspended:1;
|
||||
|
||||
// whether custom drawing is enabled
|
||||
|
||||
Reference in New Issue
Block a user