mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-09-10 14:35:42 +00:00
Merge branch 'master' into update-objc
This commit is contained in:
commit
b1e60a95e6
@ -331,6 +331,40 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
|||||||
|
|
||||||
#pragma mark - Core
|
#pragma mark - Core
|
||||||
|
|
||||||
|
- (void)__tearDown:(BOOL)tearDown subnodesOfNode:(ASDisplayNode *)node
|
||||||
|
{
|
||||||
|
for (ASDisplayNode *subnode in node.subnodes) {
|
||||||
|
if (tearDown) {
|
||||||
|
[subnode __unloadNode];
|
||||||
|
} else {
|
||||||
|
[subnode __loadNode];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)__unloadNode
|
||||||
|
{
|
||||||
|
ASDisplayNodeAssertThreadAffinity(self);
|
||||||
|
ASDN::MutexLocker l(_propertyLock);
|
||||||
|
|
||||||
|
if (_flags.layerBacked)
|
||||||
|
_pendingViewState = [_ASPendingState pendingViewStateFromLayer:_layer];
|
||||||
|
else
|
||||||
|
_pendingViewState = [_ASPendingState pendingViewStateFromView:_view];
|
||||||
|
|
||||||
|
[_view removeFromSuperview];
|
||||||
|
_view = nil;
|
||||||
|
if (_flags.layerBacked)
|
||||||
|
_layer.delegate = nil;
|
||||||
|
[_layer removeFromSuperlayer];
|
||||||
|
_layer = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)__loadNode
|
||||||
|
{
|
||||||
|
[self layer];
|
||||||
|
}
|
||||||
|
|
||||||
- (ASDisplayNode *)__rasterizedContainerNode
|
- (ASDisplayNode *)__rasterizedContainerNode
|
||||||
{
|
{
|
||||||
ASDisplayNode *node = self.supernode;
|
ASDisplayNode *node = self.supernode;
|
||||||
@ -624,6 +658,17 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
_flags.shouldRasterizeDescendants = flag;
|
_flags.shouldRasterizeDescendants = flag;
|
||||||
|
|
||||||
|
if (self.isNodeLoaded) {
|
||||||
|
//recursively tear down or build up subnodes
|
||||||
|
[self recursivelyClearContents];
|
||||||
|
[self __tearDown:flag subnodesOfNode:self];
|
||||||
|
if (flag == NO) {
|
||||||
|
[self _addSubnodeViewsAndLayers];
|
||||||
|
}
|
||||||
|
|
||||||
|
[self recursivelyDisplayImmediately];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (CGFloat)contentsScaleForDisplay
|
- (CGFloat)contentsScaleForDisplay
|
||||||
@ -653,6 +698,15 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
|||||||
[[self asyncLayer] displayImmediately];
|
[[self asyncLayer] displayImmediately];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)recursivelyDisplayImmediately
|
||||||
|
{
|
||||||
|
ASDN::MutexLocker l(_propertyLock);
|
||||||
|
for (ASDisplayNode *child in _subnodes) {
|
||||||
|
[child recursivelyDisplayImmediately];
|
||||||
|
}
|
||||||
|
[self displayImmediately];
|
||||||
|
}
|
||||||
|
|
||||||
- (void)__setNeedsLayout
|
- (void)__setNeedsLayout
|
||||||
{
|
{
|
||||||
ASDisplayNodeAssertThreadAffinity(self);
|
ASDisplayNodeAssertThreadAffinity(self);
|
||||||
@ -690,6 +744,29 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)__setSafeFrame:(CGRect)rect
|
||||||
|
{
|
||||||
|
ASDisplayNodeAssertThreadAffinity(self);
|
||||||
|
ASDN::MutexLocker l(_propertyLock);
|
||||||
|
|
||||||
|
BOOL useLayer = (_layer && ASDisplayNodeThreadIsMain());
|
||||||
|
|
||||||
|
CGPoint origin = (useLayer ? _layer.bounds.origin : self.bounds.origin);
|
||||||
|
CGPoint anchorPoint = (useLayer ? _layer.anchorPoint : self.anchorPoint);
|
||||||
|
|
||||||
|
CGRect bounds = (CGRect){ origin, rect.size };
|
||||||
|
CGPoint position = CGPointMake(rect.origin.x + rect.size.width * anchorPoint.x,
|
||||||
|
rect.origin.y + rect.size.height * anchorPoint.y);
|
||||||
|
|
||||||
|
if (useLayer) {
|
||||||
|
_layer.bounds = bounds;
|
||||||
|
_layer.position = position;
|
||||||
|
} else {
|
||||||
|
self.bounds = bounds;
|
||||||
|
self.position = position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// These private methods ensure that subclasses are not required to call super in order for _renderingSubnodes to be properly managed.
|
// These private methods ensure that subclasses are not required to call super in order for _renderingSubnodes to be properly managed.
|
||||||
|
|
||||||
- (void)__layout
|
- (void)__layout
|
||||||
@ -1637,10 +1714,10 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer)
|
|||||||
// Assume that _layout was flattened and is 1-level deep.
|
// Assume that _layout was flattened and is 1-level deep.
|
||||||
for (ASLayout *subnodeLayout in _layout.sublayouts) {
|
for (ASLayout *subnodeLayout in _layout.sublayouts) {
|
||||||
ASDisplayNodeAssert([_subnodes containsObject:subnodeLayout.layoutableObject], @"Cached sublayouts must only contain subnodes' layout.");
|
ASDisplayNodeAssert([_subnodes containsObject:subnodeLayout.layoutableObject], @"Cached sublayouts must only contain subnodes' layout.");
|
||||||
((ASDisplayNode *)subnodeLayout.layoutableObject).frame = CGRectMake(subnodeLayout.position.x,
|
[((ASDisplayNode *)subnodeLayout.layoutableObject) __setSafeFrame:CGRectMake(subnodeLayout.position.x,
|
||||||
subnodeLayout.position.y,
|
subnodeLayout.position.y,
|
||||||
subnodeLayout.size.width,
|
subnodeLayout.size.width,
|
||||||
subnodeLayout.size.height);
|
subnodeLayout.size.height)];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,7 +217,7 @@
|
|||||||
|
|
||||||
- (void)setTypingAttributes:(NSDictionary *)typingAttributes
|
- (void)setTypingAttributes:(NSDictionary *)typingAttributes
|
||||||
{
|
{
|
||||||
if (_typingAttributes == typingAttributes)
|
if (ASObjectIsEqual(typingAttributes, _typingAttributes))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_typingAttributes = [typingAttributes copy];
|
_typingAttributes = [typingAttributes copy];
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
#import "ASImageNode+CGExtras.h"
|
#import "ASImageNode+CGExtras.h"
|
||||||
|
|
||||||
#import "ASInternalHelpers.h"
|
#import "ASInternalHelpers.h"
|
||||||
|
#import "ASEqualityHelpers.h"
|
||||||
|
|
||||||
@interface _ASImageNodeDrawParameters : NSObject
|
@interface _ASImageNodeDrawParameters : NSObject
|
||||||
|
|
||||||
@ -123,7 +124,7 @@
|
|||||||
- (void)setImage:(UIImage *)image
|
- (void)setImage:(UIImage *)image
|
||||||
{
|
{
|
||||||
ASDN::MutexLocker l(_imageLock);
|
ASDN::MutexLocker l(_imageLock);
|
||||||
if (_image != image) {
|
if (!ASObjectIsEqual(_image, image)) {
|
||||||
_image = image;
|
_image = image;
|
||||||
|
|
||||||
ASDN::MutexUnlocker u(_imageLock);
|
ASDN::MutexUnlocker u(_imageLock);
|
||||||
|
@ -215,6 +215,18 @@ didFinishDownloadingImageWithIdentifier:(ASImageIdentifier)imageIdentifier
|
|||||||
*/
|
*/
|
||||||
- (nullable NSURL *)multiplexImageNode:(ASMultiplexImageNode *)imageNode URLForImageIdentifier:(ASImageIdentifier)imageIdentifier;
|
- (nullable NSURL *)multiplexImageNode:(ASMultiplexImageNode *)imageNode URLForImageIdentifier:(ASImageIdentifier)imageIdentifier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @abstract A PHAsset for the specific asset local identifier
|
||||||
|
* @param imageNode The sender.
|
||||||
|
* @param assetLocalIdentifier The local identifier for a PHAsset that this image node is loading.
|
||||||
|
*
|
||||||
|
* @discussion This optional method can improve image performance if your data source already has the PHAsset available.
|
||||||
|
* If this method is not implemented, or returns nil, the image node will request the asset from the Photos framework.
|
||||||
|
* @note This method may be called from any thread.
|
||||||
|
* @return A PHAsset corresponding to `assetLocalIdentifier`, or nil if none is available.
|
||||||
|
*/
|
||||||
|
- (PHAsset *)multiplexImageNode:(ASMultiplexImageNode *)imageNode assetForLocalIdentifier:(NSString *)assetLocalIdentifier;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
#pragma mark -
|
#pragma mark -
|
||||||
|
@ -57,6 +57,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
|
|||||||
struct {
|
struct {
|
||||||
unsigned int image:1;
|
unsigned int image:1;
|
||||||
unsigned int URL:1;
|
unsigned int URL:1;
|
||||||
|
unsigned int asset:1;
|
||||||
} _dataSourceFlags;
|
} _dataSourceFlags;
|
||||||
|
|
||||||
// Image flags.
|
// Image flags.
|
||||||
@ -66,6 +67,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
|
|||||||
id _loadedImageIdentifier;
|
id _loadedImageIdentifier;
|
||||||
id _loadingImageIdentifier;
|
id _loadingImageIdentifier;
|
||||||
id _displayedImageIdentifier;
|
id _displayedImageIdentifier;
|
||||||
|
__weak NSOperation *_phImageRequestOperation;
|
||||||
|
|
||||||
// Networking.
|
// Networking.
|
||||||
id _downloadIdentifier;
|
id _downloadIdentifier;
|
||||||
@ -168,12 +170,19 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
|
|||||||
return [self initWithCache:nil downloader:nil]; // satisfy compiler
|
return [self initWithCache:nil downloader:nil]; // satisfy compiler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)dealloc
|
||||||
|
{
|
||||||
|
[_phImageRequestOperation cancel];
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark - ASDisplayNode Overrides
|
#pragma mark - ASDisplayNode Overrides
|
||||||
- (void)clearContents
|
- (void)clearContents
|
||||||
{
|
{
|
||||||
[super clearContents]; // This actually clears the contents, so we need to do this first for our displayedImageIdentifier to be meaningful.
|
[super clearContents]; // This actually clears the contents, so we need to do this first for our displayedImageIdentifier to be meaningful.
|
||||||
[self _setDisplayedImageIdentifier:nil withImage:nil];
|
[self _setDisplayedImageIdentifier:nil withImage:nil];
|
||||||
|
|
||||||
|
[_phImageRequestOperation cancel];
|
||||||
|
|
||||||
if (_downloadIdentifier) {
|
if (_downloadIdentifier) {
|
||||||
[_downloader cancelImageDownloadForIdentifier:_downloadIdentifier];
|
[_downloader cancelImageDownloadForIdentifier:_downloadIdentifier];
|
||||||
_downloadIdentifier = nil;
|
_downloadIdentifier = nil;
|
||||||
@ -185,6 +194,14 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
|
|||||||
[super clearFetchedData];
|
[super clearFetchedData];
|
||||||
|
|
||||||
if ([self _shouldClearFetchedImageData]) {
|
if ([self _shouldClearFetchedImageData]) {
|
||||||
|
|
||||||
|
[_phImageRequestOperation cancel];
|
||||||
|
|
||||||
|
if (_downloadIdentifier) {
|
||||||
|
[_downloader cancelImageDownloadForIdentifier:_downloadIdentifier];
|
||||||
|
_downloadIdentifier = nil;
|
||||||
|
}
|
||||||
|
|
||||||
// setting this to nil makes the node fetch images the next time its display starts
|
// setting this to nil makes the node fetch images the next time its display starts
|
||||||
_loadedImageIdentifier = nil;
|
_loadedImageIdentifier = nil;
|
||||||
self.image = nil;
|
self.image = nil;
|
||||||
@ -250,6 +267,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
|
|||||||
_dataSource = dataSource;
|
_dataSource = dataSource;
|
||||||
_dataSourceFlags.image = [_dataSource respondsToSelector:@selector(multiplexImageNode:imageForImageIdentifier:)];
|
_dataSourceFlags.image = [_dataSource respondsToSelector:@selector(multiplexImageNode:imageForImageIdentifier:)];
|
||||||
_dataSourceFlags.URL = [_dataSource respondsToSelector:@selector(multiplexImageNode:URLForImageIdentifier:)];
|
_dataSourceFlags.URL = [_dataSource respondsToSelector:@selector(multiplexImageNode:URLForImageIdentifier:)];
|
||||||
|
_dataSourceFlags.asset = [_dataSource respondsToSelector:@selector(multiplexImageNode:assetForLocalIdentifier:)];
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark -
|
#pragma mark -
|
||||||
@ -268,7 +286,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
|
|||||||
{
|
{
|
||||||
OSSpinLockLock(&_imageIdentifiersLock);
|
OSSpinLockLock(&_imageIdentifiersLock);
|
||||||
|
|
||||||
if (_imageIdentifiers == imageIdentifiers) {
|
if ([_imageIdentifiers isEqual:imageIdentifiers]) {
|
||||||
OSSpinLockUnlock(&_imageIdentifiersLock);
|
OSSpinLockUnlock(&_imageIdentifiersLock);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -372,7 +390,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
|
|||||||
OSSpinLockLock(&_imageIdentifiersLock);
|
OSSpinLockLock(&_imageIdentifiersLock);
|
||||||
|
|
||||||
// If we've already loaded the best identifier, we've got nothing else to do.
|
// If we've already loaded the best identifier, we've got nothing else to do.
|
||||||
id bestImageIdentifier = ([_imageIdentifiers count] > 0) ? _imageIdentifiers[0] : nil;
|
id bestImageIdentifier = _imageIdentifiers.firstObject;
|
||||||
if (!bestImageIdentifier || [_loadedImageIdentifier isEqual:bestImageIdentifier]) {
|
if (!bestImageIdentifier || [_loadedImageIdentifier isEqual:bestImageIdentifier]) {
|
||||||
OSSpinLockUnlock(&_imageIdentifiersLock);
|
OSSpinLockUnlock(&_imageIdentifiersLock);
|
||||||
return nil;
|
return nil;
|
||||||
@ -518,17 +536,52 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
|
|||||||
ASDisplayNodeAssertNotNil(request, @"request is required");
|
ASDisplayNodeAssertNotNil(request, @"request is required");
|
||||||
ASDisplayNodeAssertNotNil(completionBlock, @"completionBlock is required");
|
ASDisplayNodeAssertNotNil(completionBlock, @"completionBlock is required");
|
||||||
|
|
||||||
// This is sometimes called on main but there's no reason to stay there
|
/*
|
||||||
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
|
* Locking rationale:
|
||||||
// Get the PHAsset itself.
|
* As of iOS 9, Photos.framework will eventually deadlock if you hit it with concurrent fetch requests. rdar://22984886
|
||||||
PHFetchResult *assetFetchResult = [PHAsset fetchAssetsWithLocalIdentifiers:@[request.assetIdentifier] options:nil];
|
* Concurrent image requests are OK, but metadata requests aren't, so we limit ourselves to one at a time.
|
||||||
if ([assetFetchResult count] == 0) {
|
*/
|
||||||
|
static NSLock *phRequestLock;
|
||||||
|
static NSOperationQueue *phImageRequestQueue;
|
||||||
|
static dispatch_once_t onceToken;
|
||||||
|
dispatch_once(&onceToken, ^{
|
||||||
|
phRequestLock = [NSLock new];
|
||||||
|
phImageRequestQueue = [NSOperationQueue new];
|
||||||
|
phImageRequestQueue.maxConcurrentOperationCount = 10;
|
||||||
|
phImageRequestQueue.name = @"org.AsyncDisplayKit.MultiplexImageNode.phImageRequestQueue";
|
||||||
|
});
|
||||||
|
|
||||||
|
// Each ASMultiplexImageNode can have max 1 inflight Photos image request operation
|
||||||
|
[_phImageRequestOperation cancel];
|
||||||
|
|
||||||
|
__weak __typeof(self) weakSelf = self;
|
||||||
|
NSOperation *newImageRequestOp = [NSBlockOperation blockOperationWithBlock:^{
|
||||||
|
__strong __typeof(weakSelf) strongSelf = weakSelf;
|
||||||
|
if (strongSelf == nil) { return; }
|
||||||
|
|
||||||
|
PHAsset *imageAsset = nil;
|
||||||
|
|
||||||
|
// Try to get the asset immediately from the data source.
|
||||||
|
if (_dataSourceFlags.asset) {
|
||||||
|
imageAsset = [strongSelf.dataSource multiplexImageNode:strongSelf assetForLocalIdentifier:request.assetIdentifier];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to locking and getting the PHAsset.
|
||||||
|
if (imageAsset == nil) {
|
||||||
|
[phRequestLock lock];
|
||||||
|
// -[PHFetchResult dealloc] plays a role in the deadlock mentioned above, so we make sure the PHFetchResult is deallocated inside the critical section
|
||||||
|
@autoreleasepool {
|
||||||
|
imageAsset = [PHAsset fetchAssetsWithLocalIdentifiers:@[request.assetIdentifier] options:nil].firstObject;
|
||||||
|
}
|
||||||
|
[phRequestLock unlock];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imageAsset == nil) {
|
||||||
// Error.
|
// Error.
|
||||||
completionBlock(nil, nil);
|
completionBlock(nil, nil);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
PHAsset *imageAsset = [assetFetchResult firstObject];
|
|
||||||
PHImageRequestOptions *options = [request.options copy];
|
PHImageRequestOptions *options = [request.options copy];
|
||||||
|
|
||||||
// We don't support opportunistic delivery – one request, one image.
|
// We don't support opportunistic delivery – one request, one image.
|
||||||
@ -542,7 +595,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
|
|||||||
options.synchronous = YES;
|
options.synchronous = YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
PHImageManager *imageManager = self.imageManager ?: PHImageManager.defaultManager;
|
PHImageManager *imageManager = strongSelf.imageManager ?: PHImageManager.defaultManager;
|
||||||
[imageManager requestImageForAsset:imageAsset targetSize:request.targetSize contentMode:request.contentMode options:options resultHandler:^(UIImage *image, NSDictionary *info) {
|
[imageManager requestImageForAsset:imageAsset targetSize:request.targetSize contentMode:request.contentMode options:options resultHandler:^(UIImage *image, NSDictionary *info) {
|
||||||
if (NSThread.isMainThread) {
|
if (NSThread.isMainThread) {
|
||||||
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
|
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
|
||||||
@ -552,7 +605,9 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
|
|||||||
completionBlock(image, info[PHImageErrorKey]);
|
completionBlock(image, info[PHImageErrorKey]);
|
||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
});
|
}];
|
||||||
|
_phImageRequestOperation = newImageRequestOp;
|
||||||
|
[phImageRequestQueue addOperation:newImageRequestOp];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)_fetchImageWithIdentifierFromCache:(id)imageIdentifier URL:(NSURL *)imageURL completion:(void (^)(UIImage *image))completionBlock
|
- (void)_fetchImageWithIdentifierFromCache:(id)imageIdentifier URL:(NSURL *)imageURL completion:(void (^)(UIImage *image))completionBlock
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
#import "ASBasicImageDownloader.h"
|
#import "ASBasicImageDownloader.h"
|
||||||
#import "ASDisplayNode+Subclasses.h"
|
#import "ASDisplayNode+Subclasses.h"
|
||||||
#import "ASThread.h"
|
#import "ASThread.h"
|
||||||
|
#import "ASEqualityHelpers.h"
|
||||||
|
|
||||||
@interface ASNetworkImageNode ()
|
@interface ASNetworkImageNode ()
|
||||||
{
|
{
|
||||||
@ -70,7 +70,7 @@
|
|||||||
{
|
{
|
||||||
ASDN::MutexLocker l(_lock);
|
ASDN::MutexLocker l(_lock);
|
||||||
|
|
||||||
if (URL == _URL || [URL isEqual:_URL]) {
|
if (ASObjectIsEqual(URL, _URL)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,7 +96,7 @@
|
|||||||
{
|
{
|
||||||
ASDN::MutexLocker l(_lock);
|
ASDN::MutexLocker l(_lock);
|
||||||
|
|
||||||
if (defaultImage == _defaultImage || [defaultImage isEqual:_defaultImage]) {
|
if (ASObjectIsEqual(defaultImage, _defaultImage)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_defaultImage = defaultImage;
|
_defaultImage = defaultImage;
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
{
|
{
|
||||||
return [super initWithViewBlock:^UIView *{
|
return [super initWithViewBlock:^UIView *{
|
||||||
return [[ASScrollView alloc] init];
|
return [[ASScrollView alloc] init];
|
||||||
}];
|
} didLoadBlock:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
@ -17,8 +17,10 @@
|
|||||||
#import <AsyncDisplayKit/ASTextNodeTextKitHelpers.h>
|
#import <AsyncDisplayKit/ASTextNodeTextKitHelpers.h>
|
||||||
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
|
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
|
||||||
|
|
||||||
|
#import "ASInternalHelpers.h"
|
||||||
#import "ASTextNodeRenderer.h"
|
#import "ASTextNodeRenderer.h"
|
||||||
#import "ASTextNodeShadower.h"
|
#import "ASTextNodeShadower.h"
|
||||||
|
#import "ASEqualityHelpers.h"
|
||||||
|
|
||||||
static const NSTimeInterval ASTextNodeHighlightFadeOutDuration = 0.15;
|
static const NSTimeInterval ASTextNodeHighlightFadeOutDuration = 0.15;
|
||||||
static const NSTimeInterval ASTextNodeHighlightFadeInDuration = 0.1;
|
static const NSTimeInterval ASTextNodeHighlightFadeInDuration = 0.1;
|
||||||
@ -315,7 +317,7 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
|
|||||||
#pragma mark - Modifying User Text
|
#pragma mark - Modifying User Text
|
||||||
|
|
||||||
- (void)setAttributedString:(NSAttributedString *)attributedString {
|
- (void)setAttributedString:(NSAttributedString *)attributedString {
|
||||||
if (attributedString == _attributedString) {
|
if (ASObjectIsEqual(attributedString, _attributedString)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -343,20 +345,28 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
|
|||||||
self.isAccessibilityElement = YES;
|
self.isAccessibilityElement = YES;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (attributedString.length > 0) {
|
||||||
|
CGFloat screenScale = ASScreenScale();
|
||||||
|
self.ascender = round([[attributedString attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL] ascender] * screenScale)/screenScale;
|
||||||
|
self.descender = round([[attributedString attribute:NSFontAttributeName atIndex:attributedString.length - 1 effectiveRange:NULL] descender] * screenScale)/screenScale;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Text Layout
|
#pragma mark - Text Layout
|
||||||
|
|
||||||
- (void)setExclusionPaths:(NSArray *)exclusionPaths
|
- (void)setExclusionPaths:(NSArray *)exclusionPaths
|
||||||
{
|
{
|
||||||
if ((_exclusionPaths == nil && exclusionPaths != nil) || (![_exclusionPaths isEqualToArray:exclusionPaths])) {
|
if (ASObjectIsEqual(exclusionPaths, _exclusionPaths)) {
|
||||||
_exclusionPaths = exclusionPaths;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_exclusionPaths = [exclusionPaths copy];
|
||||||
[self _invalidateRenderer];
|
[self _invalidateRenderer];
|
||||||
[self invalidateCalculatedLayout];
|
[self invalidateCalculatedLayout];
|
||||||
ASDisplayNodeRespectThreadAffinityOfNode(self, ^{
|
ASDisplayNodeRespectThreadAffinityOfNode(self, ^{
|
||||||
[self setNeedsDisplay];
|
[self setNeedsDisplay];
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSArray *)exclusionPaths
|
- (NSArray *)exclusionPaths
|
||||||
@ -965,28 +975,22 @@ static NSAttributedString *DefaultTruncationAttributedString()
|
|||||||
|
|
||||||
- (void)setTruncationAttributedString:(NSAttributedString *)truncationAttributedString
|
- (void)setTruncationAttributedString:(NSAttributedString *)truncationAttributedString
|
||||||
{
|
{
|
||||||
// No-op if they're exactly equal (avoid redrawing)
|
if (ASObjectIsEqual(_truncationAttributedString, truncationAttributedString)) {
|
||||||
if (_truncationAttributedString == truncationAttributedString) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (![_truncationAttributedString isEqual:truncationAttributedString]) {
|
|
||||||
_truncationAttributedString = [truncationAttributedString copy];
|
_truncationAttributedString = [truncationAttributedString copy];
|
||||||
[self _invalidateTruncationString];
|
[self _invalidateTruncationString];
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setAdditionalTruncationMessage:(NSAttributedString *)additionalTruncationMessage
|
- (void)setAdditionalTruncationMessage:(NSAttributedString *)additionalTruncationMessage
|
||||||
{
|
{
|
||||||
// Short circuit if we're setting to nil (prevent redrawing when we don't need to)
|
if (ASObjectIsEqual(_additionalTruncationMessage, additionalTruncationMessage)) {
|
||||||
if (_additionalTruncationMessage == additionalTruncationMessage) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (![_additionalTruncationMessage isEqual:additionalTruncationMessage]) {
|
|
||||||
_additionalTruncationMessage = [additionalTruncationMessage copy];
|
_additionalTruncationMessage = [additionalTruncationMessage copy];
|
||||||
[self _invalidateTruncationString];
|
[self _invalidateTruncationString];
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setTruncationMode:(NSLineBreakMode)truncationMode
|
- (void)setTruncationMode:(NSLineBreakMode)truncationMode
|
||||||
|
@ -96,6 +96,25 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
|||||||
|
|
||||||
#pragma mark - Cell Layout
|
#pragma mark - Cell Layout
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FIXME: Shouldn't this method, as well as `_layoutNodes:atIndexPaths:withAnimationOptions:` use the word "measure" instead?
|
||||||
|
*
|
||||||
|
* Once nodes have loaded their views, we can't layout in the background so this is a chance
|
||||||
|
* to do so immediately on the main thread.
|
||||||
|
*/
|
||||||
|
- (void)_layoutNodesWithMainThreadAffinity:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths {
|
||||||
|
NSAssert(NSThread.isMainThread, @"Main thread layout must be on the main thread.");
|
||||||
|
|
||||||
|
[indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, __unused BOOL * stop) {
|
||||||
|
ASCellNode *node = nodes[idx];
|
||||||
|
if (node.isNodeLoaded) {
|
||||||
|
ASSizeRange constrainedSize = [_dataSource dataController:self constrainedSizeForNodeAtIndexPath:indexPath];
|
||||||
|
[node measureWithSizeRange:constrainedSize];
|
||||||
|
node.frame = CGRectMake(0.0f, 0.0f, node.calculatedSize.width, node.calculatedSize.height);
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
- (void)_layoutNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
- (void)_layoutNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||||
{
|
{
|
||||||
ASDisplayNodeAssert([NSOperationQueue currentQueue] == _editingTransactionQueue, @"Cell node layout must be initiated from edit transaction queue");
|
ASDisplayNodeAssert([NSOperationQueue currentQueue] == _editingTransactionQueue, @"Cell node layout must be initiated from edit transaction queue");
|
||||||
@ -110,15 +129,22 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
|||||||
NSInteger batchCount = MIN(kASDataControllerSizingCountPerProcessor, indexPaths.count - j);
|
NSInteger batchCount = MIN(kASDataControllerSizingCountPerProcessor, indexPaths.count - j);
|
||||||
|
|
||||||
for (NSUInteger k = j; k < j + batchCount; k++) {
|
for (NSUInteger k = j; k < j + batchCount; k++) {
|
||||||
|
ASCellNode *node = nodes[k];
|
||||||
|
if (!node.isNodeLoaded) {
|
||||||
nodeBoundSizes[k] = [_dataSource dataController:self constrainedSizeForNodeAtIndexPath:indexPaths[k]];
|
nodeBoundSizes[k] = [_dataSource dataController:self constrainedSizeForNodeAtIndexPath:indexPaths[k]];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dispatch_group_async(layoutGroup, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
dispatch_group_async(layoutGroup, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||||
for (NSUInteger k = j; k < j + batchCount; k++) {
|
for (NSUInteger k = j; k < j + batchCount; k++) {
|
||||||
ASCellNode *node = nodes[k];
|
ASCellNode *node = nodes[k];
|
||||||
|
// Only measure nodes whose views aren't loaded, since we're in the background.
|
||||||
|
// We should already have measured loaded nodes before we left the main thread, using _layoutNodesWithMainThreadAffinity:
|
||||||
|
if (!node.isNodeLoaded) {
|
||||||
ASSizeRange constrainedSize = nodeBoundSizes[k];
|
ASSizeRange constrainedSize = nodeBoundSizes[k];
|
||||||
[node measureWithSizeRange:constrainedSize];
|
[node measureWithSizeRange:constrainedSize];
|
||||||
node.frame = CGRectMake(0, 0, node.calculatedSize.width, node.calculatedSize.height);
|
node.frame = CGRectMake(0.0f, 0.0f, node.calculatedSize.width, node.calculatedSize.height);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -245,6 +271,9 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
|||||||
NSMutableArray *updatedIndexPaths = [NSMutableArray array];
|
NSMutableArray *updatedIndexPaths = [NSMutableArray array];
|
||||||
[self _populateFromEntireDataSourceWithMutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths];
|
[self _populateFromEntireDataSourceWithMutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths];
|
||||||
|
|
||||||
|
// Measure nodes whose views are loaded before we leave the main thread
|
||||||
|
[self _layoutNodesWithMainThreadAffinity:updatedNodes atIndexPaths:updatedIndexPaths];
|
||||||
|
|
||||||
[_editingTransactionQueue addOperationWithBlock:^{
|
[_editingTransactionQueue addOperationWithBlock:^{
|
||||||
LOG(@"Edit Transaction - reloadData");
|
LOG(@"Edit Transaction - reloadData");
|
||||||
|
|
||||||
@ -399,6 +428,9 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
|||||||
NSMutableArray *updatedIndexPaths = [NSMutableArray array];
|
NSMutableArray *updatedIndexPaths = [NSMutableArray array];
|
||||||
[self _populateFromDataSourceWithSectionIndexSet:indexSet mutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths];
|
[self _populateFromDataSourceWithSectionIndexSet:indexSet mutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths];
|
||||||
|
|
||||||
|
// Measure nodes whose views are loaded before we leave the main thread
|
||||||
|
[self _layoutNodesWithMainThreadAffinity:updatedNodes atIndexPaths:updatedIndexPaths];
|
||||||
|
|
||||||
[_editingTransactionQueue addOperationWithBlock:^{
|
[_editingTransactionQueue addOperationWithBlock:^{
|
||||||
LOG(@"Edit Transaction - insertSections: %@", indexSet);
|
LOG(@"Edit Transaction - insertSections: %@", indexSet);
|
||||||
NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:indexSet.count];
|
NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:indexSet.count];
|
||||||
@ -448,6 +480,9 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
|||||||
// For example, if an initial -reloadData call is quickly followed by -reloadSections, sizing the initial set may not be done
|
// For example, if an initial -reloadData call is quickly followed by -reloadSections, sizing the initial set may not be done
|
||||||
// at this time. Thus _editingNodes could be empty and crash in ASIndexPathsForMultidimensional[...]
|
// at this time. Thus _editingNodes could be empty and crash in ASIndexPathsForMultidimensional[...]
|
||||||
|
|
||||||
|
// Measure nodes whose views are loaded before we leave the main thread
|
||||||
|
[self _layoutNodesWithMainThreadAffinity:updatedNodes atIndexPaths:updatedIndexPaths];
|
||||||
|
|
||||||
[_editingTransactionQueue addOperationWithBlock:^{
|
[_editingTransactionQueue addOperationWithBlock:^{
|
||||||
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes, sections);
|
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes, sections);
|
||||||
|
|
||||||
@ -482,9 +517,9 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
|||||||
// update the section of indexpaths
|
// update the section of indexpaths
|
||||||
NSIndexPath *sectionIndexPath = [[NSIndexPath alloc] initWithIndex:newSection];
|
NSIndexPath *sectionIndexPath = [[NSIndexPath alloc] initWithIndex:newSection];
|
||||||
NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] initWithCapacity:indexPaths.count];
|
NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] initWithCapacity:indexPaths.count];
|
||||||
[indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
|
for (NSIndexPath *indexPath in indexPaths) {
|
||||||
[updatedIndexPaths addObject:[sectionIndexPath indexPathByAddingIndex:[indexPath indexAtPosition:indexPath.length - 1]]];
|
[updatedIndexPaths addObject:[sectionIndexPath indexPathByAddingIndex:[indexPath indexAtPosition:indexPath.length - 1]]];
|
||||||
}];
|
}
|
||||||
|
|
||||||
// Don't re-calculate size for moving
|
// Don't re-calculate size for moving
|
||||||
[self _insertNodes:nodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions];
|
[self _insertNodes:nodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions];
|
||||||
@ -510,6 +545,9 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
|||||||
[nodes addObject:[_dataSource dataController:self nodeAtIndexPath:sortedIndexPaths[i]]];
|
[nodes addObject:[_dataSource dataController:self nodeAtIndexPath:sortedIndexPaths[i]]];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Measure nodes whose views are loaded before we leave the main thread
|
||||||
|
[self _layoutNodesWithMainThreadAffinity:nodes atIndexPaths:indexPaths];
|
||||||
|
|
||||||
[_editingTransactionQueue addOperationWithBlock:^{
|
[_editingTransactionQueue addOperationWithBlock:^{
|
||||||
LOG(@"Edit Transaction - insertRows: %@", indexPaths);
|
LOG(@"Edit Transaction - insertRows: %@", indexPaths);
|
||||||
[self _batchLayoutNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
|
[self _batchLayoutNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
|
||||||
@ -527,6 +565,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
|||||||
[_editingTransactionQueue waitUntilAllOperationsAreFinished];
|
[_editingTransactionQueue waitUntilAllOperationsAreFinished];
|
||||||
|
|
||||||
// sort indexPath in order to avoid messing up the index when deleting
|
// sort indexPath in order to avoid messing up the index when deleting
|
||||||
|
// FIXME: Shouldn't deletes be sorted in descending order?
|
||||||
NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)];
|
NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)];
|
||||||
|
|
||||||
[_editingTransactionQueue addOperationWithBlock:^{
|
[_editingTransactionQueue addOperationWithBlock:^{
|
||||||
@ -547,10 +586,17 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
|||||||
// Reloading requires re-fetching the data. Load it on the current calling thread, locking the data source.
|
// Reloading requires re-fetching the data. Load it on the current calling thread, locking the data source.
|
||||||
[self accessDataSourceWithBlock:^{
|
[self accessDataSourceWithBlock:^{
|
||||||
NSMutableArray *nodes = [[NSMutableArray alloc] initWithCapacity:indexPaths.count];
|
NSMutableArray *nodes = [[NSMutableArray alloc] initWithCapacity:indexPaths.count];
|
||||||
|
|
||||||
|
// FIXME: This doesn't currently do anything
|
||||||
|
// FIXME: Shouldn't deletes be sorted in descending order?
|
||||||
[indexPaths sortedArrayUsingSelector:@selector(compare:)];
|
[indexPaths sortedArrayUsingSelector:@selector(compare:)];
|
||||||
[indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
|
|
||||||
|
for (NSIndexPath *indexPath in indexPaths) {
|
||||||
[nodes addObject:[_dataSource dataController:self nodeAtIndexPath:indexPath]];
|
[nodes addObject:[_dataSource dataController:self nodeAtIndexPath:indexPath]];
|
||||||
}];
|
}
|
||||||
|
|
||||||
|
// Measure nodes whose views are loaded before we leave the main thread
|
||||||
|
[self _layoutNodesWithMainThreadAffinity:nodes atIndexPaths:indexPaths];
|
||||||
|
|
||||||
[_editingTransactionQueue addOperationWithBlock:^{
|
[_editingTransactionQueue addOperationWithBlock:^{
|
||||||
LOG(@"Edit Transaction - reloadRows: %@", indexPaths);
|
LOG(@"Edit Transaction - reloadRows: %@", indexPaths);
|
||||||
|
@ -149,7 +149,7 @@ NSAttributedString *ASCleanseAttributedStringOfCoreTextAttributes(NSAttributedSt
|
|||||||
|
|
||||||
return cleanAttributedString;
|
return cleanAttributedString;
|
||||||
} else {
|
} else {
|
||||||
return dirtyAttributedString;
|
return [dirtyAttributedString copy];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,9 +45,9 @@ typedef NS_ENUM(NSUInteger, ASStackLayoutAlignItems) {
|
|||||||
ASStackLayoutAlignItemsCenter,
|
ASStackLayoutAlignItemsCenter,
|
||||||
/** Expand children to fill cross axis */
|
/** Expand children to fill cross axis */
|
||||||
ASStackLayoutAlignItemsStretch,
|
ASStackLayoutAlignItemsStretch,
|
||||||
/** Children align to their first baseline. Only available for horizontal stack nodes */
|
/** Children align to their first baseline. Only available for horizontal stack spec */
|
||||||
ASStackLayoutAlignItemsBaselineFirst,
|
ASStackLayoutAlignItemsBaselineFirst,
|
||||||
/** Children align to their last baseline. Only available for horizontal stack nodes */
|
/** Children align to their last baseline. Only available for horizontal stack spec */
|
||||||
ASStackLayoutAlignItemsBaselineLast,
|
ASStackLayoutAlignItemsBaselineLast,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -66,8 +66,4 @@ typedef NS_ENUM(NSUInteger, ASStackLayoutAlignSelf) {
|
|||||||
ASStackLayoutAlignSelfCenter,
|
ASStackLayoutAlignSelfCenter,
|
||||||
/** Expand to fill cross axis */
|
/** Expand to fill cross axis */
|
||||||
ASStackLayoutAlignSelfStretch,
|
ASStackLayoutAlignSelfStretch,
|
||||||
/** Children align to their first baseline. Only available for horizontal stack nodes */
|
|
||||||
ASStackLayoutAlignSelfBaselineFirst,
|
|
||||||
/** Children align to their last baseline. Only available for horizontal stack nodes */
|
|
||||||
ASStackLayoutAlignSelfBaselineLast,
|
|
||||||
};
|
};
|
||||||
|
@ -101,7 +101,6 @@
|
|||||||
std::vector<id<ASLayoutable>> stackChildren = std::vector<id<ASLayoutable>>();
|
std::vector<id<ASLayoutable>> stackChildren = std::vector<id<ASLayoutable>>();
|
||||||
for (id<ASLayoutable> child in self.children) {
|
for (id<ASLayoutable> child in self.children) {
|
||||||
stackChildren.push_back(child);
|
stackChildren.push_back(child);
|
||||||
needsBaselinePass |= child.alignSelf == ASStackLayoutAlignSelfBaselineFirst || child.alignSelf == ASStackLayoutAlignSelfBaselineLast;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto unpositionedLayout = ASStackUnpositionedLayout::compute(stackChildren, style, constrainedSize);
|
const auto unpositionedLayout = ASStackUnpositionedLayout::compute(stackChildren, style, constrainedSize);
|
||||||
@ -109,12 +108,21 @@
|
|||||||
|
|
||||||
CGSize finalSize = CGSizeZero;
|
CGSize finalSize = CGSizeZero;
|
||||||
NSArray *sublayouts = nil;
|
NSArray *sublayouts = nil;
|
||||||
if (needsBaselinePass) {
|
|
||||||
|
// regardless of whether or not this stack aligns to baseline, we should let ASStackBaselinePositionedLayout::compute find the max ascender
|
||||||
|
// and min descender in case this spec is a child in another spec that wants to align to a baseline.
|
||||||
const auto baselinePositionedLayout = ASStackBaselinePositionedLayout::compute(positionedLayout, style, constrainedSize);
|
const auto baselinePositionedLayout = ASStackBaselinePositionedLayout::compute(positionedLayout, style, constrainedSize);
|
||||||
|
if (self.direction == ASStackLayoutDirectionVertical) {
|
||||||
|
ASDN::MutexLocker l(_propertyLock);
|
||||||
|
self.ascender = [[self.children firstObject] ascender];
|
||||||
|
self.descender = [[self.children lastObject] descender];
|
||||||
|
} else {
|
||||||
ASDN::MutexLocker l(_propertyLock);
|
ASDN::MutexLocker l(_propertyLock);
|
||||||
self.ascender = baselinePositionedLayout.ascender;
|
self.ascender = baselinePositionedLayout.ascender;
|
||||||
self.descender = baselinePositionedLayout.descender;
|
self.descender = baselinePositionedLayout.descender;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needsBaselinePass) {
|
||||||
finalSize = directionSize(style.direction, unpositionedLayout.stackDimensionSum, baselinePositionedLayout.crossSize);
|
finalSize = directionSize(style.direction, unpositionedLayout.stackDimensionSum, baselinePositionedLayout.crossSize);
|
||||||
sublayouts = [NSArray arrayWithObjects:&baselinePositionedLayout.sublayouts[0] count:baselinePositionedLayout.sublayouts.size()];
|
sublayouts = [NSArray arrayWithObjects:&baselinePositionedLayout.sublayouts[0] count:baselinePositionedLayout.sublayouts.size()];
|
||||||
} else {
|
} else {
|
||||||
|
@ -117,8 +117,8 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync,
|
|||||||
// Get the display block for this node.
|
// Get the display block for this node.
|
||||||
asyncdisplaykit_async_transaction_operation_block_t displayBlock = [self _displayBlockWithAsynchronous:NO isCancelledBlock:isCancelledBlock rasterizing:YES];
|
asyncdisplaykit_async_transaction_operation_block_t displayBlock = [self _displayBlockWithAsynchronous:NO isCancelledBlock:isCancelledBlock rasterizing:YES];
|
||||||
|
|
||||||
// We'll display something if there is a display block and/or a background color.
|
// We'll display something if there is a display block, clipping, translation and/or a background color.
|
||||||
BOOL shouldDisplay = displayBlock || backgroundColor;
|
BOOL shouldDisplay = displayBlock || backgroundColor || CGPointEqualToPoint(CGPointZero, frame.origin) == NO || clipsToBounds;
|
||||||
|
|
||||||
// If we should display, then push a transform, draw the background color, and draw the contents.
|
// If we should display, then push a transform, draw the background color, and draw the contents.
|
||||||
// The transform is popped in a block added after the recursion into subnodes.
|
// The transform is popped in a block added after the recursion into subnodes.
|
||||||
@ -131,8 +131,12 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync,
|
|||||||
CGContextTranslateCTM(context, frame.origin.x, frame.origin.y);
|
CGContextTranslateCTM(context, frame.origin.x, frame.origin.y);
|
||||||
|
|
||||||
//support cornerRadius
|
//support cornerRadius
|
||||||
if (rasterizingFromAscendent && cornerRadius && clipsToBounds) {
|
if (rasterizingFromAscendent && clipsToBounds) {
|
||||||
[[UIBezierPath bezierPathWithRoundedRect:bounds cornerRadius:self.cornerRadius] addClip];
|
if (cornerRadius) {
|
||||||
|
[[UIBezierPath bezierPathWithRoundedRect:bounds cornerRadius:cornerRadius] addClip];
|
||||||
|
} else {
|
||||||
|
[[UIBezierPath bezierPathWithRect:bounds] addClip];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fill background if any.
|
// Fill background if any.
|
||||||
|
@ -143,22 +143,7 @@
|
|||||||
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.)");
|
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
|
#endif
|
||||||
|
|
||||||
BOOL useLayer = (_layer && ASDisplayNodeThreadIsMain());
|
[self __setSafeFrame:rect];
|
||||||
|
|
||||||
CGPoint origin = (useLayer ? _layer.bounds.origin : self.bounds.origin);
|
|
||||||
CGPoint anchorPoint = (useLayer ? _layer.anchorPoint : self.anchorPoint);
|
|
||||||
|
|
||||||
CGRect bounds = (CGRect){ origin, rect.size };
|
|
||||||
CGPoint position = CGPointMake(rect.origin.x + rect.size.width * anchorPoint.x,
|
|
||||||
rect.origin.y + rect.size.height * anchorPoint.y);
|
|
||||||
|
|
||||||
if (useLayer) {
|
|
||||||
_layer.bounds = bounds;
|
|
||||||
_layer.position = position;
|
|
||||||
} else {
|
|
||||||
self.bounds = bounds;
|
|
||||||
self.position = position;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setNeedsDisplay
|
- (void)setNeedsDisplay
|
||||||
|
@ -126,6 +126,11 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) {
|
|||||||
- (ASLayout *)__measureWithSizeRange:(ASSizeRange)constrainedSize;
|
- (ASLayout *)__measureWithSizeRange:(ASSizeRange)constrainedSize;
|
||||||
|
|
||||||
- (void)__setNeedsLayout;
|
- (void)__setNeedsLayout;
|
||||||
|
/**
|
||||||
|
* Sets a new frame to this node by changing its bounds and position. This method can be safely called even if the transform property
|
||||||
|
* contains a non-identity transform, because bounds and position can be changed in such case.
|
||||||
|
*/
|
||||||
|
- (void)__setSafeFrame:(CGRect)rect;
|
||||||
- (void)__layout;
|
- (void)__layout;
|
||||||
- (void)__setSupernode:(ASDisplayNode *)supernode;
|
- (void)__setSupernode:(ASDisplayNode *)supernode;
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ ASStackBaselinePositionedLayout ASStackBaselinePositionedLayout::compute(const A
|
|||||||
const auto ascenderIt = std::max_element(positionedLayout.sublayouts.begin(), positionedLayout.sublayouts.end(), [&](const ASLayout *a, const ASLayout *b){
|
const auto ascenderIt = std::max_element(positionedLayout.sublayouts.begin(), positionedLayout.sublayouts.end(), [&](const ASLayout *a, const ASLayout *b){
|
||||||
return a.layoutableObject.ascender < b.layoutableObject.ascender;
|
return a.layoutableObject.ascender < b.layoutableObject.ascender;
|
||||||
});
|
});
|
||||||
const CGFloat maxAscender = baselineIt == positionedLayout.sublayouts.end() ? 0 : (*ascenderIt).layoutableObject.ascender;
|
const CGFloat maxAscender = ascenderIt == positionedLayout.sublayouts.end() ? 0 : (*ascenderIt).layoutableObject.ascender;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Step 3: Take each child and update its layout position based on the baseline offset.
|
Step 3: Take each child and update its layout position based on the baseline offset.
|
||||||
@ -103,9 +103,13 @@ ASStackBaselinePositionedLayout ASStackBaselinePositionedLayout::compute(const A
|
|||||||
spacing between the two nodes is from the baseline, not the bounding box.
|
spacing between the two nodes is from the baseline, not the bounding box.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
std::vector<ASLayout *> stackedChildren;
|
||||||
|
// Only change positions of layouts this stackSpec is aligning to a baseline. Otherwise we are only here to
|
||||||
|
// compute the min/max descender/ascender for this stack spec.
|
||||||
|
if (style.baselineRelativeArrangement || style.alignItems == ASStackLayoutAlignItemsBaselineFirst || style.alignItems == ASStackLayoutAlignItemsBaselineLast) {
|
||||||
CGPoint p = CGPointZero;
|
CGPoint p = CGPointZero;
|
||||||
BOOL first = YES;
|
BOOL first = YES;
|
||||||
auto stackedChildren = AS::map(positionedLayout.sublayouts, [&](ASLayout *l) -> ASLayout *{
|
stackedChildren = AS::map(positionedLayout.sublayouts, [&](ASLayout *l) -> ASLayout *{
|
||||||
__weak id<ASLayoutable> child = l.layoutableObject;
|
__weak id<ASLayoutable> child = l.layoutableObject;
|
||||||
p = p + directionPoint(style.direction, child.spacingBefore, 0);
|
p = p + directionPoint(style.direction, child.spacingBefore, 0);
|
||||||
if (first) {
|
if (first) {
|
||||||
@ -130,6 +134,9 @@ ASStackBaselinePositionedLayout ASStackBaselinePositionedLayout::compute(const A
|
|||||||
|
|
||||||
return l;
|
return l;
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
stackedChildren = positionedLayout.sublayouts;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Step 4: Since we have been mucking with positions, there is a chance that our cross size has changed. Imagine a node with a font size of 40
|
Step 4: Since we have been mucking with positions, there is a chance that our cross size has changed. Imagine a node with a font size of 40
|
||||||
|
@ -63,10 +63,6 @@ inline ASStackLayoutAlignItems alignment(ASStackLayoutAlignSelf childAlignment,
|
|||||||
return ASStackLayoutAlignItemsStart;
|
return ASStackLayoutAlignItemsStart;
|
||||||
case ASStackLayoutAlignSelfStretch:
|
case ASStackLayoutAlignSelfStretch:
|
||||||
return ASStackLayoutAlignItemsStretch;
|
return ASStackLayoutAlignItemsStretch;
|
||||||
case ASStackLayoutAlignSelfBaselineFirst:
|
|
||||||
return ASStackLayoutAlignItemsBaselineFirst;
|
|
||||||
case ASStackLayoutAlignSelfBaselineLast:
|
|
||||||
return ASStackLayoutAlignItemsBaselineLast;
|
|
||||||
case ASStackLayoutAlignSelfAuto:
|
case ASStackLayoutAlignSelfAuto:
|
||||||
default:
|
default:
|
||||||
return stackAlignment;
|
return stackAlignment;
|
||||||
|
@ -27,4 +27,7 @@
|
|||||||
- (void)applyToView:(UIView *)view;
|
- (void)applyToView:(UIView *)view;
|
||||||
- (void)applyToLayer:(CALayer *)layer;
|
- (void)applyToLayer:(CALayer *)layer;
|
||||||
|
|
||||||
|
+ (_ASPendingState *)pendingViewStateFromLayer:(CALayer *)layer;
|
||||||
|
+ (_ASPendingState *)pendingViewStateFromView:(UIView *)view;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
@ -796,4 +796,212 @@
|
|||||||
view.accessibilityIdentifier = accessibilityIdentifier;
|
view.accessibilityIdentifier = accessibilityIdentifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
+ (_ASPendingState *)pendingViewStateFromLayer:(CALayer *)layer
|
||||||
|
{
|
||||||
|
_ASPendingState *pendingState = [[_ASPendingState alloc] init];
|
||||||
|
|
||||||
|
pendingState.anchorPoint = layer.anchorPoint;
|
||||||
|
(pendingState->_flags).setAnchorPoint = YES;
|
||||||
|
|
||||||
|
pendingState.position = layer.position;
|
||||||
|
(pendingState->_flags).setPosition = YES;
|
||||||
|
|
||||||
|
pendingState.zPosition = layer.zPosition;
|
||||||
|
(pendingState->_flags).setZPosition = YES;
|
||||||
|
|
||||||
|
pendingState.bounds = layer.bounds;
|
||||||
|
(pendingState->_flags).setBounds = YES;
|
||||||
|
|
||||||
|
pendingState.contentsScale = layer.contentsScale;
|
||||||
|
(pendingState->_flags).setContentsScale = YES;
|
||||||
|
|
||||||
|
pendingState.transform = layer.transform;
|
||||||
|
(pendingState->_flags).setTransform = YES;
|
||||||
|
|
||||||
|
pendingState.sublayerTransform = layer.sublayerTransform;
|
||||||
|
(pendingState->_flags).setSublayerTransform = YES;
|
||||||
|
|
||||||
|
pendingState.contents = layer.contents;
|
||||||
|
(pendingState->_flags).setContents = YES;
|
||||||
|
|
||||||
|
pendingState.clipsToBounds = layer.masksToBounds;
|
||||||
|
(pendingState->_flags).setClipsToBounds = YES;
|
||||||
|
|
||||||
|
pendingState.backgroundColor = layer.backgroundColor;
|
||||||
|
(pendingState->_flags).setBackgroundColor = YES;
|
||||||
|
|
||||||
|
pendingState.opaque = layer.opaque;
|
||||||
|
(pendingState->_flags).setOpaque = YES;
|
||||||
|
|
||||||
|
pendingState.hidden = layer.hidden;
|
||||||
|
(pendingState->_flags).setHidden = YES;
|
||||||
|
|
||||||
|
pendingState.alpha = layer.opacity;
|
||||||
|
(pendingState->_flags).setAlpha = YES;
|
||||||
|
|
||||||
|
pendingState.cornerRadius = layer.cornerRadius;
|
||||||
|
(pendingState->_flags).setCornerRadius = YES;
|
||||||
|
|
||||||
|
pendingState.contentMode = ASDisplayNodeUIContentModeFromCAContentsGravity(layer.contentsGravity);
|
||||||
|
(pendingState->_flags).setContentMode = YES;
|
||||||
|
|
||||||
|
pendingState.shadowColor = layer.shadowColor;
|
||||||
|
(pendingState->_flags).setShadowColor = YES;
|
||||||
|
|
||||||
|
pendingState.shadowOpacity = layer.shadowOpacity;
|
||||||
|
(pendingState->_flags).setShadowOpacity = YES;
|
||||||
|
|
||||||
|
pendingState.shadowOffset = layer.shadowOffset;
|
||||||
|
(pendingState->_flags).setShadowOffset = YES;
|
||||||
|
|
||||||
|
pendingState.shadowRadius = layer.shadowRadius;
|
||||||
|
(pendingState->_flags).setShadowRadius = YES;
|
||||||
|
|
||||||
|
pendingState.borderWidth = layer.borderWidth;
|
||||||
|
(pendingState->_flags).setBorderWidth = YES;
|
||||||
|
|
||||||
|
pendingState.borderColor = layer.borderColor;
|
||||||
|
(pendingState->_flags).setBorderColor = YES;
|
||||||
|
|
||||||
|
pendingState.needsDisplayOnBoundsChange = layer.needsDisplayOnBoundsChange;
|
||||||
|
(pendingState->_flags).setNeedsDisplayOnBoundsChange = YES;
|
||||||
|
|
||||||
|
pendingState.allowsEdgeAntialiasing = layer.allowsEdgeAntialiasing;
|
||||||
|
(pendingState->_flags).setAllowsEdgeAntialiasing = YES;
|
||||||
|
|
||||||
|
pendingState.edgeAntialiasingMask = layer.edgeAntialiasingMask;
|
||||||
|
(pendingState->_flags).setEdgeAntialiasingMask = YES;
|
||||||
|
|
||||||
|
return pendingState;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (_ASPendingState *)pendingViewStateFromView:(UIView *)view
|
||||||
|
{
|
||||||
|
_ASPendingState *pendingState = [[_ASPendingState alloc] init];
|
||||||
|
|
||||||
|
CALayer *layer = view.layer;
|
||||||
|
|
||||||
|
pendingState.anchorPoint = layer.anchorPoint;
|
||||||
|
(pendingState->_flags).setAnchorPoint = YES;
|
||||||
|
|
||||||
|
pendingState.position = layer.position;
|
||||||
|
(pendingState->_flags).setPosition = YES;
|
||||||
|
|
||||||
|
pendingState.zPosition = layer.zPosition;
|
||||||
|
(pendingState->_flags).setZPosition = YES;
|
||||||
|
|
||||||
|
pendingState.bounds = view.bounds;
|
||||||
|
(pendingState->_flags).setBounds = YES;
|
||||||
|
|
||||||
|
pendingState.contentsScale = layer.contentsScale;
|
||||||
|
(pendingState->_flags).setContentsScale = YES;
|
||||||
|
|
||||||
|
pendingState.transform = layer.transform;
|
||||||
|
(pendingState->_flags).setTransform = YES;
|
||||||
|
|
||||||
|
pendingState.sublayerTransform = layer.sublayerTransform;
|
||||||
|
(pendingState->_flags).setSublayerTransform = YES;
|
||||||
|
|
||||||
|
pendingState.contents = layer.contents;
|
||||||
|
(pendingState->_flags).setContents = YES;
|
||||||
|
|
||||||
|
pendingState.clipsToBounds = view.clipsToBounds;
|
||||||
|
(pendingState->_flags).setClipsToBounds = YES;
|
||||||
|
|
||||||
|
pendingState.backgroundColor = layer.backgroundColor;
|
||||||
|
(pendingState->_flags).setBackgroundColor = YES;
|
||||||
|
|
||||||
|
pendingState.tintColor = view.tintColor;
|
||||||
|
(pendingState->_flags).setTintColor = YES;
|
||||||
|
|
||||||
|
pendingState.opaque = layer.opaque;
|
||||||
|
(pendingState->_flags).setOpaque = YES;
|
||||||
|
|
||||||
|
pendingState.hidden = view.hidden;
|
||||||
|
(pendingState->_flags).setHidden = YES;
|
||||||
|
|
||||||
|
pendingState.alpha = view.alpha;
|
||||||
|
(pendingState->_flags).setAlpha = YES;
|
||||||
|
|
||||||
|
pendingState.cornerRadius = layer.cornerRadius;
|
||||||
|
(pendingState->_flags).setCornerRadius = YES;
|
||||||
|
|
||||||
|
pendingState.contentMode = view.contentMode;
|
||||||
|
(pendingState->_flags).setContentMode = YES;
|
||||||
|
|
||||||
|
pendingState.userInteractionEnabled = view.userInteractionEnabled;
|
||||||
|
(pendingState->_flags).setUserInteractionEnabled = YES;
|
||||||
|
|
||||||
|
pendingState.exclusiveTouch = view.exclusiveTouch;
|
||||||
|
(pendingState->_flags).setExclusiveTouch = YES;
|
||||||
|
|
||||||
|
pendingState.shadowColor = layer.shadowColor;
|
||||||
|
(pendingState->_flags).setShadowColor = YES;
|
||||||
|
|
||||||
|
pendingState.shadowOpacity = layer.shadowOpacity;
|
||||||
|
(pendingState->_flags).setShadowOpacity = YES;
|
||||||
|
|
||||||
|
pendingState.shadowOffset = layer.shadowOffset;
|
||||||
|
(pendingState->_flags).setShadowOffset = YES;
|
||||||
|
|
||||||
|
pendingState.shadowRadius = layer.shadowRadius;
|
||||||
|
(pendingState->_flags).setShadowRadius = YES;
|
||||||
|
|
||||||
|
pendingState.borderWidth = layer.borderWidth;
|
||||||
|
(pendingState->_flags).setBorderWidth = YES;
|
||||||
|
|
||||||
|
pendingState.borderColor = layer.borderColor;
|
||||||
|
(pendingState->_flags).setBorderColor = YES;
|
||||||
|
|
||||||
|
pendingState.autoresizingMask = view.autoresizingMask;
|
||||||
|
(pendingState->_flags).setAutoresizingMask = YES;
|
||||||
|
|
||||||
|
pendingState.autoresizesSubviews = view.autoresizesSubviews;
|
||||||
|
(pendingState->_flags).setAutoresizesSubviews = YES;
|
||||||
|
|
||||||
|
pendingState.needsDisplayOnBoundsChange = layer.needsDisplayOnBoundsChange;
|
||||||
|
(pendingState->_flags).setNeedsDisplayOnBoundsChange = YES;
|
||||||
|
|
||||||
|
pendingState.allowsEdgeAntialiasing = layer.allowsEdgeAntialiasing;
|
||||||
|
(pendingState->_flags).setAllowsEdgeAntialiasing = YES;
|
||||||
|
|
||||||
|
pendingState.edgeAntialiasingMask = layer.edgeAntialiasingMask;
|
||||||
|
(pendingState->_flags).setEdgeAntialiasingMask = YES;
|
||||||
|
|
||||||
|
pendingState.isAccessibilityElement = view.isAccessibilityElement;
|
||||||
|
(pendingState->_flags).setIsAccessibilityElement = YES;
|
||||||
|
|
||||||
|
pendingState.accessibilityLabel = view.accessibilityLabel;
|
||||||
|
(pendingState->_flags).setAccessibilityLabel = YES;
|
||||||
|
|
||||||
|
pendingState.accessibilityHint = view.accessibilityHint;
|
||||||
|
(pendingState->_flags).setAccessibilityHint = YES;
|
||||||
|
|
||||||
|
pendingState.accessibilityValue = view.accessibilityValue;
|
||||||
|
(pendingState->_flags).setAccessibilityValue = YES;
|
||||||
|
|
||||||
|
pendingState.accessibilityTraits = view.accessibilityTraits;
|
||||||
|
(pendingState->_flags).setAccessibilityTraits = YES;
|
||||||
|
|
||||||
|
pendingState.accessibilityFrame = view.accessibilityFrame;
|
||||||
|
(pendingState->_flags).setAccessibilityFrame = YES;
|
||||||
|
|
||||||
|
pendingState.accessibilityLanguage = view.accessibilityLanguage;
|
||||||
|
(pendingState->_flags).setAccessibilityLanguage = YES;
|
||||||
|
|
||||||
|
pendingState.accessibilityElementsHidden = view.accessibilityElementsHidden;
|
||||||
|
(pendingState->_flags).setAccessibilityElementsHidden = YES;
|
||||||
|
|
||||||
|
pendingState.accessibilityViewIsModal = view.accessibilityViewIsModal;
|
||||||
|
(pendingState->_flags).setAccessibilityViewIsModal = YES;
|
||||||
|
|
||||||
|
pendingState.shouldGroupAccessibilityChildren = view.shouldGroupAccessibilityChildren;
|
||||||
|
(pendingState->_flags).setShouldGroupAccessibilityChildren = YES;
|
||||||
|
|
||||||
|
pendingState.accessibilityIdentifier = view.accessibilityIdentifier;
|
||||||
|
(pendingState->_flags).setAccessibilityIdentifier = YES;
|
||||||
|
|
||||||
|
return pendingState;
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
@ -14,6 +14,12 @@
|
|||||||
{ \
|
{ \
|
||||||
[ASSnapshotTestCase hackilySynchronouslyRecursivelyRenderNode:node__]; \
|
[ASSnapshotTestCase hackilySynchronouslyRecursivelyRenderNode:node__]; \
|
||||||
FBSnapshotVerifyLayer(node__.layer, identifier__); \
|
FBSnapshotVerifyLayer(node__.layer, identifier__); \
|
||||||
|
[node__ setShouldRasterizeDescendants:YES]; \
|
||||||
|
[ASSnapshotTestCase hackilySynchronouslyRecursivelyRenderNode:node__]; \
|
||||||
|
FBSnapshotVerifyLayer(node__.layer, identifier__); \
|
||||||
|
[node__ setShouldRasterizeDescendants:NO]; \
|
||||||
|
[ASSnapshotTestCase hackilySynchronouslyRecursivelyRenderNode:node__]; \
|
||||||
|
FBSnapshotVerifyLayer(node__.layer, identifier__); \
|
||||||
}
|
}
|
||||||
|
|
||||||
@interface ASSnapshotTestCase : FBSnapshotTestCase
|
@interface ASSnapshotTestCase : FBSnapshotTestCase
|
||||||
|
Loading…
x
Reference in New Issue
Block a user