Switch to instance methods of draw and display

This patch switches to instance methods of draw and display for
ASTextNode and ASImageNode to attempt to increase their performance.

It also fixes some thread safety issues in ASImageNode which appear
to have been regressions (though probably not hit very often).

And it sets up work for allowing modification of CGContexts before
and after a node's contents are drawn.
This commit is contained in:
Garrett Moon 2016-01-27 20:05:03 -08:00
parent 5004935d82
commit 01c1680904
7 changed files with 207 additions and 107 deletions

View File

@ -6,6 +6,8 @@
* of patent rights can be found in the PATENTS file in the same directory. * of patent rights can be found in the PATENTS file in the same directory.
*/ */
#import "_ASDisplayLayer.h"
@interface ASDisplayNode (Beta) @interface ASDisplayNode (Beta)
+ (BOOL)shouldUseNewRenderingRange; + (BOOL)shouldUseNewRenderingRange;
@ -20,4 +22,25 @@
*/ */
- (void)recursivelyEnsureDisplaySynchronously:(BOOL)synchronously; - (void)recursivelyEnsureDisplaySynchronously:(BOOL)synchronously;
- (void)drawRect:(CGRect)bounds withParameters:(id <NSObject>)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing;
- (UIImage *)displayWithParameters:(id <NSObject>)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled;
/**
* @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 willDisplayNodeContentBlock;
/**
* @abstract allow modification of a context after the node's content is drawn
*
* @discussion
*/
@property (nonatomic, strong) ASDisplayNodeContextModifier didDisplayNodeContentBlock;
@end @end

View File

