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 Jul 31, 2019
1 parent 7aba287 commit 5bed56d
Show file tree
Hide file tree
Showing 16 changed files with 182 additions and 16 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
70 changes: 62 additions & 8 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 Down Expand Up @@ -69,7 +69,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 +131,55 @@ - (void)setDisplaysAsynchronously:(BOOL)displaysAsynchronously
[self.titleNode setDisplaysAsynchronously:displaysAsynchronously];
}


- (NSAttributedString *)__updateTitle:(NSAttributedString *)title withForegroundColor:(UIColor *)newColor
{
if (title) {
NSMutableAttributedString *mutString = [[NSMutableAttributedString alloc] initWithAttributedString:title];
NSRange limit = NSMakeRange(0, _normalAttributedTitle.length);
NSRange effectiveRange;
UIColor *attributeValue = (UIColor *)[mutString attribute:NSForegroundColorAttributeName atIndex:limit.location effectiveRange:&effectiveRange];
if (attributeValue == nil) {
[mutString setAttributes:@{ NSForegroundColorAttributeName : newColor } range:limit];
}

return [mutString copy];
}
return title;
}

- (void)updateTitleForegroundColor:(UIColor *)newColor
{
if (_normalAttributedTitle) {
[self setAttributedTitle:[self __updateTitle:_normalAttributedTitle withForegroundColor:newColor] forState:UIControlStateNormal];
}

if (_highlightedAttributedTitle) {
[self setAttributedTitle:[self __updateTitle:_highlightedAttributedTitle withForegroundColor:newColor] forState:UIControlStateHighlighted];
}

if (_selectedAttributedTitle) {
[self setAttributedTitle:[self __updateTitle:_selectedAttributedTitle withForegroundColor:newColor] forState:UIControlStateSelected];
}
if (_selectedHighlightedAttributedTitle) {
[self setAttributedTitle:[self __updateTitle:_selectedHighlightedAttributedTitle withForegroundColor:newColor] forState:UIControlStateSelected | UIControlStateHighlighted];
}

if (_disabledAttributedTitle) {
[self setAttributedTitle:[self __updateTitle:_disabledAttributedTitle withForegroundColor:newColor] forState:UIControlStateDisabled];
}
}

- (void)setTintColor:(UIColor *)tintColor
{
if (![self.tintColor isEqual:tintColor]) {
[super setTintColor:tintColor];
// Forward tint color to underlying image and title nodes to mirror UIButton
self.imageNode.tintColor = tintColor;
[self updateTitleForegroundColor:tintColor];
}
}

- (void)updateImage
{
[self lock];
Expand Down Expand Up @@ -301,12 +350,17 @@ - (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;
} else if (self.tintColor != nil) {
attributes[NSForegroundColorAttributeName] = self.tintColor;
}

NSAttributedString *string = [[NSAttributedString alloc] initWithString:title attributes:[attributes copy]];
[self setAttributedTitle:string forState:state];
}
#endif
Expand Down
17 changes: 16 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 @@ -508,7 +516,14 @@ + (UIImage *)createContentsForkey:(ASImageNodeContentsKey *)key drawParameters:(
key.didDisplayNodeContentWithRenderingContext(context, drawParameters);
}
});

UIImageRenderingMode renderingMode = [key.image renderingMode];
if ((renderingMode == UIImageRenderingModeAlwaysTemplate
|| renderingMode == UIImageRenderingModeAutomatic)
&& key.tintColor) {
asimagenode_modification_block_t tintModificationBlock = ASImageNodeTintColorModificationBlock(key.tintColor);
result = tintModificationBlock(result);
}

if (key.imageModificationBlock) {
result = key.imageModificationBlock(result);
}
Expand Down
58 changes: 58 additions & 0 deletions Tests/ASButtonSnapshotTests.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//
// ASButtonSnapshotTests.m
// AsyncDisplayKitTests
//
// Created by Rahul Malik on 7/26/19.
// Copyright © 2019 Pinterest. All rights reserved.
//

#import <AsyncDisplayKit/AsyncDisplayKit.h>
#import "ASSnapshotTestCase.h"

@interface ASButtonSnapshotTests : ASSnapshotTestCase

@end


@implementation ASButtonSnapshotTests

- (UIImage *)testImage
{
NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"logo-square"
ofType:@"png"
inDirectory:@"TestResources"];
return [UIImage imageWithContentsOfFile:path];
}


- (void)testTintColor
{
ASButtonNode *node = [[ASButtonNode alloc] init];
node.tintColor = UIColor.redColor;
[node setImage:[self testImage] forState:UIControlStateNormal];
[node setTitle:@"Press Me"
withFont:[UIFont systemFontOfSize:48]
withColor:nil
forState:UIControlStateNormal];
node.imageNode.style.width = ASDimensionMake(200);
node.imageNode.style.height = ASDimensionMake(200);
ASDisplayNodeSizeToFitSize(node, CGSizeMake(1000, 1000));
ASSnapshotVerifyNode(node, nil);
}

- (void)testTintColorWithForegroundColorSet
{
ASButtonNode *node = [[ASButtonNode alloc] init];
node.tintColor = UIColor.redColor;
[node setImage:[self testImage] forState:UIControlStateNormal];
[node setTitle:@"Press Me"
withFont:[UIFont systemFontOfSize:48]
withColor:[UIColor blueColor]
forState:UIControlStateNormal];
node.imageNode.style.width = ASDimensionMake(200);
node.imageNode.style.height = ASDimensionMake(200);
ASDisplayNodeSizeToFitSize(node, CGSizeMake(1000, 1000));
ASSnapshotVerifyNode(node, nil);
}

@end
39 changes: 37 additions & 2 deletions Tests/ASImageNodeSnapshotTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ @implementation ASImageNodeSnapshotTests
- (void)setUp
{
[super setUp];

self.recordMode = NO;
}

Expand Down Expand Up @@ -72,7 +71,43 @@ - (void)testTintColorBlock
ASImageNode *node = [[ASImageNode alloc] init];
node.image = tinted;
ASDisplayNodeSizeToFitSize(node, test.size);

ASSnapshotVerifyNode(node, nil);
}

- (void)testTintColorOnNodePropertyAlwaysTemplate
{
UIImage *test = [self testImage];
ASImageNode *node = [[ASImageNode alloc] init];
node.image = [test imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
node.tintColor = UIColor.redColor;
ASDisplayNodeSizeToFitSize(node, test.size);
// Tint color should change view
ASSnapshotVerifyNode(node, @"red_tint");

node.tintColor = UIColor.blueColor;
// Tint color should change view
ASSnapshotVerifyNode(node, @"blue_tint");
}

- (void)testTintColorOnNodePropertyAutomatic
{
UIImage *test = [self testImage];
ASImageNode *node = [[ASImageNode alloc] init];
node.image = [test imageWithRenderingMode:UIImageRenderingModeAutomatic];
// Tint color should change view
node.tintColor = UIColor.redColor;
ASDisplayNodeSizeToFitSize(node, test.size);
ASSnapshotVerifyNode(node, nil);
}

- (void)testTintColorOnNodePropertyAlwaysOriginal
{
UIImage *test = [self testImage];
ASImageNode *node = [[ASImageNode alloc] init];
node.image = [test imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
// Tint color should not have changed since the image render mode is original
node.tintColor = UIColor.redColor;
ASDisplayNodeSizeToFitSize(node, test.size);
ASSnapshotVerifyNode(node, nil);
}

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ...ageNodeSnapshotTests/[email protected]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ...mageNodeSnapshotTests/[email protected]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 5bed56d

Please sign in to comment.