From bc361caf5ee6a8a5593a1fe7d0b292faf8c55e95 Mon Sep 17 00:00:00 2001 From: appleguy Date: Sun, 9 Jul 2017 10:54:39 -0700 Subject: [PATCH] [ASDisplayNode] Allow setting stretchable contents on nodes; add bridged properties. (#429) This makes it much easier to use the ASCoreAnimationExtras method which offers by far the most efficient way to display a stretchable image. In the future, we should move that function to UIImage+ASConvenience or another header where it can be more easily found and enjoyed! --- Source/ASDisplayNode.h | 46 +++++++++++-------- Source/ASDisplayNode.mm | 2 +- Source/Details/UIView+ASConvenience.h | 6 ++- Source/Private/ASDisplayNode+AsyncDisplay.mm | 2 +- Source/Private/ASDisplayNode+UIViewBridge.mm | 48 ++++++++++++++++++++ Source/Private/_ASCoreAnimationExtras.h | 23 +++++++++- Source/Private/_ASCoreAnimationExtras.mm | 18 +++++--- Source/Private/_ASPendingState.mm | 36 +++++++++++++-- 8 files changed, 147 insertions(+), 34 deletions(-) diff --git a/Source/ASDisplayNode.h b/Source/ASDisplayNode.h index 96bb2c3c5..f97f4dfc9 100644 --- a/Source/ASDisplayNode.h +++ b/Source/ASDisplayNode.h @@ -621,29 +621,32 @@ extern NSInteger const ASDefaultDrawingPriority; */ - (void)layoutIfNeeded; -@property (nonatomic, strong, nullable) id contents; // default=nil +@property (nonatomic, assign) CGRect frame; // default=CGRectZero +@property (nonatomic, assign) CGRect bounds; // default=CGRectZero +@property (nonatomic, assign) CGPoint position; // default=CGPointZero +@property (nonatomic, assign) CGFloat alpha; // default=1.0f + @property (nonatomic, assign) BOOL clipsToBounds; // default==NO +@property (nonatomic, getter=isHidden) BOOL hidden; // default==NO @property (nonatomic, getter=isOpaque) BOOL opaque; // default==YES -@property (nonatomic, assign) BOOL allowsGroupOpacity; -@property (nonatomic, assign) BOOL allowsEdgeAntialiasing; -@property (nonatomic, assign) unsigned int edgeAntialiasingMask; // default==all values from CAEdgeAntialiasingMask +@property (nonatomic, strong, nullable) id contents; // default=nil +@property (nonatomic, assign) CGRect contentsRect; // default={0,0,1,1}. @see CALayer.h for details. +@property (nonatomic, assign) CGRect contentsCenter; // default={0,0,1,1}. @see CALayer.h for details. +@property (nonatomic, assign) CGFloat contentsScale; // default=1.0f. See @contentsScaleForDisplay for details. +@property (nonatomic, assign) CGFloat rasterizationScale; // default=1.0f. -@property (nonatomic, getter=isHidden) BOOL hidden; // default==NO -@property (nonatomic, assign) BOOL needsDisplayOnBoundsChange; // default==NO -@property (nonatomic, assign) BOOL autoresizesSubviews; // default==YES (undefined for layer-backed nodes) -@property (nonatomic, assign) UIViewAutoresizing autoresizingMask; // default==UIViewAutoresizingNone (undefined for layer-backed nodes) -@property (nonatomic, assign) CGFloat alpha; // default=1.0f -@property (nonatomic, assign) CGRect bounds; // default=CGRectZero -@property (nonatomic, assign) CGRect frame; // default=CGRectZero @property (nonatomic, assign) CGPoint anchorPoint; // default={0.5, 0.5} @property (nonatomic, assign) CGFloat zPosition; // default=0.0 -@property (nonatomic, assign) CGPoint position; // default=CGPointZero @property (nonatomic, assign) CGFloat cornerRadius; // default=0.0 -@property (nonatomic, assign) CGFloat contentsScale; // default=1.0f. See @contentsScaleForDisplay for more info @property (nonatomic, assign) CATransform3D transform; // default=CATransform3DIdentity @property (nonatomic, assign) CATransform3D subnodeTransform; // default=CATransform3DIdentity +@property (nonatomic, assign, getter=isUserInteractionEnabled) BOOL userInteractionEnabled; // default=YES (NO for layer-backed nodes) +#if TARGET_OS_IOS +@property (nonatomic, assign, getter=isExclusiveTouch) BOOL exclusiveTouch; // default=NO +#endif + /** * @abstract The node view's background color. * @@ -652,8 +655,8 @@ extern NSInteger const ASDefaultDrawingPriority; */ @property (nonatomic, strong, nullable) UIColor *backgroundColor; // default=nil -@property (nonatomic, strong, null_resettable) UIColor *tintColor; // default=Blue -- (void)tintColorDidChange; // Notifies the node when the tintColor has changed. +@property (nonatomic, strong, null_resettable) UIColor *tintColor; // default=Blue +- (void)tintColorDidChange; // Notifies the node when the tintColor has changed. /** * @abstract A flag used to determine how a node lays out its content when its bounds change. @@ -664,12 +667,9 @@ extern NSInteger const ASDefaultDrawingPriority; * contentMode for your content while it's being re-rendered. */ @property (nonatomic, assign) UIViewContentMode contentMode; // default=UIViewContentModeScaleToFill +@property (nonatomic, copy) NSString *contentsGravity; // Use .contentMode in preference when possible. @property (nonatomic, assign) UISemanticContentAttribute semanticContentAttribute; // default=Unspecified -@property (nonatomic, assign, getter=isUserInteractionEnabled) BOOL userInteractionEnabled; // default=YES (NO for layer-backed nodes) -#if TARGET_OS_IOS -@property (nonatomic, assign, getter=isExclusiveTouch) BOOL exclusiveTouch; // default=NO -#endif @property (nonatomic, nullable) CGColorRef shadowColor; // default=opaque rgb black @property (nonatomic, assign) CGFloat shadowOpacity; // default=0.0 @property (nonatomic, assign) CGSize shadowOffset; // default=(0, -3) @@ -677,6 +677,14 @@ extern NSInteger const ASDefaultDrawingPriority; @property (nonatomic, assign) CGFloat borderWidth; // default=0 @property (nonatomic, nullable) CGColorRef borderColor; // default=opaque rgb black +@property (nonatomic, assign) BOOL allowsGroupOpacity; +@property (nonatomic, assign) BOOL allowsEdgeAntialiasing; +@property (nonatomic, assign) unsigned int edgeAntialiasingMask; // default==all values from CAEdgeAntialiasingMask + +@property (nonatomic, assign) BOOL needsDisplayOnBoundsChange; // default==NO +@property (nonatomic, assign) BOOL autoresizesSubviews; // default==YES (undefined for layer-backed nodes) +@property (nonatomic, assign) UIViewAutoresizing autoresizingMask; // default==UIViewAutoresizingNone (undefined for layer-backed nodes) + // UIResponder methods // By default these fall through to the underlying view, but can be overridden. - (BOOL)canBecomeFirstResponder; // default==NO diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index 95547a084..71ae46b38 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -2442,7 +2442,7 @@ - (void)_locked_setupPlaceholderLayerIfNeeded if (_placeholderImage) { BOOL stretchable = !UIEdgeInsetsEqualToEdgeInsets(_placeholderImage.capInsets, UIEdgeInsetsZero); if (stretchable) { - ASDisplayNodeSetupLayerContentsWithResizableImage(_placeholderLayer, _placeholderImage); + ASDisplayNodeSetResizableContents(_placeholderLayer, _placeholderImage); } else { _placeholderLayer.contentsScale = self.contentsScale; _placeholderLayer.contents = (id)_placeholderImage.CGImage; diff --git a/Source/Details/UIView+ASConvenience.h b/Source/Details/UIView+ASConvenience.h index 98a36d89e..537a4ae9e 100644 --- a/Source/Details/UIView+ASConvenience.h +++ b/Source/Details/UIView+ASConvenience.h @@ -29,9 +29,13 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) CGPoint position; @property (nonatomic, assign) CGFloat zPosition; @property (nonatomic, assign) CGPoint anchorPoint; -@property (nullable, nonatomic, strong) id contents; @property (nonatomic, assign) CGFloat cornerRadius; +@property (nullable, nonatomic, strong) id contents; +@property (nonatomic, copy) NSString *contentsGravity; +@property (nonatomic, assign) CGRect contentsRect; +@property (nonatomic, assign) CGRect contentsCenter; @property (nonatomic, assign) CGFloat contentsScale; +@property (nonatomic, assign) CGFloat rasterizationScale; @property (nonatomic, assign) CATransform3D transform; @property (nonatomic, assign) CATransform3D sublayerTransform; @property (nonatomic, assign) BOOL needsDisplayOnBoundsChange; diff --git a/Source/Private/ASDisplayNode+AsyncDisplay.mm b/Source/Private/ASDisplayNode+AsyncDisplay.mm index c24dfc5dc..00bb0ca66 100644 --- a/Source/Private/ASDisplayNode+AsyncDisplay.mm +++ b/Source/Private/ASDisplayNode+AsyncDisplay.mm @@ -341,7 +341,7 @@ - (void)displayAsyncLayer:(_ASDisplayLayer *)asyncLayer asynchronously:(BOOL)asy UIImage *image = (UIImage *)value; BOOL stretchable = (NO == UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero)); if (stretchable) { - ASDisplayNodeSetupLayerContentsWithResizableImage(layer, image); + ASDisplayNodeSetResizableContents(layer, image); } else { layer.contentsScale = self.contentsScale; layer.contents = (id)image.CGImage; diff --git a/Source/Private/ASDisplayNode+UIViewBridge.mm b/Source/Private/ASDisplayNode+UIViewBridge.mm index 7027d5f5d..cd3705921 100644 --- a/Source/Private/ASDisplayNode+UIViewBridge.mm +++ b/Source/Private/ASDisplayNode+UIViewBridge.mm @@ -195,6 +195,42 @@ - (void)setCornerRadius:(CGFloat)newCornerRadius _setToLayer(cornerRadius, newCornerRadius); } +- (NSString *)contentsGravity +{ + _bridge_prologue_read; + return _getFromLayer(contentsGravity); +} + +- (void)setContentsGravity:(NSString *)newContentsGravity +{ + _bridge_prologue_write; + _setToLayer(contentsGravity, newContentsGravity); +} + +- (CGRect)contentsRect +{ + _bridge_prologue_read; + return _getFromLayer(contentsRect); +} + +- (void)setContentsRect:(CGRect)newContentsRect +{ + _bridge_prologue_write; + _setToLayer(contentsRect, newContentsRect); +} + +- (CGRect)contentsCenter +{ + _bridge_prologue_read; + return _getFromLayer(contentsCenter); +} + +- (void)setContentsCenter:(CGRect)newContentsCenter +{ + _bridge_prologue_write; + _setToLayer(contentsCenter, newContentsCenter); +} + - (CGFloat)contentsScale { _bridge_prologue_read; @@ -207,6 +243,18 @@ - (void)setContentsScale:(CGFloat)newContentsScale _setToLayer(contentsScale, newContentsScale); } +- (CGFloat)rasterizationScale +{ + _bridge_prologue_read; + return _getFromLayer(rasterizationScale); +} + +- (void)setRasterizationScale:(CGFloat)newRasterizationScale +{ + _bridge_prologue_write; + _setToLayer(rasterizationScale, newRasterizationScale); +} + - (CGRect)bounds { _bridge_prologue_read; diff --git a/Source/Private/_ASCoreAnimationExtras.h b/Source/Private/_ASCoreAnimationExtras.h index 9cbd5f5d6..327254917 100644 --- a/Source/Private/_ASCoreAnimationExtras.h +++ b/Source/Private/_ASCoreAnimationExtras.h @@ -18,10 +18,28 @@ #import #import +#import ASDISPLAYNODE_EXTERN_C_BEGIN -extern void ASDisplayNodeSetupLayerContentsWithResizableImage(CALayer *layer, UIImage *image); +// This protocol defines the core properties that ASDisplayNode and CALayer share, for managing contents. +@protocol ASResizableContents +@required +@property id contents; +@property CGRect contentsRect; +@property CGRect contentsCenter; +@property CGFloat contentsScale; +@property CGFloat rasterizationScale; +@property NSString *contentsGravity; +@end + +@interface CALayer (ASResizableContents) +@end +@interface ASDisplayNode (ASResizableContents) +@end + +// This function can operate on either an ASDisplayNode (including un-loaded) or CALayer directly. +extern void ASDisplayNodeSetResizableContents(id obj, UIImage *image); /** Turns a value of UIViewContentMode to a string for debugging or serialization @@ -67,4 +85,7 @@ extern UIImage *ASDisplayNodeStretchableBoxContentsWithColor(UIColor *color, CGS */ extern BOOL ASDisplayNodeLayerHasAnimations(CALayer *layer); +// This function is a less generalized version of ASDisplayNodeSetResizableContents. +extern void ASDisplayNodeSetupLayerContentsWithResizableImage(CALayer *layer, UIImage *image) ASDISPLAYNODE_DEPRECATED; + ASDISPLAYNODE_EXTERN_C_END diff --git a/Source/Private/_ASCoreAnimationExtras.mm b/Source/Private/_ASCoreAnimationExtras.mm index 62c96203c..3cd18c8e8 100644 --- a/Source/Private/_ASCoreAnimationExtras.mm +++ b/Source/Private/_ASCoreAnimationExtras.mm @@ -20,17 +20,21 @@ #import extern void ASDisplayNodeSetupLayerContentsWithResizableImage(CALayer *layer, UIImage *image) +{ + ASDisplayNodeSetResizableContents(layer, image); +} + +extern void ASDisplayNodeSetResizableContents(id obj, UIImage *image) { // FIXME: This method does not currently handle UIImageResizingModeTile, which is the default on iOS 6. // I'm not sure of a way to use CALayer directly to perform such tiling on the GPU, though the stretch is handled by the GPU, // and CALayer.h documents the fact that contentsCenter is used to stretch the pixels. if (image) { - // Image may not actually be stretchable in one or both dimensions; this is handled - layer.contents = (id)[image CGImage]; - layer.contentsScale = [image scale]; - layer.rasterizationScale = [image scale]; + obj.contents = (id)[image CGImage]; + obj.contentsScale = [image scale]; + obj.rasterizationScale = [image scale]; CGSize imageSize = [image size]; ASDisplayNodeCAssert(image.resizingMode == UIImageResizingModeStretch || UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero), @@ -51,11 +55,11 @@ extern void ASDisplayNodeSetupLayerContentsWithResizableImage(CALayer *layer, UI contentsCenter.origin.y = ((insets.top + halfPixelFudge) / imageSize.height); contentsCenter.size.height = (imageSize.height - (insets.top + insets.bottom + 1.f) + otherPixelFudge) / imageSize.height; } - layer.contentsGravity = kCAGravityResize; - layer.contentsCenter = contentsCenter; + obj.contentsGravity = kCAGravityResize; + obj.contentsCenter = contentsCenter; } else { - layer.contents = nil; + obj.contents = nil; } } diff --git a/Source/Private/_ASPendingState.mm b/Source/Private/_ASPendingState.mm index e76b89556..26d253b18 100644 --- a/Source/Private/_ASPendingState.mm +++ b/Source/Private/_ASPendingState.mm @@ -52,7 +52,11 @@ int setAnchorPoint:1; int setPosition:1; int setZPosition:1; + int setContentsGravity:1; + int setContentsRect:1; + int setContentsCenter:1; int setContentsScale:1; + int setRasterizationScale:1; int setTransform:1; int setSublayerTransform:1; int setUserInteractionEnabled:1; @@ -101,7 +105,11 @@ @implementation _ASPendingState CGPoint anchorPoint; CGPoint position; CGFloat zPosition; + NSString *contentsGravity; + CGRect contentsRect; + CGRect contentsCenter; CGFloat contentsScale; + CGFloat rasterizationScale; CATransform3D transform; CATransform3D sublayerTransform; CGColorRef shadowColor; @@ -176,7 +184,11 @@ ASDISPLAYNODE_INLINE void ASPendingStateApplyMetricsToLayer(_ASPendingState *sta @synthesize anchorPoint=anchorPoint; @synthesize position=position; @synthesize zPosition=zPosition; +@synthesize contentsGravity=contentsGravity; +@synthesize contentsRect=contentsRect; +@synthesize contentsCenter=contentsCenter; @synthesize contentsScale=contentsScale; +@synthesize rasterizationScale=rasterizationScale; @synthesize transform=transform; @synthesize sublayerTransform=sublayerTransform; @synthesize userInteractionEnabled=userInteractionEnabled; @@ -827,9 +839,6 @@ - (void)applyToView:(UIView *)view withSpecialPropertiesHandling:(BOOL)specialPr if (flags.setBounds) view.bounds = bounds; - if (flags.setContentsScale) - layer.contentsScale = contentsScale; - if (flags.setTransform) layer.transform = transform; @@ -839,6 +848,21 @@ - (void)applyToView:(UIView *)view withSpecialPropertiesHandling:(BOOL)specialPr if (flags.setContents) layer.contents = contents; + if (flags.setContentsGravity) + layer.contentsGravity = contentsGravity; + + if (flags.setContentsRect) + layer.contentsRect = contentsRect; + + if (flags.setContentsCenter) + layer.contentsCenter = contentsCenter; + + if (flags.setContentsScale) + layer.contentsScale = contentsScale; + + if (flags.setRasterizationScale) + layer.rasterizationScale = rasterizationScale; + if (flags.setClipsToBounds) view.clipsToBounds = clipsToBounds; @@ -1037,10 +1061,14 @@ + (_ASPendingState *)pendingViewStateFromView:(UIView *)view pendingState.position = layer.position; pendingState.zPosition = layer.zPosition; pendingState.bounds = view.bounds; - pendingState.contentsScale = layer.contentsScale; pendingState.transform = layer.transform; pendingState.sublayerTransform = layer.sublayerTransform; pendingState.contents = layer.contents; + pendingState.contentsGravity = layer.contentsGravity; + pendingState.contentsRect = layer.contentsRect; + pendingState.contentsCenter = layer.contentsCenter; + pendingState.contentsScale = layer.contentsScale; + pendingState.rasterizationScale = layer.rasterizationScale; pendingState.clipsToBounds = view.clipsToBounds; pendingState.backgroundColor = layer.backgroundColor; pendingState.tintColor = view.tintColor;