@ -37,6 +37,11 @@ typedef CALayer * _Nonnull(^ASDisplayNodeLayerBlock)();
*/ */
typedef void (^ASDisplayNodeDidLoadBlock)(ASDisplayNode * _Nonnull node); typedef void (^ASDisplayNodeDidLoadBlock)(ASDisplayNode * _Nonnull node);
/**
* ASDisplayNode will / did render node content in context.
*/
typedef void (^ASDisplayNodeContextModifier)(CGContextRef context);
/** /**
Interface state is available on ASDisplayNode and ASViewController, and 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 allows checking whether a node is in an interface situation where it is prudent to trigger certain

View File

@ -110,8 +110,12 @@ static struct ASDisplayNodeFlags GetASDisplayNodeFlags(Class c, ASDisplayNode *i
flags.implementsImageDisplay = ([c respondsToSelector:@selector(displayWithParameters:isCancelled:)] ? 1 : 0); flags.implementsImageDisplay = ([c respondsToSelector:@selector(displayWithParameters:isCancelled:)] ? 1 : 0);
if (instance) { if (instance) {
flags.implementsDrawParameters = ([instance respondsToSelector:@selector(drawParametersForAsyncLayer:)] ? 1 : 0); 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 { } else {
flags.implementsDrawParameters = ([c instancesRespondToSelector:@selector(drawParametersForAsyncLayer:)] ? 1 : 0); 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; 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 // Helper method to summarize whether or not the node run through the display process
- (BOOL)__implementsDisplay - (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 - (void)_setupPlaceholderLayer

View File

@ -14,6 +14,7 @@
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h> #import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
#import <AsyncDisplayKit/ASDisplayNodeInternal.h> #import <AsyncDisplayKit/ASDisplayNodeInternal.h>
#import <AsyncDisplayKit/ASDisplayNodeExtras.h> #import <AsyncDisplayKit/ASDisplayNodeExtras.h>
#import <AsyncDisplayKit/ASDisplayNode+Beta.h>
#import "ASImageNode+CGExtras.h" #import "ASImageNode+CGExtras.h"
@ -22,42 +23,34 @@
@interface _ASImageNodeDrawParameters : NSObject @interface _ASImageNodeDrawParameters : NSObject
@property (nonatomic, assign, readonly) BOOL cropEnabled;
@property (nonatomic, assign) BOOL opaque; @property (nonatomic, assign) BOOL opaque;
@property (nonatomic, retain) UIImage *image;
@property (nonatomic, assign) CGRect bounds; @property (nonatomic, assign) CGRect bounds;
@property (nonatomic, assign) CGFloat contentsScale; @property (nonatomic, assign) CGFloat contentsScale;
@property (nonatomic, retain) UIColor *backgroundColor; @property (nonatomic, retain) UIColor *backgroundColor;
@property (nonatomic, assign) UIViewContentMode contentMode; @property (nonatomic, assign) UIViewContentMode contentMode;
@property (nonatomic, assign) CGRect cropRect;
@property (nonatomic, copy) asimagenode_modification_block_t imageModificationBlock;
@end @end
// TODO: eliminate explicit parameters with a set of keys copied from the node // TODO: eliminate explicit parameters with a set of keys copied from the node
@implementation _ASImageNodeDrawParameters @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]; self = [self init];
if (!self) return nil; if (!self) return nil;
_cropEnabled = cropEnabled;
_opaque = opaque; _opaque = opaque;
_image = image;
_bounds = bounds; _bounds = bounds;
_contentsScale = contentsScale; _contentsScale = contentsScale;
_backgroundColor = backgroundColor; _backgroundColor = backgroundColor;
_contentMode = contentMode; _contentMode = contentMode;
_cropRect = cropRect;
_imageModificationBlock = [imageModificationBlock copy];
return self; return self;
} }
- (NSString *)description - (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 @end
@ -78,6 +71,7 @@
} }
@synthesize image = _image; @synthesize image = _image;
@synthesize imageModificationBlock = _imageModificationBlock;
- (id)init - (id)init
{ {
@ -153,40 +147,51 @@
{ {
BOOL hasValidCropBounds = _cropEnabled && !CGRectIsNull(_cropDisplayBounds) && !CGRectIsEmpty(_cropDisplayBounds); BOOL hasValidCropBounds = _cropEnabled && !CGRectIsNull(_cropDisplayBounds) && !CGRectIsEmpty(_cropDisplayBounds);
return [[_ASImageNodeDrawParameters alloc] initWithCrop:_cropEnabled return [[_ASImageNodeDrawParameters alloc] initWithBounds:self.bounds
opaque:self.opaque opaque:self.opaque
image:self.image
bounds:(hasValidCropBounds ? _cropDisplayBounds : self.bounds)
contentsScale:self.contentsScaleForDisplay contentsScale:self.contentsScaleForDisplay
backgroundColor:self.backgroundColor backgroundColor:self.backgroundColor
contentMode:self.contentMode contentMode:self.contentMode];
cropRect:self.cropRect
imageModificationBlock:self.imageModificationBlock];
} }
+ (UIImage *)displayWithParameters:(_ASImageNodeDrawParameters *)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled - (UIImage *)displayWithParameters:(_ASImageNodeDrawParameters *)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled
{ {
UIImage *image = parameters.image; ASDN::MutexLocker l(_imageLock);
UIImage *image = _image;
if (!image) { if (!image) {
return nil; return nil;
} }
ASDisplayNodeAssert(parameters.contentsScale > 0, @"invalid contentsScale at display time"); BOOL cropEnabled = _cropEnabled;
CGFloat contentsScale = _contentsScaleForDisplay;
CGRect cropDisplayBounds = _cropDisplayBounds;
CGRect cropRect = _cropRect;
asimagenode_modification_block_t imageModificationBlock = _imageModificationBlock;
ASDN::MutexUnlocker u(_imageLock);
ASDisplayNodeContextModifier preContextBlock = self.willDisplayNodeContentBlock;
ASDisplayNodeContextModifier postContextBlock = self.didDisplayNodeContentBlock;
BOOL hasValidCropBounds = cropEnabled && !CGRectIsNull(cropDisplayBounds) && !CGRectIsEmpty(cropDisplayBounds);
CGRect bounds = (hasValidCropBounds ? cropDisplayBounds : parameters.bounds);
BOOL isOpaque = parameters.opaque;
CGFloat contentsScaleForDisplay = parameters.contentsScale;
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 // if the image is resizable, bail early since the image has likely already been configured
BOOL stretchable = !UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero); BOOL stretchable = !UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero);
if (stretchable) { if (stretchable) {
if (parameters.imageModificationBlock != NULL) { if (imageModificationBlock != NULL) {
image = parameters.imageModificationBlock(image); image = imageModificationBlock(image);
} }
return image; return image;
} }
CGRect bounds = parameters.bounds;
CGFloat contentsScale = parameters.contentsScale;
UIViewContentMode contentMode = parameters.contentMode;
CGSize imageSize = image.size; CGSize imageSize = image.size;
CGSize imageSizeInPixels = CGSizeMake(imageSize.width * image.scale, imageSize.height * image.scale); CGSize imageSizeInPixels = CGSizeMake(imageSize.width * image.scale, imageSize.height * image.scale);
CGSize boundsSizeInPixels = CGSizeMake(floorf(bounds.size.width * contentsScale), floorf(bounds.size.height * contentsScale)); CGSize boundsSizeInPixels = CGSizeMake(floorf(bounds.size.width * contentsScale), floorf(bounds.size.height * contentsScale));
@ -206,14 +211,14 @@
} }
// If we're not supposed to do any cropping, just decode image at original size // 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; backingSize = imageSizeInPixels;
imageDrawRect = (CGRect){.size = backingSize}; imageDrawRect = (CGRect){.size = backingSize};
} else { } else {
ASCroppedImageBackingSizeAndDrawRectInBounds(imageSizeInPixels, ASCroppedImageBackingSizeAndDrawRectInBounds(imageSizeInPixels,
boundsSizeInPixels, boundsSizeInPixels,
contentMode, contentMode,
parameters.cropRect, cropRect,
&backingSize, &backingSize,
&imageDrawRect); &imageDrawRect);
} }
@ -227,14 +232,19 @@
// Use contentsScale of 1.0 and do the contentsScale handling in boundsSizeInPixels so ASCroppedImageBackingSizeAndDrawRectInBounds // Use contentsScale of 1.0 and do the contentsScale handling in boundsSizeInPixels so ASCroppedImageBackingSizeAndDrawRectInBounds
// will do its rounding on pixel instead of point boundaries // 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 view is opaque, fill the context with background color
if (parameters.opaque && parameters.backgroundColor) { if (isOpaque && backgroundColor) {
[parameters.backgroundColor setFill]; [backgroundColor setFill];
UIRectFill({ .size = backingSize }); UIRectFill({ .size = backingSize });
} }
CGContextRef context = UIGraphicsGetCurrentContext();
if (preContextBlock) {
preContextBlock(context);
}
// iOS 9 appears to contain a thread safety regression when drawing the same CGImageRef on // 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. // 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, // The issue is present in Mac OS X El Capitan and has been seen hanging Pro apps like Adobe Premier,
@ -251,6 +261,10 @@
[image drawInRect:imageDrawRect]; [image drawInRect:imageDrawRect];
} }
if (postContextBlock) {
postContextBlock(context);
}
if (isCancelled()) { if (isCancelled()) {
UIGraphicsEndImageContext(); UIGraphicsEndImageContext();
return nil; return nil;
@ -260,8 +274,8 @@
UIGraphicsEndImageContext(); UIGraphicsEndImageContext();
if (parameters.imageModificationBlock != NULL) { if (imageModificationBlock != NULL) {
result = parameters.imageModificationBlock(result); result = imageModificationBlock(result);
} }
return result; return result;
@ -271,8 +285,9 @@
{ {
[super displayDidFinish]; [super displayDidFinish];
ASDN::MutexLocker l(_imageLock);
// If we've got a block to perform after displaying, do it. // If we've got a block to perform after displaying, do it.
if (self.image && _displayCompletionBlock) { if (_image && _displayCompletionBlock) {
// FIXME: _displayCompletionBlock is not protected by lock // FIXME: _displayCompletionBlock is not protected by lock
_displayCompletionBlock(NO); _displayCompletionBlock(NO);
@ -291,6 +306,7 @@
// Stash the block and call-site queue. We'll invoke it in -displayDidFinish. // Stash the block and call-site queue. We'll invoke it in -displayDidFinish.
// FIXME: _displayCompletionBlock not protected by lock // FIXME: _displayCompletionBlock not protected by lock
ASDN::MutexLocker l(_imageLock);
if (_displayCompletionBlock != displayCompletionBlock) { if (_displayCompletionBlock != displayCompletionBlock) {
_displayCompletionBlock = [displayCompletionBlock copy]; _displayCompletionBlock = [displayCompletionBlock copy];
} }
@ -301,6 +317,7 @@
#pragma mark - Cropping #pragma mark - Cropping
- (BOOL)isCropEnabled - (BOOL)isCropEnabled
{ {
ASDN::MutexLocker l(_imageLock);
return _cropEnabled; return _cropEnabled;
} }
@ -311,6 +328,7 @@
- (void)setCropEnabled:(BOOL)cropEnabled recropImmediately:(BOOL)recropImmediately inBounds:(CGRect)cropBounds - (void)setCropEnabled:(BOOL)cropEnabled recropImmediately:(BOOL)recropImmediately inBounds:(CGRect)cropBounds
{ {
ASDN::MutexLocker l(_imageLock);
if (_cropEnabled == cropEnabled) if (_cropEnabled == cropEnabled)
return; return;
@ -331,11 +349,13 @@
- (CGRect)cropRect - (CGRect)cropRect
{ {
ASDN::MutexLocker l(_imageLock);
return _cropRect; return _cropRect;
} }
- (void)setCropRect:(CGRect)cropRect - (void)setCropRect:(CGRect)cropRect
{ {
ASDN::MutexLocker l(_imageLock);
if (CGRectEqualToRect(_cropRect, cropRect)) if (CGRectEqualToRect(_cropRect, cropRect))
return; return;
@ -354,6 +374,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 @end

View File

@ -34,13 +34,7 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
@interface ASTextNodeDrawParameters : NSObject @interface ASTextNodeDrawParameters : NSObject
- (instancetype)initWithRenderer:(ASTextKitRenderer *)renderer @property (nonatomic, assign, readonly) CGRect bounds;
textOrigin:(CGPoint)textOrigin
backgroundColor:(UIColor *)backgroundColor;
@property (nonatomic, strong, readonly) ASTextKitRenderer *renderer;
@property (nonatomic, assign, readonly) CGPoint textOrigin;
@property (nonatomic, strong, readonly) UIColor *backgroundColor; @property (nonatomic, strong, readonly) UIColor *backgroundColor;
@ -48,30 +42,16 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
@implementation ASTextNodeDrawParameters @implementation ASTextNodeDrawParameters
- (instancetype)initWithRenderer:(ASTextKitRenderer *)renderer - (instancetype)initWithBounds:(CGRect)bounds
textOrigin:(CGPoint)textOrigin
backgroundColor:(UIColor *)backgroundColor backgroundColor:(UIColor *)backgroundColor
{ {
if (self = [super init]) { if (self = [super init]) {
_renderer = renderer; _bounds = bounds;
_textOrigin = textOrigin;
_backgroundColor = backgroundColor; _backgroundColor = backgroundColor;
} }
return self; 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 @end
@interface ASTextNode () <UIGestureRecognizerDelegate> @interface ASTextNode () <UIGestureRecognizerDelegate>
@ -237,11 +217,17 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
#pragma mark - Renderer Management #pragma mark - Renderer Management
//only safe to call on the main thread
- (ASTextKitRenderer *)_renderer - (ASTextKitRenderer *)_renderer
{
return [self _rendererWithBounds:self.bounds];
}
- (ASTextKitRenderer *)_rendererWithBounds:(CGRect)bounds
{ {
ASDN::MutexLocker l(_rendererLock); ASDN::MutexLocker l(_rendererLock);
if (_renderer == nil) { 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] _renderer = [[ASTextKitRenderer alloc] initWithTextKitAttributes:[self _rendererAttributes]
constrainedSize:constrainedSize]; constrainedSize:constrainedSize];
} }
@ -420,13 +406,17 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
#pragma mark - Drawing #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(); CGContextRef context = UIGraphicsGetCurrentContext();
ASDisplayNodeAssert(context, @"This is no good without a context."); ASDisplayNodeAssert(context, @"This is no good without a context.");
CGContextSaveGState(context); CGContextSaveGState(context);
ASTextKitRenderer *renderer = [self _rendererWithBounds:parameters.bounds];
UIEdgeInsets shadowPadding = [self shadowPaddingWithRenderer:renderer];
CGPoint textOrigin = CGPointMake(parameters.bounds.origin.x - shadowPadding.left, parameters.bounds.origin.y - shadowPadding.top);
// Fill background // Fill background
if (!isRasterizing) { if (!isRasterizing) {
UIColor *backgroundColor = parameters.backgroundColor; UIColor *backgroundColor = parameters.backgroundColor;
@ -437,26 +427,18 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
} }
// Draw shadow // Draw shadow
[[parameters.renderer shadower] setShadowInContext:context]; [[renderer shadower] setShadowInContext:context];
// Draw text // Draw text
bounds.origin = parameters.textOrigin; bounds.origin = textOrigin;
[parameters.renderer drawInContext:context bounds:bounds]; [renderer drawInContext:context bounds:bounds];
CGContextRestoreGState(context); CGContextRestoreGState(context);
} }
- (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer - (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer
{ {
CGRect bounds = self.bounds; return [[ASTextNodeDrawParameters alloc] initWithBounds:self.bounds backgroundColor:self.backgroundColor];
[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];
} }
#pragma mark - Attributes #pragma mark - Attributes
@ -1016,9 +998,15 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
} }
} }
//only safe to call on main thread
- (UIEdgeInsets)shadowPadding - (UIEdgeInsets)shadowPadding
{ {
return [self _renderer].shadower.shadowPadding; return [self shadowPaddingWithRenderer:[self _renderer]];
}
- (UIEdgeInsets)shadowPaddingWithRenderer:(ASTextKitRenderer *)renderer
{
return renderer.shadower.shadowPadding;
} }
#pragma mark - Truncation Message #pragma mark - Truncation Message

View File

@ -12,6 +12,7 @@
#import "ASAssert.h" #import "ASAssert.h"
#import "ASDisplayNodeInternal.h" #import "ASDisplayNodeInternal.h"
#import "ASDisplayNode+FrameworkPrivate.h" #import "ASDisplayNode+FrameworkPrivate.h"
#import "ASDisplayNode+Beta.h"
@interface ASDisplayNode () <_ASDisplayLayerDelegate> @interface ASDisplayNode () <_ASDisplayLayerDelegate>
@end @end
@ -224,7 +225,7 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync,
return image; return image;
}; };
} else if (_flags.implementsImageDisplay) { } else if (_flags.implementsInstanceImageDisplay || _flags.implementsImageDisplay) {
// Capture drawParameters from delegate on main thread // Capture drawParameters from delegate on main thread
id drawParameters = [self drawParameters]; id drawParameters = [self drawParameters];
@ -237,12 +238,17 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync,
ASDN_DELAY_FOR_DISPLAY(); ASDN_DELAY_FOR_DISPLAY();
UIImage *result = [[self class] displayWithParameters:drawParameters isCancelled:isCancelledBlock]; UIImage *result = nil;
if (_flags.implementsInstanceImageDisplay) {
result = [self displayWithParameters:drawParameters isCancelled:isCancelledBlock];
} else {
result = [[self class] displayWithParameters:drawParameters isCancelled:isCancelledBlock];
}
__ASDisplayLayerDecrementConcurrentDisplayCount(asynchronous, rasterizing); __ASDisplayLayerDecrementConcurrentDisplayCount(asynchronous, rasterizing);
return result; return result;
}; };
} else if (_flags.implementsDrawRect) { } else if (_flags.implementsInstanceDrawRect || _flags.implementsDrawRect) {
CGRect bounds = self.bounds; CGRect bounds = self.bounds;
if (CGRectIsEmpty(bounds)) { if (CGRectIsEmpty(bounds)) {
@ -267,7 +273,20 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync,
UIGraphicsBeginImageContextWithOptions(bounds.size, opaque, contentsScaleForDisplay); UIGraphicsBeginImageContextWithOptions(bounds.size, opaque, contentsScaleForDisplay);
} }
CGContextRef currentContext = UIGraphicsGetCurrentContext();
if (_preContextModifier) {
_preContextModifier(currentContext);
}
if (_flags.implementsInstanceDrawRect) {
[self drawRect:bounds withParameters:drawParameters isCancelled:isCancelledBlock isRasterizing:rasterizing];
} else {
[[self class] drawRect:bounds withParameters:drawParameters isCancelled:isCancelledBlock isRasterizing:rasterizing]; [[self class] drawRect:bounds withParameters:drawParameters isCancelled:isCancelledBlock isRasterizing:rasterizing];
}
if (_postContextModifier) {
_postContextModifier(currentContext);
}
if (isCancelledBlock()) { if (isCancelledBlock()) {
if (!rasterizing) { if (!rasterizing) {
@ -370,4 +389,28 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync,
[_displaySentinel increment]; [_displaySentinel increment];
} }
- (ASDisplayNodeContextModifier)willDisplayNodeContentBlock
{
ASDN::MutexLocker l(_propertyLock);
return _preContextModifier;
}
- (ASDisplayNodeContextModifier)didDisplayNodeContentBlock
{
ASDN::MutexLocker l(_propertyLock);
return _postContextModifier;
}
- (void)setWillDisplayNodeContentBlock:(ASDisplayNodeContextModifier)contextModifier
{
ASDN::MutexLocker l(_propertyLock);
_preContextModifier = contextModifier;
}
- (void)setDidDisplayNodeContentBlock:(ASDisplayNodeContextModifier)contextModifier;
{
ASDN::MutexLocker l(_propertyLock);
_postContextModifier = contextModifier;
}
@end @end

View File

@ -87,7 +87,9 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides)
unsigned hasCustomDrawingPriority:1; unsigned hasCustomDrawingPriority:1;
// whether custom drawing is enabled // whether custom drawing is enabled
unsigned implementsInstanceDrawRect:1;
unsigned implementsDrawRect:1; unsigned implementsDrawRect:1;
unsigned implementsInstanceImageDisplay:1;
unsigned implementsImageDisplay:1; unsigned implementsImageDisplay:1;
unsigned implementsDrawParameters:1; unsigned implementsDrawParameters:1;
@ -101,6 +103,9 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides)
ASDisplayNodeExtraIvars _extra; ASDisplayNodeExtraIvars _extra;
ASDisplayNodeContextModifier _preContextModifier;
ASDisplayNodeContextModifier _postContextModifier;
#if TIME_DISPLAYNODE_OPS #if TIME_DISPLAYNODE_OPS
@public @public
NSTimeInterval _debugTimeToCreateView; NSTimeInterval _debugTimeToCreateView;