diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 4cb422688..4c8584e6f 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -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 /* ASButtonNodeSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 81FF150622EB5F410039311A /* ASButtonNodeSnapshotTests.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 */; }; @@ -747,6 +748,7 @@ 81E95C131D62639600336598 /* ASTextNodeSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextNodeSnapshotTests.mm; sourceTree = ""; }; 81EE384D1C8E94F000456208 /* ASRunLoopQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASRunLoopQueue.h; path = ../ASRunLoopQueue.h; sourceTree = ""; }; 81EE384E1C8E94F000456208 /* ASRunLoopQueue.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASRunLoopQueue.mm; path = ../ASRunLoopQueue.mm; sourceTree = ""; }; + 81FF150622EB5F410039311A /* ASButtonNodeSnapshotTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ASButtonNodeSnapshotTests.mm; sourceTree = ""; }; 83A7D9581D44542100BF333E /* ASWeakMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASWeakMap.h; sourceTree = ""; }; 83A7D9591D44542100BF333E /* ASWeakMap.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASWeakMap.mm; sourceTree = ""; }; 83A7D95D1D446A6E00BF333E /* ASWeakMapTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASWeakMapTests.mm; sourceTree = ""; }; @@ -1297,10 +1299,6 @@ 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 */, @@ -1308,12 +1306,15 @@ 242995D21B29743C00090100 /* ASBasicImageDownloaderTests.mm */, 296A0A341A951ABF005ACEAA /* ASBatchFetchingTests.mm */, CC3B208F1C3F892D00798563 /* ASBridgedPropertiesTests.mm */, + F325E48B21745F9E00AC93A4 /* ASButtonNodeTests.mm */, + 81FF150622EB5F410039311A /* ASButtonNodeSnapshotTests.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 */, @@ -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 */, @@ -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 */, @@ -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 */, @@ -2293,6 +2296,7 @@ CC3B208E1C3F7D0A00798563 /* ASWeakSetTests.mm in Sources */, F711994E1D20C21100568860 /* ASDisplayNodeExtrasTests.mm in Sources */, BB5FC3CE1F9BA689007F191E /* ASNavigationControllerTests.mm in Sources */, + 81FF150722EB5F410039311A /* ASButtonNodeSnapshotTests.mm in Sources */, ACF6ED5D1B178DC700DA7C62 /* ASDimensionTests.mm in Sources */, BB5FC3D11F9C9389007F191E /* ASTabBarControllerTests.mm in Sources */, 695BE2551DC1245C008E6EA5 /* ASWrapperSpecSnapshotTests.mm in Sources */, diff --git a/Source/ASButtonNode.mm b/Source/ASButtonNode.mm index 7af329307..0e0c26b2e 100644 --- a/Source/ASButtonNode.mm +++ b/Source/ASButtonNode.mm @@ -41,7 +41,7 @@ - (instancetype)init _contentEdgeInsets = UIEdgeInsetsZero; _imageAlignment = ASButtonNodeImageAlignmentBeginning; self.accessibilityTraits = self.defaultAccessibilityTraits; - + [self updateYogaLayoutIfNeeded]; } return self; @@ -52,12 +52,9 @@ - (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; + _titleNode.textColorFollowsTintColor = YES; } return _titleNode; } @@ -69,7 +66,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; } @@ -131,6 +128,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]; @@ -301,12 +309,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 diff --git a/Source/ASImageNode.mm b/Source/ASImageNode.mm index 7393dac66..f51d3a324 100644 --- a/Source/ASImageNode.mm +++ b/Source/ASImageNode.mm @@ -41,6 +41,7 @@ @interface ASImageNodeDrawParameters : NSObject { CGRect _bounds; CGFloat _contentsScale; UIColor *_backgroundColor; + UIColor *_tintColor; UIViewContentMode _contentMode; BOOL _cropEnabled; BOOL _forceUpscaling; @@ -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; @@ -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; @@ -112,6 +115,7 @@ - (NSUInteger)hash CGRect imageDrawRect; NSInteger isOpaque; NSUInteger backgroundColorHash; + NSUInteger tintColorHash; void *willDisplayNodeContentWithRenderingContext; void *didDisplayNodeContentWithRenderingContext; void *imageModificationBlock; @@ -122,6 +126,7 @@ - (NSUInteger)hash _imageDrawRect, _isOpaque, _backgroundColor.hash, + _tintColor.hash, (void *)_willDisplayNodeContentWithRenderingContext, (void *)_didDisplayNodeContentWithRenderingContext, (void *)_imageModificationBlock @@ -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; @@ -330,6 +336,7 @@ + (UIImage *)displayWithParameters:(id)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; @@ -401,6 +408,7 @@ + (UIImage *)displayWithParameters:(id)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; @@ -499,6 +507,10 @@ + (UIImage *)createContentsForkey:(ASImageNodeContentsKey *)key drawParameters:( UIImage *image = key.image; BOOL canUseCopy = (contextIsClean || ASImageAlphaInfoIsOpaque(CGImageGetAlphaInfo(image.CGImage))); CGBlendMode blendMode = canUseCopy ? kCGBlendModeCopy : kCGBlendModeNormal; + UIImageRenderingMode renderingMode = [image renderingMode]; + if (renderingMode == UIImageRenderingModeAlwaysTemplate && key.tintColor) { + [key.tintColor setFill]; + } @synchronized(image) { [image drawInRect:key.imageDrawRect blendMode:blendMode alpha:1]; @@ -508,7 +520,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); } diff --git a/Source/ASTextNode.h b/Source/ASTextNode.h index a67f1f574..b05a1d54a 100644 --- a/Source/ASTextNode.h +++ b/Source/ASTextNode.h @@ -224,6 +224,12 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic) BOOL alwaysHandleTruncationTokenTap; +/** + @abstract if YES will use the value of `self.tintColor` if the foreground color of text is not defined. + @discussion This is mainly used from ASButtonNode since by default text nodes do not respect tintColor settings unless contained within a interactive control + */ +@property (nonatomic) BOOL textColorFollowsTintColor; + @end @interface ASTextNode (Unavailable) diff --git a/Source/ASTextNode.mm b/Source/ASTextNode.mm index 0d65dbb0f..ae613305d 100644 --- a/Source/ASTextNode.mm +++ b/Source/ASTextNode.mm @@ -252,6 +252,10 @@ - (instancetype)init // on the special placeholder behavior of ASTextNode. _placeholderColor = ASDisplayNodeDefaultPlaceholderColor(); _placeholderInsets = UIEdgeInsetsMake(1.0, 0.0, 1.0, 0.0); + + // Tint color is applied when text nodes are within controls and indicate user action + // Most text nodes do not require interaction and this matches the default value of UILabel + _textColorFollowsTintColor = NO; } return self; @@ -377,7 +381,8 @@ - (ASTextKitAttributes)_locked_rendererAttributes .shadowOffset = _shadowOffset, .shadowColor = _cachedShadowUIColor, .shadowOpacity = _shadowOpacity, - .shadowRadius = _shadowRadius + .shadowRadius = _shadowRadius, + .tintColor = self.textColorFollowsTintColor ? self.tintColor : nil }; } @@ -571,7 +576,8 @@ + (UIImage *)displayWithParameters:(id)parameters isCancelled:(NS_NOES [backgroundColor setFill]; UIRectFillUsingBlendMode(CGContextGetClipBoundingBox(context), kCGBlendModeCopy); } - + + // Draw text [renderer drawInContext:context bounds:drawParameter->_bounds]; CGContextRestoreGState(context); diff --git a/Source/ASTextNode2.h b/Source/ASTextNode2.h index b46a0bcb4..5848adc37 100644 --- a/Source/ASTextNode2.h +++ b/Source/ASTextNode2.h @@ -220,6 +220,12 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic) BOOL alwaysHandleTruncationTokenTap; +/** + @abstract if YES will use the value of `self.tintColor` if the foreground color of text is not defined. + @discussion This is mainly used from ASButtonNode since by default text nodes do not respect tintColor settings unless contained within a interactive control + */ +@property (nonatomic) BOOL textColorFollowsTintColor; + + (void)enableDebugging; #pragma mark - Layout and Sizing diff --git a/Source/ASTextNode2.mm b/Source/ASTextNode2.mm index 92c2b813d..001ebddd7 100644 --- a/Source/ASTextNode2.mm +++ b/Source/ASTextNode2.mm @@ -517,6 +517,20 @@ - (void)prepareAttributedString:(NSMutableAttributedString *)attributedString is shadow.shadowBlurRadius = _shadowRadius; [attributedString addAttribute:NSShadowAttributeName value:shadow range:NSMakeRange(0, attributedString.length)]; } + + // Apply tint color if needed and foreground color is not already specified + if (self.textColorFollowsTintColor) { + // Apply tint color if specified and if foreground color is undefined for attributedString + 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]; + UIColor *tintColor = self.tintColor; + if (attributeValue == nil && tintColor) { + // None are found, apply tint color if available. Fallback to "black" text color + [attributedString addAttributes:@{ NSForegroundColorAttributeName : tintColor } range:limit]; + } + } } #pragma mark - Drawing diff --git a/Source/Private/_ASPendingState.mm b/Source/Private/_ASPendingState.mm index 6922f6a2b..c85ef4a96 100644 --- a/Source/Private/_ASPendingState.mm +++ b/Source/Private/_ASPendingState.mm @@ -229,7 +229,6 @@ ASDISPLAYNODE_INLINE void ASPendingStateApplyMetricsToLayer(_ASPendingState *sta @synthesize maskedCorners = maskedCorners; static CGColorRef blackColorRef = NULL; -static UIColor *defaultTintColor = nil; - (instancetype)init { @@ -244,7 +243,6 @@ - (instancetype)init blackColorRef = CGColorCreate(colorSpace, (CGFloat[]){0,0,0,1} ); CFRetain(blackColorRef); CGColorSpaceRelease(colorSpace); - defaultTintColor = [UIColor colorWithRed:0.0 green:0.478 blue:1.0 alpha:1.0]; }); // Set defaults, these come from the defaults specified in CALayer and UIView @@ -253,7 +251,7 @@ - (instancetype)init frame = CGRectZero; bounds = CGRectZero; backgroundColor = nil; - tintColor = defaultTintColor; + tintColor = nil; _flags.hidden = NO; _flags.needsDisplayOnBoundsChange = NO; _flags.allowsGroupOpacity = ASDefaultAllowsGroupOpacity(); diff --git a/Source/TextKit/ASTextKitAttributes.h b/Source/TextKit/ASTextKitAttributes.h index d8c21dbc2..bf9017a59 100644 --- a/Source/TextKit/ASTextKitAttributes.h +++ b/Source/TextKit/ASTextKitAttributes.h @@ -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. @@ -102,6 +106,7 @@ struct ASTextKitAttributes { shadowOpacity, shadowRadius, pointSizeScaleFactors, + [tintColor copy] }; }; @@ -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; diff --git a/Source/TextKit/ASTextKitContext.h b/Source/TextKit/ASTextKitContext.h index 82a40b7d8..df9d0a0c6 100644 --- a/Source/TextKit/ASTextKitContext.h +++ b/Source/TextKit/ASTextKitContext.h @@ -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 diff --git a/Source/TextKit/ASTextKitContext.mm b/Source/TextKit/ASTextKitContext.mm index b3097466c..5ef840c66 100644 --- a/Source/TextKit/ASTextKitContext.mm +++ b/Source/TextKit/ASTextKitContext.mm @@ -27,6 +27,7 @@ @implementation ASTextKitContext } - (instancetype)initWithAttributedString:(NSAttributedString *)attributedString + tintColor:(UIColor *)tintColor lineBreakMode:(NSLineBreakMode)lineBreakMode maximumNumberOfLines:(NSUInteger)maximumNumberOfLines exclusionPaths:(NSArray *)exclusionPaths @@ -59,6 +60,18 @@ - (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 + 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 if available. Fallback to "black" text color + if (tintColor) { + [_textStorage addAttributes:@{ NSForegroundColorAttributeName : tintColor } range:limit]; + } + } } _textContainer = [[NSTextContainer alloc] initWithSize:constrainedSize]; diff --git a/Source/TextKit/ASTextKitRenderer.mm b/Source/TextKit/ASTextKitRenderer.mm index b724d7074..5da78e1f7 100644 --- a/Source/TextKit/ASTextKitRenderer.mm +++ b/Source/TextKit/ASTextKitRenderer.mm @@ -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 diff --git a/Source/TextKit/ASTextKitTailTruncater.mm b/Source/TextKit/ASTextKitTailTruncater.mm index b81e27be0..0fd8a1c97 100644 --- a/Source/TextKit/ASTextKitTailTruncater.mm +++ b/Source/TextKit/ASTextKitTailTruncater.mm @@ -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 diff --git a/Tests/ASButtonNodeSnapshotTests.mm b/Tests/ASButtonNodeSnapshotTests.mm new file mode 100644 index 000000000..ea8385f90 --- /dev/null +++ b/Tests/ASButtonNodeSnapshotTests.mm @@ -0,0 +1,83 @@ +// +// ASButtonNodeSnapshotTests.mm +// Texture +// +// Copyright (c) Pinterest, Inc. All rights reserved. +// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 +// + +#import +#import "ASSnapshotTestCase.h" + +@interface ASButtonNodeSnapshotTests : ASSnapshotTestCase + +@end + + +@implementation ASButtonNodeSnapshotTests + +- (void)setUp +{ + [super setUp]; + self.recordMode = NO; +} + +- (UIImage *)testImage +{ + NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"logo-square" + ofType:@"png" + inDirectory:@"TestResources"]; + return [[UIImage imageWithContentsOfFile:path] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; +} + +- (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)testChangingTintColor +{ + 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); + + node.tintColor = UIColor.blueColor; + ASSnapshotVerifyNode(node, @"modified_tint"); +} + + +- (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 diff --git a/Tests/ASImageNodeSnapshotTests.mm b/Tests/ASImageNodeSnapshotTests.mm index 55cb5f866..9102e51ef 100644 --- a/Tests/ASImageNodeSnapshotTests.mm +++ b/Tests/ASImageNodeSnapshotTests.mm @@ -19,7 +19,6 @@ @implementation ASImageNodeSnapshotTests - (void)setUp { [super setUp]; - self.recordMode = NO; } @@ -65,14 +64,41 @@ - (void)testForcedScaling @"Contents should be 100 x 100 by contents scale."); } -- (void)testTintColorBlock +- (void)testTintColorOnNodePropertyAlwaysTemplate { UIImage *test = [self testImage]; - UIImage *tinted = ASImageNodeTintColorModificationBlock([UIColor redColor])(test); ASImageNode *node = [[ASImageNode alloc] init]; - node.image = tinted; + 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; + // Tint color should not change view since it depends on being contained within certain views + // for automatic rendering to utilize tint color. + 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); } @@ -87,7 +113,6 @@ - (void)testRoundedCornerBlock ASImageNode *node = [[ASImageNode alloc] init]; node.image = rounded; ASDisplayNodeSizeToFitSize(node, rounded.size); - ASSnapshotVerifyNode(node, nil); } diff --git a/Tests/ASTextKitTruncationTests.mm b/Tests/ASTextKitTruncationTests.mm index a9d5544c3..1e19c8d0f 100644 --- a/Tests/ASTextKitTruncationTests.mm +++ b/Tests/ASTextKitTruncationTests.mm @@ -42,6 +42,7 @@ - (void)testEmptyTruncationStringSameAsStraightTextKitTailTruncation CGSize constrainedSize = CGSizeMake(100, 50); NSAttributedString *attributedString = [self _sentenceAttributedString]; ASTextKitContext *context = [[ASTextKitContext alloc] initWithAttributedString:attributedString + tintColor:nil lineBreakMode:NSLineBreakByWordWrapping maximumNumberOfLines:0 exclusionPaths:nil @@ -64,6 +65,7 @@ - (void)testSimpleTailTruncation CGSize constrainedSize = CGSizeMake(100, 60); NSAttributedString *attributedString = [self _sentenceAttributedString]; ASTextKitContext *context = [[ASTextKitContext alloc] initWithAttributedString:attributedString + tintColor:nil lineBreakMode:NSLineBreakByWordWrapping maximumNumberOfLines:0 exclusionPaths:nil @@ -87,6 +89,7 @@ - (void)testAvoidedCharTailWordBoundaryTruncation CGSize constrainedSize = CGSizeMake(100, 50); NSAttributedString *attributedString = [self _sentenceAttributedString]; ASTextKitContext *context = [[ASTextKitContext alloc] initWithAttributedString:attributedString + tintColor:nil lineBreakMode:NSLineBreakByWordWrapping maximumNumberOfLines:0 exclusionPaths:nil @@ -109,6 +112,7 @@ - (void)testAvoidedCharTailCharBoundaryTruncation CGSize constrainedSize = CGSizeMake(50, 50); NSAttributedString *attributedString = [self _sentenceAttributedString]; ASTextKitContext *context = [[ASTextKitContext alloc] initWithAttributedString:attributedString + tintColor:nil lineBreakMode:NSLineBreakByCharWrapping maximumNumberOfLines:0 exclusionPaths:nil @@ -132,6 +136,7 @@ - (void)testHandleZeroSizeConstrainedSize NSAttributedString *attributedString = [self _sentenceAttributedString]; ASTextKitContext *context = [[ASTextKitContext alloc] initWithAttributedString:attributedString + tintColor:nil lineBreakMode:NSLineBreakByWordWrapping maximumNumberOfLines:0 exclusionPaths:nil @@ -149,6 +154,7 @@ - (void)testHandleZeroHeightConstrainedSize CGSize constrainedSize = CGSizeMake(50, 0); NSAttributedString *attributedString = [self _sentenceAttributedString]; ASTextKitContext *context = [[ASTextKitContext alloc] initWithAttributedString:attributedString + tintColor:nil lineBreakMode:NSLineBreakByCharWrapping maximumNumberOfLines:0 exclusionPaths:nil diff --git a/Tests/ASTextNode2SnapshotTests.mm b/Tests/ASTextNode2SnapshotTests.mm index 76eaf77ad..af3313fef 100644 --- a/Tests/ASTextNode2SnapshotTests.mm +++ b/Tests/ASTextNode2SnapshotTests.mm @@ -317,4 +317,18 @@ - (void)testThatDefaultTruncationTokenAttributesAreInheritedFromTextWhenTruncate ASSnapshotVerifyNode(textNode, nil); } +- (void)testTextTintColor_ASTextNode2 +{ + // trivial test case to ensure ASSnapshotTestCase works + ASTextNode *textNode = [[ASTextNode alloc] init]; + textNode.attributedText = [[NSAttributedString alloc] initWithString:@"judar" + attributes:@{NSFontAttributeName: [UIFont italicSystemFontOfSize:24]}]; + textNode.textColorFollowsTintColor = YES; + textNode.textContainerInset = UIEdgeInsetsMake(0, 2, 0, 2); + textNode.tintColor = UIColor.redColor; + ASDisplayNodeSizeToFitSizeRange(textNode, ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX))); + + ASSnapshotVerifyNode(textNode, nil); +} + @end diff --git a/Tests/ReferenceImages_iOS_10/ASButtonNodeSnapshotTests/testChangingTintColor@2x.png b/Tests/ReferenceImages_iOS_10/ASButtonNodeSnapshotTests/testChangingTintColor@2x.png new file mode 100644 index 000000000..5c8a8a573 Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASButtonNodeSnapshotTests/testChangingTintColor@2x.png differ diff --git a/Tests/ReferenceImages_iOS_10/ASButtonNodeSnapshotTests/testChangingTintColor_modified_tint@2x.png b/Tests/ReferenceImages_iOS_10/ASButtonNodeSnapshotTests/testChangingTintColor_modified_tint@2x.png new file mode 100644 index 000000000..89a8b63cc Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASButtonNodeSnapshotTests/testChangingTintColor_modified_tint@2x.png differ diff --git a/Tests/ReferenceImages_iOS_10/ASButtonNodeSnapshotTests/testTintColor@2x.png b/Tests/ReferenceImages_iOS_10/ASButtonNodeSnapshotTests/testTintColor@2x.png new file mode 100644 index 000000000..5c8a8a573 Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASButtonNodeSnapshotTests/testTintColor@2x.png differ diff --git a/Tests/ReferenceImages_iOS_10/ASButtonNodeSnapshotTests/testTintColorWithForegroundColorSet@2x.png b/Tests/ReferenceImages_iOS_10/ASButtonNodeSnapshotTests/testTintColorWithForegroundColorSet@2x.png new file mode 100644 index 000000000..e6dfeed94 Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASButtonNodeSnapshotTests/testTintColorWithForegroundColorSet@2x.png differ diff --git a/Tests/ReferenceImages_iOS_10/ASImageNodeSnapshotTests/testForcedScaling_first@2x.png b/Tests/ReferenceImages_iOS_10/ASImageNodeSnapshotTests/testForcedScaling_first@2x.png new file mode 100644 index 000000000..03963aea3 Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASImageNodeSnapshotTests/testForcedScaling_first@2x.png differ diff --git a/Tests/ReferenceImages_iOS_10/ASImageNodeSnapshotTests/testForcedScaling_second@2x.png b/Tests/ReferenceImages_iOS_10/ASImageNodeSnapshotTests/testForcedScaling_second@2x.png new file mode 100644 index 000000000..3e2f7232e Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASImageNodeSnapshotTests/testForcedScaling_second@2x.png differ diff --git a/Tests/ReferenceImages_iOS_10/ASImageNodeSnapshotTests/testRenderLogoSquare@2x.png b/Tests/ReferenceImages_iOS_10/ASImageNodeSnapshotTests/testRenderLogoSquare@2x.png new file mode 100644 index 000000000..03963aea3 Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASImageNodeSnapshotTests/testRenderLogoSquare@2x.png differ diff --git a/Tests/ReferenceImages_iOS_10/ASImageNodeSnapshotTests/testRoundedCornerBlock@2x.png b/Tests/ReferenceImages_iOS_10/ASImageNodeSnapshotTests/testRoundedCornerBlock@2x.png new file mode 100644 index 000000000..bccba69ca Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASImageNodeSnapshotTests/testRoundedCornerBlock@2x.png differ diff --git a/Tests/ReferenceImages_iOS_10/ASImageNodeSnapshotTests/testTintColorOnNodePropertyAlwaysOriginal@2x.png b/Tests/ReferenceImages_iOS_10/ASImageNodeSnapshotTests/testTintColorOnNodePropertyAlwaysOriginal@2x.png new file mode 100644 index 000000000..3b19513ef Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASImageNodeSnapshotTests/testTintColorOnNodePropertyAlwaysOriginal@2x.png differ diff --git a/Tests/ReferenceImages_iOS_10/ASImageNodeSnapshotTests/testTintColorOnNodePropertyAlwaysTemplate_blue_tint@2x.png b/Tests/ReferenceImages_iOS_10/ASImageNodeSnapshotTests/testTintColorOnNodePropertyAlwaysTemplate_blue_tint@2x.png new file mode 100644 index 000000000..2a1cc4685 Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASImageNodeSnapshotTests/testTintColorOnNodePropertyAlwaysTemplate_blue_tint@2x.png differ diff --git a/Tests/ReferenceImages_iOS_10/ASImageNodeSnapshotTests/testTintColorOnNodePropertyAlwaysTemplate_red_tint@2x.png b/Tests/ReferenceImages_iOS_10/ASImageNodeSnapshotTests/testTintColorOnNodePropertyAlwaysTemplate_red_tint@2x.png new file mode 100644 index 000000000..1d1b9d95b Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASImageNodeSnapshotTests/testTintColorOnNodePropertyAlwaysTemplate_red_tint@2x.png differ diff --git a/Tests/ReferenceImages_iOS_10/ASImageNodeSnapshotTests/testTintColorOnNodePropertyAutomatic@2x.png b/Tests/ReferenceImages_iOS_10/ASImageNodeSnapshotTests/testTintColorOnNodePropertyAutomatic@2x.png new file mode 100644 index 000000000..3b19513ef Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASImageNodeSnapshotTests/testTintColorOnNodePropertyAutomatic@2x.png differ diff --git a/Tests/ReferenceImages_iOS_10/ASTextNode2SnapshotTests/testTextTintColor_ASTextNode2@2x.png b/Tests/ReferenceImages_iOS_10/ASTextNode2SnapshotTests/testTextTintColor_ASTextNode2@2x.png new file mode 100644 index 000000000..fc3bb798b Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASTextNode2SnapshotTests/testTextTintColor_ASTextNode2@2x.png differ