Skip to content

Commit

Permalink
Add support for tintColor on ASImageNode and ASButtonNode
Browse files Browse the repository at this point in the history
- Adding the ability to specify a tintColor for images / buttons that behave similarly to how they would under UIKit
  • Loading branch information
rahul-malik committed Aug 8, 2019
1 parent 7aba287 commit fef6c3d
Show file tree
Hide file tree
Showing 25 changed files with 200 additions and 25 deletions.
14 changes: 9 additions & 5 deletions AsyncDisplayKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@
8021EC1D1D2B00B100799119 /* UIImage+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 8021EC1A1D2B00B100799119 /* UIImage+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; };
8021EC1F1D2B00B100799119 /* UIImage+ASConvenience.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.mm */; };
81E95C141D62639600336598 /* ASTextNodeSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 81E95C131D62639600336598 /* ASTextNodeSnapshotTests.mm */; };
81FF150722EB5F410039311A /* ASButtonSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 81FF150622EB5F410039311A /* ASButtonSnapshotTests.mm */; };
83A7D95B1D44547700BF333E /* ASWeakMap.mm in Sources */ = {isa = PBXBuildFile; fileRef = 83A7D9591D44542100BF333E /* ASWeakMap.mm */; };
83A7D95C1D44548100BF333E /* ASWeakMap.h in Headers */ = {isa = PBXBuildFile; fileRef = 83A7D9581D44542100BF333E /* ASWeakMap.h */; settings = {ATTRIBUTES = (Private, ); }; };
83A7D95E1D446A6E00BF333E /* ASWeakMapTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 83A7D95D1D446A6E00BF333E /* ASWeakMapTests.mm */; };
Expand Down Expand Up @@ -747,6 +748,7 @@
81E95C131D62639600336598 /* ASTextNodeSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextNodeSnapshotTests.mm; sourceTree = "<group>"; };
81EE384D1C8E94F000456208 /* ASRunLoopQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASRunLoopQueue.h; path = ../ASRunLoopQueue.h; sourceTree = "<group>"; };
81EE384E1C8E94F000456208 /* ASRunLoopQueue.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASRunLoopQueue.mm; path = ../ASRunLoopQueue.mm; sourceTree = "<group>"; };
81FF150622EB5F410039311A /* ASButtonSnapshotTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ASButtonSnapshotTests.mm; sourceTree = "<group>"; };
83A7D9581D44542100BF333E /* ASWeakMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASWeakMap.h; sourceTree = "<group>"; };
83A7D9591D44542100BF333E /* ASWeakMap.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASWeakMap.mm; sourceTree = "<group>"; };
83A7D95D1D446A6E00BF333E /* ASWeakMapTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASWeakMapTests.mm; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1297,23 +1299,22 @@
058D09C5195D04C000B7D73C /* Tests */ = {
isa = PBXGroup;
children = (
9692B4FE219E12370060C2C3 /* ASCollectionViewThrashTests.mm */,
F325E48F217460B000AC93A4 /* ASTextNode2Tests.mm */,
F325E48B21745F9E00AC93A4 /* ASButtonNodeTests.mm */,
F3F698D1211CAD4600800CB1 /* ASDisplayViewAccessibilityTests.mm */,
DBC452DD1C5C6A6A00B16017 /* ArrayDiffingTests.mm */,
AC026B571BD3F61800BBC17E /* ASAbsoluteLayoutSpecSnapshotTests.mm */,
696FCB301D6E46050093471E /* ASBackgroundLayoutSpecSnapshotTests.mm */,
29CDC2E11AAE70D000833CA4 /* ASBasicImageDownloaderContextTests.mm */,
242995D21B29743C00090100 /* ASBasicImageDownloaderTests.mm */,
296A0A341A951ABF005ACEAA /* ASBatchFetchingTests.mm */,
CC3B208F1C3F892D00798563 /* ASBridgedPropertiesTests.mm */,
F325E48B21745F9E00AC93A4 /* ASButtonNodeTests.mm */,
81FF150622EB5F410039311A /* ASButtonSnapshotTests.mm */,
CC051F1E1D7A286A006434CB /* ASCALayerTests.mm */,
ACF6ED531B178DC700DA7C62 /* ASCenterLayoutSpecSnapshotTests.mm */,
CCDD148A1EEDCD9D0020834E /* ASCollectionModernDataSourceTests.mm */,
CC35CEC520DD87280006448D /* ASCollectionsTests.mm */,
2538B6F21BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.mm */,
9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.mm */,
9692B4FE219E12370060C2C3 /* ASCollectionViewThrashTests.mm */,
CCEDDDD8200C518800FFCD0A /* ASConfigurationTests.mm */,
2911485B1A77147A005D0878 /* ASControlNodeTests.mm */,
1A6C000F1FAB4ED400D05926 /* ASCornerLayoutSpecSnapshotTests.mm */,
Expand All @@ -1328,6 +1329,7 @@
058D0A2F195D057000B7D73C /* ASDisplayNodeTests.mm */,
058D0A30195D057000B7D73C /* ASDisplayNodeTestsHelper.h */,
058D0A31195D057000B7D73C /* ASDisplayNodeTestsHelper.mm */,
F3F698D1211CAD4600800CB1 /* ASDisplayViewAccessibilityTests.mm */,
697B31591CFE4B410049936F /* ASEditableTextNodeTests.mm */,
471D04B0224CB98600649215 /* ASImageNodeBackingSizeTests.mm */,
056D21541ABCEF50001107EF /* ASImageNodeSnapshotTests.mm */,
Expand Down Expand Up @@ -1366,6 +1368,7 @@
254C6B531BF8FF2A003EC431 /* ASTextKitTests.mm */,
254C6B511BF8FE6D003EC431 /* ASTextKitTruncationTests.mm */,
C057D9BC20B5453D00FC9112 /* ASTextNode2SnapshotTests.mm */,
F325E48F217460B000AC93A4 /* ASTextNode2Tests.mm */,
CC8B05D71D73979700F54286 /* ASTextNodePerformanceTests.mm */,
81E95C131D62639600336598 /* ASTextNodeSnapshotTests.mm */,
058D0A36195D057000B7D73C /* ASTextNodeTests.mm */,
Expand All @@ -1374,13 +1377,13 @@
9644CFDF2193777C00213478 /* ASThrashUtility.m */,
CCE4F9BC1F0ECE5200062E4E /* ASTLayoutFixture.h */,
CCE4F9BD1F0ECE5200062E4E /* ASTLayoutFixture.mm */,
D933F040224AD17F00FF495E /* ASTransactionTests.mm */,
CC0AEEA31D66316E005D1C78 /* ASUICollectionViewTests.mm */,
AEEC47E31C21D3D200EC1693 /* ASVideoNodeTests.mm */,
CCA221D21D6FA7EF00AF6A0F /* ASViewControllerTests.mm */,
83A7D95D1D446A6E00BF333E /* ASWeakMapTests.mm */,
CC3B208D1C3F7D0A00798563 /* ASWeakSetTests.mm */,
695BE2541DC1245C008E6EA5 /* ASWrapperSpecSnapshotTests.mm */,
D933F040224AD17F00FF495E /* ASTransactionTests.mm */,
057D02C01AC0A66700C7AC3C /* AsyncDisplayKitTestHost */,
CC583ABF1EF9BAB400134156 /* Common */,
058D09C6195D04C000B7D73C /* Supporting Files */,
Expand Down Expand Up @@ -2293,6 +2296,7 @@
CC3B208E1C3F7D0A00798563 /* ASWeakSetTests.mm in Sources */,
F711994E1D20C21100568860 /* ASDisplayNodeExtrasTests.mm in Sources */,
BB5FC3CE1F9BA689007F191E /* ASNavigationControllerTests.mm in Sources */,
81FF150722EB5F410039311A /* ASButtonSnapshotTests.mm in Sources */,
ACF6ED5D1B178DC700DA7C62 /* ASDimensionTests.mm in Sources */,
BB5FC3D11F9C9389007F191E /* ASTabBarControllerTests.mm in Sources */,
695BE2551DC1245C008E6EA5 /* ASWrapperSpecSnapshotTests.mm in Sources */,
Expand Down
35 changes: 22 additions & 13 deletions Source/ASButtonNode.mm
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ - (instancetype)init
_contentEdgeInsets = UIEdgeInsetsZero;
_imageAlignment = ASButtonNodeImageAlignmentBeginning;
self.accessibilityTraits = self.defaultAccessibilityTraits;

[self updateYogaLayoutIfNeeded];
}
return self;
Expand All @@ -52,11 +52,7 @@ - (ASTextNode *)titleNode
ASLockScopeSelf();
if (!_titleNode) {
_titleNode = [[ASTextNode alloc] init];
#if TARGET_OS_IOS
// tvOS needs access to the underlying view
// of the button node to add a touch handler.
[_titleNode setLayerBacked:YES];
#endif
// Intentionally not layer-backing the image node since tintColor may be applied
_titleNode.style.flexShrink = 1.0;
}
return _titleNode;
Expand All @@ -69,7 +65,7 @@ - (ASImageNode *)imageNode
ASLockScopeSelf();
if (!_imageNode) {
_imageNode = [[ASImageNode alloc] init];
[_imageNode setLayerBacked:YES];
// Intentionally not layer-backing the image node since tintColor may be applied
}
return _imageNode;
}
Expand Down Expand Up @@ -131,6 +127,17 @@ - (void)setDisplaysAsynchronously:(BOOL)displaysAsynchronously
[self.titleNode setDisplaysAsynchronously:displaysAsynchronously];
}

