mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-24 07:05:35 +00:00
Merge pull request #1132 from garrettmoon/switchToInstanceDrawAndDisplay
[ASDisplayNode+AsyncDisplay] Implement instance methods of draw and display, use for text and image performance boost.
This commit is contained in:
@@ -20,4 +20,21 @@
|
||||
*/
|
||||
- (void)recursivelyEnsureDisplaySynchronously:(BOOL)synchronously;
|
||||
|
||||
/**
|
||||
* @abstract allow modification of a context before the node's content is drawn
|
||||
*
|
||||
* @discussion Set the block to be called after the context has been created and before the node's content is drawn.
|
||||
* You can override this to modify the context before the content is drawn. You are responsible for saving and
|
||||
* restoring context if necessary. Restoring can be done in contextDidDisplayNodeContent
|
||||
* This block can be called from *any* thread and it is unsafe to access any UIKit main thread properties from it.
|
||||
*/
|
||||
@property (nonatomic, strong) ASDisplayNodeContextModifier willDisplayNodeContentWithRenderingContext;
|
||||
|
||||
/**
|
||||
* @abstract allow modification of a context after the node's content is drawn
|
||||
*
|
||||
* @discussion
|
||||
*/
|
||||
@property (nonatomic, strong) ASDisplayNodeContextModifier didDisplayNodeContentWithRenderingContext;
|
||||
|
||||
@end
|
||||
|
||||
@@ -37,6 +37,11 @@ typedef CALayer * _Nonnull(^ASDisplayNodeLayerBlock)();
|
||||
*/
|
||||
typedef void (^ASDisplayNodeDidLoadBlock)(ASDisplayNode * _Nonnull node);
|
||||
|
||||
/**
|
||||
* ASDisplayNode will / did render node content in context.
|
||||
*/
|
||||
typedef void (^ASDisplayNodeContextModifier)(_Nonnull CGContextRef context);
|
||||
|
||||
/**
|
||||
Interface state is available on ASDisplayNode and ASViewController, and
|
||||
allows checking whether a node is in an interface situation where it is prudent to trigger certain
|
||||
|
||||
@@ -110,8 +110,12 @@ static struct ASDisplayNodeFlags GetASDisplayNodeFlags(Class c, ASDisplayNode *i
|
||||
flags.implementsImageDisplay = ([c respondsToSelector:@selector(displayWithParameters:isCancelled:)] ? 1 : 0);
|
||||
if (instance) {
|
||||
flags.implementsDrawParameters = ([instance respondsToSelector:@selector(drawParametersForAsyncLayer:)] ? 1 : 0);
|
||||
flags.implementsInstanceDrawRect = ([instance respondsToSelector:@selector(drawRect:withParameters:isCancelled:isRasterizing:)] ? 1 : 0);
|
||||
flags.implementsInstanceImageDisplay = ([instance respondsToSelector:@selector(displayWithParameters:isCancelled:)] ? 1 : 0);
|
||||
} else {
|
||||
flags.implementsDrawParameters = ([c instancesRespondToSelector:@selector(drawParametersForAsyncLayer:)] ? 1 : 0);
|
||||
flags.implementsInstanceDrawRect = ([c instancesRespondToSelector:@selector(drawRect:withParameters:isCancelled:isRasterizing:)] ? 1 : 0);
|
||||
flags.implementsInstanceImageDisplay = ([c instancesRespondToSelector:@selector(displayWithParameters:isCancelled:)] ? 1 : 0);
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
@@ -1491,7 +1495,7 @@ static NSInteger incrementIfFound(NSInteger i) {
|
||||
// Helper method to summarize whether or not the node run through the display process
|
||||
- (BOOL)__implementsDisplay
|
||||
{
|
||||
return _flags.implementsDrawRect == YES || _flags.implementsImageDisplay == YES || self.shouldRasterizeDescendants;
|
||||
return _flags.implementsDrawRect == YES || _flags.implementsImageDisplay == YES || self.shouldRasterizeDescendants || _flags.implementsInstanceDrawRect || _flags.implementsInstanceImageDisplay;
|
||||
}
|
||||
|
||||
- (void)_setupPlaceholderLayer
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Beta.h>
|
||||
|
||||
#import "ASImageNode+CGExtras.h"
|
||||
|
||||
@@ -22,42 +23,34 @@
|
||||
|
||||
@interface _ASImageNodeDrawParameters : NSObject
|
||||
|
||||
@property (nonatomic, assign, readonly) BOOL cropEnabled;
|
||||
@property (nonatomic, assign) BOOL opaque;
|
||||
@property (nonatomic, retain) UIImage *image;
|
||||
@property (nonatomic, assign) CGRect bounds;
|
||||
@property (nonatomic, assign) CGFloat contentsScale;
|
||||
@property (nonatomic, retain) UIColor *backgroundColor;
|
||||
@property (nonatomic, assign) UIViewContentMode contentMode;
|
||||
@property (nonatomic, assign) CGRect cropRect;
|
||||
@property (nonatomic, copy) asimagenode_modification_block_t imageModificationBlock;
|
||||
|
||||
@end
|
||||
|
||||
// TODO: eliminate explicit parameters with a set of keys copied from the node
|
||||
@implementation _ASImageNodeDrawParameters
|
||||
|
||||
- (id)initWithCrop:(BOOL)cropEnabled opaque:(BOOL)opaque image:(UIImage *)image bounds:(CGRect)bounds contentsScale:(CGFloat)contentsScale backgroundColor:(UIColor *)backgroundColor contentMode:(UIViewContentMode)contentMode cropRect:(CGRect)cropRect imageModificationBlock:(asimagenode_modification_block_t)imageModificationBlock
|
||||
- (id)initWithBounds:(CGRect)bounds opaque:(BOOL)opaque contentsScale:(CGFloat)contentsScale backgroundColor:(UIColor *)backgroundColor contentMode:(UIViewContentMode)contentMode
|
||||
{
|
||||
self = [self init];
|
||||
if (!self) return nil;
|
||||
|
||||
_cropEnabled = cropEnabled;
|
||||
_opaque = opaque;
|
||||
_image = image;
|
||||
_bounds = bounds;
|
||||
_contentsScale = contentsScale;
|
||||
_backgroundColor = backgroundColor;
|
||||
_contentMode = contentMode;
|
||||
_cropRect = cropRect;
|
||||
_imageModificationBlock = [imageModificationBlock copy];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"<%@ : %p image:%@ cropEnabled:%@ opaque:%@ bounds:%@ contentsScale:%.2f backgroundColor:%@ contentMode:%@ cropRect:%@>", [self class], self, self.image, @(self.cropEnabled), @(self.opaque), NSStringFromCGRect(self.bounds), self.contentsScale, self.backgroundColor, ASDisplayNodeNSStringFromUIContentMode(self.contentMode), NSStringFromCGRect(self.cropRect)];
|
||||
return [NSString stringWithFormat:@"<%@ : %p opaque:%@ bounds:%@ contentsScale:%.2f backgroundColor:%@ contentMode:%@>", [self class], self, @(self.opaque), NSStringFromCGRect(self.bounds), self.contentsScale, self.backgroundColor, ASDisplayNodeNSStringFromUIContentMode(self.contentMode)];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -78,6 +71,7 @@
|
||||
}
|
||||
|
||||
@synthesize image = _image;
|
||||
@synthesize imageModificationBlock = _imageModificationBlock;
|
||||
|
||||
- (id)init
|
||||
{
|
||||
@@ -151,90 +145,110 @@
|
||||
|
||||
- (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer;
|
||||
{
|
||||
BOOL hasValidCropBounds = _cropEnabled && !CGRectIsNull(_cropDisplayBounds) && !CGRectIsEmpty(_cropDisplayBounds);
|
||||
|
||||
return [[_ASImageNodeDrawParameters alloc] initWithCrop:_cropEnabled
|
||||
opaque:self.opaque
|
||||
image:self.image
|
||||
bounds:(hasValidCropBounds ? _cropDisplayBounds : self.bounds)
|
||||
contentsScale:self.contentsScaleForDisplay
|
||||
backgroundColor:self.backgroundColor
|
||||
contentMode:self.contentMode
|
||||
cropRect:self.cropRect
|
||||
imageModificationBlock:self.imageModificationBlock];
|
||||
return [[_ASImageNodeDrawParameters alloc] initWithBounds:self.bounds
|
||||
opaque:self.opaque
|
||||
contentsScale:self.contentsScaleForDisplay
|
||||
backgroundColor:self.backgroundColor
|
||||
contentMode:self.contentMode];
|
||||
}
|
||||
|
||||
+ (UIImage *)displayWithParameters:(_ASImageNodeDrawParameters *)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled
|
||||
- (UIImage *)displayWithParameters:(_ASImageNodeDrawParameters *)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled
|
||||
{
|
||||
UIImage *image = parameters.image;
|
||||
|
||||
if (!image) {
|
||||
return nil;
|
||||
UIImage *image;
|
||||
BOOL cropEnabled;
|
||||
CGFloat contentsScale;
|
||||
CGRect cropDisplayBounds;
|
||||
CGRect cropRect;
|
||||
asimagenode_modification_block_t imageModificationBlock;
|
||||
|
||||
{
|
||||
ASDN::MutexLocker l(_imageLock);
|
||||
image = _image;
|
||||
if (!image) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
cropEnabled = _cropEnabled;
|
||||
contentsScale = _contentsScaleForDisplay;
|
||||
cropDisplayBounds = _cropDisplayBounds;
|
||||
cropRect = _cropRect;
|
||||
imageModificationBlock = _imageModificationBlock;
|
||||
}
|
||||
|
||||
ASDisplayNodeAssert(parameters.contentsScale > 0, @"invalid contentsScale at display time");
|
||||
|
||||
|
||||
ASDisplayNodeContextModifier preContextBlock = self.willDisplayNodeContentWithRenderingContext;
|
||||
ASDisplayNodeContextModifier postContextBlock = self.didDisplayNodeContentWithRenderingContext;
|
||||
|
||||
BOOL hasValidCropBounds = cropEnabled && !CGRectIsNull(cropDisplayBounds) && !CGRectIsEmpty(cropDisplayBounds);
|
||||
|
||||
CGRect bounds = (hasValidCropBounds ? cropDisplayBounds : parameters.bounds);
|
||||
BOOL isOpaque = parameters.opaque;
|
||||
UIColor *backgroundColor = parameters.backgroundColor;
|
||||
UIViewContentMode contentMode = parameters.contentMode;
|
||||
|
||||
ASDisplayNodeAssert(contentsScale > 0, @"invalid contentsScale at display time");
|
||||
|
||||
// if the image is resizable, bail early since the image has likely already been configured
|
||||
BOOL stretchable = !UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero);
|
||||
if (stretchable) {
|
||||
if (parameters.imageModificationBlock != NULL) {
|
||||
image = parameters.imageModificationBlock(image);
|
||||
if (imageModificationBlock != NULL) {
|
||||
image = imageModificationBlock(image);
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
CGRect bounds = parameters.bounds;
|
||||
|
||||
CGFloat contentsScale = parameters.contentsScale;
|
||||
UIViewContentMode contentMode = parameters.contentMode;
|
||||
|
||||
CGSize imageSize = image.size;
|
||||
CGSize imageSizeInPixels = CGSizeMake(imageSize.width * image.scale, imageSize.height * image.scale);
|
||||
CGSize boundsSizeInPixels = CGSizeMake(floorf(bounds.size.width * contentsScale), floorf(bounds.size.height * contentsScale));
|
||||
|
||||
|
||||
BOOL contentModeSupported = contentMode == UIViewContentModeScaleAspectFill
|
||||
|| contentMode == UIViewContentModeScaleAspectFit
|
||||
|| contentMode == UIViewContentModeCenter;
|
||||
|
||||
|| contentMode == UIViewContentModeScaleAspectFit
|
||||
|| contentMode == UIViewContentModeCenter;
|
||||
|
||||
CGSize backingSize;
|
||||
CGRect imageDrawRect;
|
||||
|
||||
|
||||
if (boundsSizeInPixels.width * contentsScale < 1.0f ||
|
||||
boundsSizeInPixels.height * contentsScale < 1.0f ||
|
||||
imageSizeInPixels.width < 1.0f ||
|
||||
imageSizeInPixels.height < 1.0f) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
// If we're not supposed to do any cropping, just decode image at original size
|
||||
if (!parameters.cropEnabled || !contentModeSupported || stretchable) {
|
||||
if (!cropEnabled || !contentModeSupported || stretchable) {
|
||||
backingSize = imageSizeInPixels;
|
||||
imageDrawRect = (CGRect){.size = backingSize};
|
||||
} else {
|
||||
ASCroppedImageBackingSizeAndDrawRectInBounds(imageSizeInPixels,
|
||||
boundsSizeInPixels,
|
||||
contentMode,
|
||||
parameters.cropRect,
|
||||
cropRect,
|
||||
&backingSize,
|
||||
&imageDrawRect);
|
||||
}
|
||||
|
||||
|
||||
if (backingSize.width <= 0.0f ||
|
||||
backingSize.height <= 0.0f ||
|
||||
imageDrawRect.size.width <= 0.0f ||
|
||||
imageDrawRect.size.height <= 0.0f) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
// Use contentsScale of 1.0 and do the contentsScale handling in boundsSizeInPixels so ASCroppedImageBackingSizeAndDrawRectInBounds
|
||||
// will do its rounding on pixel instead of point boundaries
|
||||
UIGraphicsBeginImageContextWithOptions(backingSize, parameters.opaque, 1.0);
|
||||
UIGraphicsBeginImageContextWithOptions(backingSize, isOpaque, 1.0);
|
||||
|
||||
// if view is opaque, fill the context with background color
|
||||
if (parameters.opaque && parameters.backgroundColor) {
|
||||
[parameters.backgroundColor setFill];
|
||||
if (isOpaque && backgroundColor) {
|
||||
[backgroundColor setFill];
|
||||
UIRectFill({ .size = backingSize });
|
||||
}
|
||||
|
||||
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
if (context && preContextBlock) {
|
||||
preContextBlock(context);
|
||||
}
|
||||
|
||||
// iOS 9 appears to contain a thread safety regression when drawing the same CGImageRef on
|
||||
// multiple threads concurrently. In fact, instead of crashing, it appears to deadlock.
|
||||
// The issue is present in Mac OS X El Capitan and has been seen hanging Pro apps like Adobe Premier,
|
||||
@@ -250,20 +264,24 @@
|
||||
@synchronized(image) {
|
||||
[image drawInRect:imageDrawRect];
|
||||
}
|
||||
|
||||
|
||||
if (context && postContextBlock) {
|
||||
postContextBlock(context);
|
||||
}
|
||||
|
||||
if (isCancelled()) {
|
||||
UIGraphicsEndImageContext();
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
|
||||
|
||||
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
if (parameters.imageModificationBlock != NULL) {
|
||||
result = parameters.imageModificationBlock(result);
|
||||
|
||||
if (imageModificationBlock != NULL) {
|
||||
result = imageModificationBlock(result);
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -271,12 +289,21 @@
|
||||
{
|
||||
[super displayDidFinish];
|
||||
|
||||
// If we've got a block to perform after displaying, do it.
|
||||
if (self.image && _displayCompletionBlock) {
|
||||
ASDN::MutexLocker l(_imageLock);
|
||||
|
||||
// FIXME: _displayCompletionBlock is not protected by lock
|
||||
_displayCompletionBlock(NO);
|
||||
void (^displayCompletionBlock)(BOOL canceled) = _displayCompletionBlock;
|
||||
UIImage *image = _image;
|
||||
|
||||
ASDN::MutexLocker u(_imageLock);
|
||||
|
||||
// If we've got a block to perform after displaying, do it.
|
||||
if (image && displayCompletionBlock) {
|
||||
|
||||
displayCompletionBlock(NO);
|
||||
|
||||
ASDN::MutexLocker l(_imageLock);
|
||||
_displayCompletionBlock = nil;
|
||||
ASDN::MutexLocker u(_imageLock);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,7 +317,7 @@
|
||||
}
|
||||
|
||||
// Stash the block and call-site queue. We'll invoke it in -displayDidFinish.
|
||||
// FIXME: _displayCompletionBlock not protected by lock
|
||||
ASDN::MutexLocker l(_imageLock);
|
||||
if (_displayCompletionBlock != displayCompletionBlock) {
|
||||
_displayCompletionBlock = [displayCompletionBlock copy];
|
||||
}
|
||||
@@ -301,6 +328,7 @@
|
||||
#pragma mark - Cropping
|
||||
- (BOOL)isCropEnabled
|
||||
{
|
||||
ASDN::MutexLocker l(_imageLock);
|
||||
return _cropEnabled;
|
||||
}
|
||||
|
||||
@@ -311,6 +339,7 @@
|
||||
|
||||
- (void)setCropEnabled:(BOOL)cropEnabled recropImmediately:(BOOL)recropImmediately inBounds:(CGRect)cropBounds
|
||||
{
|
||||
ASDN::MutexLocker l(_imageLock);
|
||||
if (_cropEnabled == cropEnabled)
|
||||
return;
|
||||
|
||||
@@ -331,11 +360,13 @@
|
||||
|
||||
- (CGRect)cropRect
|
||||
{
|
||||
ASDN::MutexLocker l(_imageLock);
|
||||
return _cropRect;
|
||||
}
|
||||
|
||||
- (void)setCropRect:(CGRect)cropRect
|
||||
{
|
||||
ASDN::MutexLocker l(_imageLock);
|
||||
if (CGRectEqualToRect(_cropRect, cropRect))
|
||||
return;
|
||||
|
||||
@@ -354,6 +385,18 @@
|
||||
});
|
||||
}
|
||||
|
||||
- (asimagenode_modification_block_t)imageModificationBlock
|
||||
{
|
||||
ASDN::MutexLocker l(_imageLock);
|
||||
return _imageModificationBlock;
|
||||
}
|
||||
|
||||
- (void)setImageModificationBlock:(asimagenode_modification_block_t)imageModificationBlock
|
||||
{
|
||||
ASDN::MutexLocker l(_imageLock);
|
||||
_imageModificationBlock = imageModificationBlock;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@@ -35,13 +35,7 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
|
||||
|
||||
@interface ASTextNodeDrawParameters : NSObject
|
||||
|
||||
- (instancetype)initWithRenderer:(ASTextKitRenderer *)renderer
|
||||
textOrigin:(CGPoint)textOrigin
|
||||
backgroundColor:(UIColor *)backgroundColor;
|
||||
|
||||
@property (nonatomic, strong, readonly) ASTextKitRenderer *renderer;
|
||||
|
||||
@property (nonatomic, assign, readonly) CGPoint textOrigin;
|
||||
@property (nonatomic, assign, readonly) CGRect bounds;
|
||||
|
||||
@property (nonatomic, strong, readonly) UIColor *backgroundColor;
|
||||
|
||||
@@ -49,30 +43,16 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
|
||||
|
||||
@implementation ASTextNodeDrawParameters
|
||||
|
||||
- (instancetype)initWithRenderer:(ASTextKitRenderer *)renderer
|
||||
textOrigin:(CGPoint)textOrigin
|
||||
backgroundColor:(UIColor *)backgroundColor
|
||||
- (instancetype)initWithBounds:(CGRect)bounds
|
||||
backgroundColor:(UIColor *)backgroundColor
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_renderer = renderer;
|
||||
_textOrigin = textOrigin;
|
||||
_bounds = bounds;
|
||||
_backgroundColor = backgroundColor;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
// Destruction of the layout managers/containers/text storage is quite
|
||||
// expensive, and can take some time, so we dispatch onto a bg queue to
|
||||
// actually dealloc.
|
||||
__block ASTextKitRenderer *renderer = _renderer;
|
||||
ASPerformBlockOnBackgroundThread(^{
|
||||
renderer = nil;
|
||||
});
|
||||
_renderer = nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface ASTextNode () <UIGestureRecognizerDelegate, NSLayoutManagerDelegate>
|
||||
@@ -242,11 +222,17 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
|
||||
#pragma mark - Renderer Management
|
||||
|
||||
//only safe to call on the main thread because self.bounds is only safe to call on the main thread one our node is loaded
|
||||
- (ASTextKitRenderer *)_renderer
|
||||
{
|
||||
return [self _rendererWithBounds:self.bounds];
|
||||
}
|
||||
|
||||
- (ASTextKitRenderer *)_rendererWithBounds:(CGRect)bounds
|
||||
{
|
||||
ASDN::MutexLocker l(_rendererLock);
|
||||
if (_renderer == nil) {
|
||||
CGSize constrainedSize = _constrainedSize.width != -INFINITY ? _constrainedSize : self.bounds.size;
|
||||
CGSize constrainedSize = _constrainedSize.width != -INFINITY ? _constrainedSize : bounds.size;
|
||||
_renderer = [[ASTextKitRenderer alloc] initWithTextKitAttributes:[self _rendererAttributes]
|
||||
constrainedSize:constrainedSize];
|
||||
}
|
||||
@@ -435,13 +421,18 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
|
||||
#pragma mark - Drawing
|
||||
|
||||
+ (void)drawRect:(CGRect)bounds withParameters:(ASTextNodeDrawParameters *)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing
|
||||
- (void)drawRect:(CGRect)bounds withParameters:(ASTextNodeDrawParameters *)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing
|
||||
{
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
ASDisplayNodeAssert(context, @"This is no good without a context.");
|
||||
|
||||
|
||||
CGContextSaveGState(context);
|
||||
|
||||
|
||||
ASTextKitRenderer *renderer = [self _rendererWithBounds:parameters.bounds];
|
||||
UIEdgeInsets shadowPadding = [self shadowPaddingWithRenderer:renderer];
|
||||
CGPoint boundsOrigin = parameters.bounds.origin;
|
||||
CGPoint textOrigin = CGPointMake(boundsOrigin.x - shadowPadding.left, boundsOrigin.y - shadowPadding.top);
|
||||
|
||||
// Fill background
|
||||
if (!isRasterizing) {
|
||||
UIColor *backgroundColor = parameters.backgroundColor;
|
||||
@@ -450,28 +441,20 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
UIRectFillUsingBlendMode(CGContextGetClipBoundingBox(context), kCGBlendModeCopy);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Draw shadow
|
||||
[[parameters.renderer shadower] setShadowInContext:context];
|
||||
|
||||
[[renderer shadower] setShadowInContext:context];
|
||||
|
||||
// Draw text
|
||||
bounds.origin = parameters.textOrigin;
|
||||
[parameters.renderer drawInContext:context bounds:bounds];
|
||||
|
||||
bounds.origin = textOrigin;
|
||||
[renderer drawInContext:context bounds:bounds];
|
||||
|
||||
CGContextRestoreGState(context);
|
||||
}
|
||||
|
||||
- (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer
|
||||
{
|
||||
CGRect bounds = self.bounds;
|
||||
[self _invalidateRendererIfNeededForBoundsSize:bounds.size];
|
||||
|
||||
// Offset the text origin by any shadow padding
|
||||
UIEdgeInsets shadowPadding = [self shadowPadding];
|
||||
CGPoint textOrigin = CGPointMake(bounds.origin.x - shadowPadding.left, bounds.origin.y - shadowPadding.top);
|
||||
return [[ASTextNodeDrawParameters alloc] initWithRenderer:[self _renderer]
|
||||
textOrigin:textOrigin
|
||||
backgroundColor:self.backgroundColor];
|
||||
return [[ASTextNodeDrawParameters alloc] initWithBounds:self.bounds backgroundColor:self.backgroundColor];
|
||||
}
|
||||
|
||||
#pragma mark - Attributes
|
||||
@@ -1031,9 +1014,15 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
}
|
||||
}
|
||||
|
||||
//only safe to call on main thread, because [self _renderer] is only safe to call on the main thread
|
||||
- (UIEdgeInsets)shadowPadding
|
||||
{
|
||||
return [self _renderer].shadower.shadowPadding;
|
||||
return [self shadowPaddingWithRenderer:[self _renderer]];
|
||||
}
|
||||
|
||||
- (UIEdgeInsets)shadowPaddingWithRenderer:(ASTextKitRenderer *)renderer
|
||||
{
|
||||
return renderer.shadower.shadowPadding;
|
||||
}
|
||||
|
||||
#pragma mark - Truncation Message
|
||||
|
||||
@@ -94,6 +94,18 @@ typedef BOOL(^asdisplaynode_iscancelled_block_t)(void);
|
||||
*/
|
||||
+ (UIImage *)displayWithParameters:(id<NSObject>)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock;
|
||||
|
||||
/**
|
||||
* @abstract instance version of drawRect class method
|
||||
* @see drawRect:withParameters:isCancelled:isRasterizing class method
|
||||
*/
|
||||
- (void)drawRect:(CGRect)bounds withParameters:(id <NSObject>)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing;
|
||||
|
||||
/**
|
||||
* @abstract instance version of display class method
|
||||
* @see displayWithParameters:isCancelled class method
|
||||
*/
|
||||
- (UIImage *)displayWithParameters:(id <NSObject>)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled;
|
||||
|
||||
// Called on the main thread only
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#import "ASAssert.h"
|
||||
#import "ASDisplayNodeInternal.h"
|
||||
#import "ASDisplayNode+FrameworkPrivate.h"
|
||||
#import "ASDisplayNode+Beta.h"
|
||||
|
||||
@interface ASDisplayNode () <_ASDisplayLayerDelegate>
|
||||
@end
|
||||
@@ -224,25 +225,32 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync,
|
||||
|
||||
return image;
|
||||
};
|
||||
} else if (_flags.implementsImageDisplay) {
|
||||
} else if (_flags.implementsInstanceImageDisplay || _flags.implementsImageDisplay) {
|
||||
// Capture drawParameters from delegate on main thread
|
||||
id drawParameters = [self drawParameters];
|
||||
|
||||
|
||||
displayBlock = ^id{
|
||||
__ASDisplayLayerIncrementConcurrentDisplayCount(asynchronous, rasterizing);
|
||||
if (isCancelledBlock()) {
|
||||
__ASDisplayLayerDecrementConcurrentDisplayCount(asynchronous, rasterizing);
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
ASDN_DELAY_FOR_DISPLAY();
|
||||
|
||||
UIImage *result = [[self class] displayWithParameters:drawParameters isCancelled:isCancelledBlock];
|
||||
|
||||
UIImage *result = nil;
|
||||
//We can't call _willDisplayNodeContentWithRenderingContext or _didDisplayNodeContentWithRenderingContext because we don't
|
||||
//have a context. We rely on implementors of displayWithParameters:isCancelled: to call
|
||||
if (_flags.implementsInstanceImageDisplay) {
|
||||
result = [self displayWithParameters:drawParameters isCancelled:isCancelledBlock];
|
||||
} else {
|
||||
result = [[self class] displayWithParameters:drawParameters isCancelled:isCancelledBlock];
|
||||
}
|
||||
__ASDisplayLayerDecrementConcurrentDisplayCount(asynchronous, rasterizing);
|
||||
return result;
|
||||
};
|
||||
|
||||
} else if (_flags.implementsDrawRect) {
|
||||
|
||||
} else if (_flags.implementsInstanceDrawRect || _flags.implementsDrawRect) {
|
||||
|
||||
CGRect bounds = self.bounds;
|
||||
if (CGRectIsEmpty(bounds)) {
|
||||
@@ -267,7 +275,20 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync,
|
||||
UIGraphicsBeginImageContextWithOptions(bounds.size, opaque, contentsScaleForDisplay);
|
||||
}
|
||||
|
||||
[[self class] drawRect:bounds withParameters:drawParameters isCancelled:isCancelledBlock isRasterizing:rasterizing];
|
||||
CGContextRef currentContext = UIGraphicsGetCurrentContext();
|
||||
if (currentContext && _willDisplayNodeContentWithRenderingContext) {
|
||||
_willDisplayNodeContentWithRenderingContext(currentContext);
|
||||
}
|
||||
|
||||
if (_flags.implementsInstanceDrawRect) {
|
||||
[self drawRect:bounds withParameters:drawParameters isCancelled:isCancelledBlock isRasterizing:rasterizing];
|
||||
} else {
|
||||
[[self class] drawRect:bounds withParameters:drawParameters isCancelled:isCancelledBlock isRasterizing:rasterizing];
|
||||
}
|
||||
|
||||
if (currentContext && _didDisplayNodeContentWithRenderingContext) {
|
||||
_didDisplayNodeContentWithRenderingContext(currentContext);
|
||||
}
|
||||
|
||||
if (isCancelledBlock()) {
|
||||
if (!rasterizing) {
|
||||
@@ -370,4 +391,28 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync,
|
||||
[_displaySentinel increment];
|
||||
}
|
||||
|
||||
- (ASDisplayNodeContextModifier)willDisplayNodeContentWithRenderingContext
|
||||
{
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
return _willDisplayNodeContentWithRenderingContext;
|
||||
}
|
||||
|
||||
- (ASDisplayNodeContextModifier)didDisplayNodeContentWithRenderingContext
|
||||
{
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
return _didDisplayNodeContentWithRenderingContext;
|
||||
}
|
||||
|
||||
- (void)setWillDisplayNodeContentWithRenderingContext:(ASDisplayNodeContextModifier)contextModifier
|
||||
{
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
_willDisplayNodeContentWithRenderingContext = contextModifier;
|
||||
}
|
||||
|
||||
- (void)setDidDisplayNodeContentWithRenderingContext:(ASDisplayNodeContextModifier)contextModifier;
|
||||
{
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
_didDisplayNodeContentWithRenderingContext = contextModifier;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#import "ASSentinel.h"
|
||||
#import "ASThread.h"
|
||||
#import "ASLayoutOptions.h"
|
||||
#import "_ASDisplayLayer.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
|
||||
@@ -87,7 +87,9 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides)
|
||||
unsigned hasCustomDrawingPriority:1;
|
||||
|
||||
// whether custom drawing is enabled
|
||||
unsigned implementsInstanceDrawRect:1;
|
||||
unsigned implementsDrawRect:1;
|
||||
unsigned implementsInstanceImageDisplay:1;
|
||||
unsigned implementsImageDisplay:1;
|
||||
unsigned implementsDrawParameters:1;
|
||||
|
||||
@@ -101,6 +103,9 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides)
|
||||
|
||||
ASDisplayNodeExtraIvars _extra;
|
||||
|
||||
ASDisplayNodeContextModifier _willDisplayNodeContentWithRenderingContext;
|
||||
ASDisplayNodeContextModifier _didDisplayNodeContentWithRenderingContext;
|
||||
|
||||
#if TIME_DISPLAYNODE_OPS
|
||||
@public
|
||||
NSTimeInterval _debugTimeToCreateView;
|
||||
|
||||
Reference in New Issue
Block a user