-(void)tintColorDidChange
{
[super tintColorDidChange];
// UIButton documentation states that it tints the image and title of buttons when tintColor is set.
// | "The tint color to apply to the button title and image."
// | From: https://developer.apple.com/documentation/uikit/uibutton/1624025-tintcolor
UIColor *tintColor = self.tintColor;
self.imageNode.tintColor = tintColor;
self.titleNode.tintColor = tintColor;
}

- (void)updateImage
{
[self lock];
Expand Down Expand Up @@ -301,12 +308,14 @@ - (void)setImageAlignment:(ASButtonNodeImageAlignment)imageAlignment
#if TARGET_OS_IOS
- (void)setTitle:(NSString *)title withFont:(UIFont *)font withColor:(UIColor *)color forState:(UIControlState)state
{
NSDictionary *attributes = @{
NSFontAttributeName: font ? : [UIFont systemFontOfSize:[UIFont buttonFontSize]],
NSForegroundColorAttributeName : color ? : [UIColor blackColor]
};

NSAttributedString *string = [[NSAttributedString alloc] initWithString:title attributes:attributes];
NSMutableDictionary *attributes = [NSMutableDictionary dictionary];
attributes[NSFontAttributeName] = font ? : [UIFont systemFontOfSize:[UIFont buttonFontSize]];
if (color != nil) {
// From apple's documentation: If color is not specified, NSForegroundColorAttributeName will fallback to black
// Only set if the color is nonnull
attributes[NSForegroundColorAttributeName] = color;
}
NSAttributedString *string = [[NSAttributedString alloc] initWithString:title attributes:[attributes copy]];
[self setAttributedTitle:string forState:state];
}
#endif
Expand Down
24 changes: 23 additions & 1 deletion Source/ASImageNode.mm
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ @interface ASImageNodeDrawParameters : NSObject {
CGRect _bounds;
CGFloat _contentsScale;
UIColor *_backgroundColor;
UIColor *_tintColor;
UIViewContentMode _contentMode;
BOOL _cropEnabled;
BOOL _forceUpscaling;
Expand Down Expand Up @@ -69,6 +70,7 @@ @interface ASImageNodeContentsKey : NSObject
@property CGRect imageDrawRect;
@property BOOL isOpaque;
@property (nonatomic, copy) UIColor *backgroundColor;
@property (nonatomic, copy) UIColor *tintColor;
@property (nonatomic) ASDisplayNodeContextModifier willDisplayNodeContentWithRenderingContext;
@property (nonatomic) ASDisplayNodeContextModifier didDisplayNodeContentWithRenderingContext;
@property (nonatomic) asimagenode_modification_block_t imageModificationBlock;
Expand All @@ -94,6 +96,7 @@ - (BOOL)isEqual:(id)object
&& CGRectEqualToRect(_imageDrawRect, other.imageDrawRect)
&& _isOpaque == other.isOpaque
&& [_backgroundColor isEqual:other.backgroundColor]
&& [_tintColor isEqual:other.tintColor]
&& _willDisplayNodeContentWithRenderingContext == other.willDisplayNodeContentWithRenderingContext
&& _didDisplayNodeContentWithRenderingContext == other.didDisplayNodeContentWithRenderingContext
&& _imageModificationBlock == other.imageModificationBlock;
Expand All @@ -112,6 +115,7 @@ - (NSUInteger)hash
CGRect imageDrawRect;
NSInteger isOpaque;
NSUInteger backgroundColorHash;
NSUInteger tintColorHash;
void *willDisplayNodeContentWithRenderingContext;
void *didDisplayNodeContentWithRenderingContext;
void *imageModificationBlock;
Expand All @@ -122,6 +126,7 @@ - (NSUInteger)hash
_imageDrawRect,
_isOpaque,
_backgroundColor.hash,
_tintColor.hash,
(void *)_willDisplayNodeContentWithRenderingContext,
(void *)_didDisplayNodeContentWithRenderingContext,
(void *)_imageModificationBlock
Expand Down Expand Up @@ -296,6 +301,7 @@ - (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer
drawParameters->_opaque = self.opaque;
drawParameters->_contentsScale = _contentsScaleForDisplay;
drawParameters->_backgroundColor = self.backgroundColor;
drawParameters->_tintColor = self.tintColor;
drawParameters->_contentMode = self.contentMode;
drawParameters->_cropEnabled = _imageNodeFlags.cropEnabled;
drawParameters->_forceUpscaling = _imageNodeFlags.forceUpscaling;
Expand Down Expand Up @@ -330,6 +336,7 @@ + (UIImage *)displayWithParameters:(id<NSObject>)parameter isCancelled:(NS_NOESC
BOOL cropEnabled = drawParameter->_cropEnabled;
BOOL isOpaque = drawParameter->_opaque;
UIColor *backgroundColor = drawParameter->_backgroundColor;
UIColor *tintColor = drawParameter->_tintColor;
UIViewContentMode contentMode = drawParameter->_contentMode;
CGFloat contentsScale = drawParameter->_contentsScale;
CGRect cropDisplayBounds = drawParameter->_cropDisplayBounds;
Expand Down Expand Up @@ -401,6 +408,7 @@ + (UIImage *)displayWithParameters:(id<NSObject>)parameter isCancelled:(NS_NOESC
contentsKey.imageDrawRect = imageDrawRect;
contentsKey.isOpaque = isOpaque;
contentsKey.backgroundColor = backgroundColor;
contentsKey.tintColor = tintColor;
contentsKey.willDisplayNodeContentWithRenderingContext = willDisplayNodeContentWithRenderingContext;
contentsKey.didDisplayNodeContentWithRenderingContext = didDisplayNodeContentWithRenderingContext;
contentsKey.imageModificationBlock = imageModificationBlock;
Expand Down Expand Up @@ -500,6 +508,14 @@ + (UIImage *)createContentsForkey:(ASImageNodeContentsKey *)key drawParameters:(
BOOL canUseCopy = (contextIsClean || ASImageAlphaInfoIsOpaque(CGImageGetAlphaInfo(image.CGImage)));
CGBlendMode blendMode = canUseCopy ? kCGBlendModeCopy : kCGBlendModeNormal;

UIImageRenderingMode renderingMode = [key.image renderingMode];
if ((renderingMode == UIImageRenderingModeAlwaysTemplate
|| renderingMode == UIImageRenderingModeAutomatic)
&& key.tintColor) {
[key.tintColor setFill];
image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
}

@synchronized(image) {
[image drawInRect:key.imageDrawRect blendMode:blendMode alpha:1];
}
Expand All @@ -508,7 +524,13 @@ + (UIImage *)createContentsForkey:(ASImageNodeContentsKey *)key drawParameters:(
key.didDisplayNodeContentWithRenderingContext(context, drawParameters);
}
});


// if the original image was stretchy, keep it stretchy
UIImage *originalImage = key.image;
if (!UIEdgeInsetsEqualToEdgeInsets(originalImage.capInsets, UIEdgeInsetsZero)) {
result = [result resizableImageWithCapInsets:originalImage.capInsets resizingMode:originalImage.resizingMode];
}

if (key.imageModificationBlock) {
result = key.imageModificationBlock(result);
}
Expand Down
6 changes: 4 additions & 2 deletions Source/ASTextNode.mm
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,8 @@ - (ASTextKitAttributes)_locked_rendererAttributes
.shadowOffset = _shadowOffset,
.shadowColor = _cachedShadowUIColor,
.shadowOpacity = _shadowOpacity,
.shadowRadius = _shadowRadius
.shadowRadius = _shadowRadius,
.tintColor = self.tintColor
};
}

Expand Down Expand Up @@ -570,7 +571,8 @@ + (UIImage *)displayWithParameters:(id<NSObject>)parameters isCancelled:(NS_NOES
[backgroundColor setFill];
UIRectFillUsingBlendMode(CGContextGetClipBoundingBox(context), kCGBlendModeCopy);
}



// Draw text
[renderer drawInContext:context bounds:drawParameter->_bounds];
CGContextRestoreGState(context);
Expand Down
8 changes: 7 additions & 1 deletion Source/TextKit/ASTextKitAttributes.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ struct ASTextKitAttributes {
*/
NSArray *pointSizeScaleFactors;

/**
The tint color to use in drawing the text foreground color. Only applied if the attributedString does not define foreground color
*/
UIColor *tintColor;
/**
We provide an explicit copy function so we can use aggregate initializer syntax while providing copy semantics for
the NSObjects inside.
Expand All @@ -102,6 +106,7 @@ struct ASTextKitAttributes {
shadowOpacity,
shadowRadius,
pointSizeScaleFactors,
[tintColor copy]
};
};

Expand All @@ -119,7 +124,8 @@ struct ASTextKitAttributes {
&& ASObjectIsEqual(avoidTailTruncationSet, other.avoidTailTruncationSet)
&& ASObjectIsEqual(shadowColor, other.shadowColor)
&& ASObjectIsEqual(attributedString, other.attributedString)
&& ASObjectIsEqual(truncationAttributedString, other.truncationAttributedString);
&& ASObjectIsEqual(truncationAttributedString, other.truncationAttributedString)
&& ASObjectIsEqual(tintColor, other.tintColor);
}

size_t hash() const;
Expand Down
1 change: 1 addition & 0 deletions Source/TextKit/ASTextKitContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ AS_SUBCLASSING_RESTRICTED
Initialization of TextKit components is a globally locking operation so be careful of bottlenecks with this class.
*/
- (instancetype)initWithAttributedString:(NSAttributedString *)attributedString
tintColor:(UIColor *)tintColor
lineBreakMode:(NSLineBreakMode)lineBreakMode
maximumNumberOfLines:(NSUInteger)maximumNumberOfLines
exclusionPaths:(NSArray *)exclusionPaths
Expand Down
12 changes: 12 additions & 0 deletions Source/TextKit/ASTextKitContext.mm
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ @implementation ASTextKitContext
}

- (instancetype)initWithAttributedString:(NSAttributedString *)attributedString
tintColor:(UIColor *)tintColor
lineBreakMode:(NSLineBreakMode)lineBreakMode
maximumNumberOfLines:(NSUInteger)maximumNumberOfLines
exclusionPaths:(NSArray *)exclusionPaths
Expand Down Expand Up @@ -59,6 +60,17 @@ - (instancetype)initWithAttributedString:(NSAttributedString *)attributedString
// See https://github.com/facebook/AsyncDisplayKit/issues/2894
if (attributedString) {
[_textStorage setAttributedString:attributedString];
// Apply tint color if specified and if foreground color is undefined for attributedString
if (tintColor) {
NSRange limit = NSMakeRange(0, attributedString.length);
NSRange effectiveRange;
// Look for previous attributes that define foreground color
UIColor *attributeValue = (UIColor *)[attributedString attribute:NSForegroundColorAttributeName atIndex:limit.location effectiveRange:&effectiveRange];
if (attributeValue == nil) {
// None are found, apply tint color
[_textStorage addAttributes:@{ NSForegroundColorAttributeName : tintColor } range:limit];
}
}
}

_textContainer = [[NSTextContainer alloc] initWithSize:constrainedSize];
Expand Down
1 change: 1 addition & 0 deletions Source/TextKit/ASTextKitRenderer.mm
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ - (instancetype)initWithTextKitAttributes:(const ASTextKitAttributes &)attribute
CGSize shadowConstrainedSize = [[self shadower] insetSizeWithConstrainedSize:_constrainedSize];

_context = [[ASTextKitContext alloc] initWithAttributedString:attributes.attributedString
tintColor:attributes.tintColor
lineBreakMode:attributes.lineBreakMode
maximumNumberOfLines:attributes.maximumNumberOfLines
exclusionPaths:attributes.exclusionPaths
Expand Down
1 change: 1 addition & 0 deletions Source/TextKit/ASTextKitTailTruncater.mm
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ - (NSUInteger)_calculateCharacterIndexBeforeTruncationMessage:(NSLayoutManager *

// Calculate the bounding rectangle for the truncation message
ASTextKitContext *truncationContext = [[ASTextKitContext alloc] initWithAttributedString:_truncationAttributedString
tintColor:nil
lineBreakMode:NSLineBreakByWordWrapping
maximumNumberOfLines:1
exclusionPaths:nil
Expand Down
Loading

0 comments on commit fef6c3d

Please sign in to comment.