From 8621d4b79731e13a0c6e397abd93c193c6219000 Mon Sep 17 00:00:00 2001 From: TomSwift Date: Mon, 16 Apr 2018 08:59:26 -0700 Subject: [PATCH] iOS textTransform style support Summary: Issue [#2088](https://github.com/facebook/react-native/issues/2088). The basic desire is to have a declarative mechanism to transform text content to uppercase or lowercase or titlecase ("capitalized"). My test plan involves having added a test-case to the RNTester app within the `` component area. I then manually verified that the rendered content met my expectation. Here is the markup that exercises my enhancement: ``` This text should be uppercased. This TEXT SHOULD be lowercased. This text should be CAPITALIZED. Mixed:{' '} uppercase{' '} LoWeRcAsE{' '} capitalize each word ``` And here is a screenshot of the result: ![screen shot 2018-03-14 at 3 01 02 pm](https://user-images.githubusercontent.com/575821/37433772-7abe7fa0-279a-11e8-9ec9-fb3aa1952dad.png) [Website Documentation PR](https://github.com/facebook/react-native-website/pull/254) https://github.com/facebook/react-native-website/pull/254 [IOS] [ENHANCEMENT] [Text] - added textTransform style property enabling declarative casing transformations Closes https://github.com/facebook/react-native/pull/18387 Differential Revision: D7583315 Pulled By: shergin fbshipit-source-id: a5d22aea2aa4f494b7b25a055abe64799ccbaa79 --- Libraries/StyleSheet/StyleSheetTypes.js | 1 + .../Text/BaseText/RCTBaseTextShadowView.m | 2 +- .../Text/BaseText/RCTBaseTextViewManager.m | 1 + Libraries/Text/RCTConvert+Text.h | 2 ++ Libraries/Text/RCTConvert+Text.m | 7 ++++ .../Text/RCTText.xcodeproj/project.pbxproj | 6 ++++ Libraries/Text/RCTTextAttributes.h | 7 ++++ Libraries/Text/RCTTextAttributes.m | 20 ++++++++++- Libraries/Text/RCTTextTransform.h | 16 +++++++++ Libraries/Text/TextStylePropTypes.js | 6 ++++ RNTester/js/TextExample.ios.js | 36 +++++++++++++++++++ 11 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 Libraries/Text/RCTTextTransform.h diff --git a/Libraries/StyleSheet/StyleSheetTypes.js b/Libraries/StyleSheet/StyleSheetTypes.js index cd80f8b8d..ffec5b54b 100644 --- a/Libraries/StyleSheet/StyleSheetTypes.js +++ b/Libraries/StyleSheet/StyleSheetTypes.js @@ -201,6 +201,7 @@ export type ____TextStyle_Internal = $ReadOnly<{| | 'underline line-through', textDecorationStyle?: 'solid' | 'double' | 'dotted' | 'dashed', textDecorationColor?: ColorValue, + textTransform?: 'none' | 'capitalize' | 'uppercase' | 'lowercase', writingDirection?: 'auto' | 'ltr' | 'rtl', |}>; diff --git a/Libraries/Text/BaseText/RCTBaseTextShadowView.m b/Libraries/Text/BaseText/RCTBaseTextShadowView.m index 85162926e..49ce7690d 100644 --- a/Libraries/Text/BaseText/RCTBaseTextShadowView.m +++ b/Libraries/Text/BaseText/RCTBaseTextShadowView.m @@ -63,7 +63,7 @@ NSString *const RCTBaseTextShadowViewEmbeddedShadowViewAttributeName = @"RCTBase NSString *text = rawTextShadowView.text; if (text) { NSAttributedString *rawTextAttributedString = - [[NSAttributedString alloc] initWithString:rawTextShadowView.text + [[NSAttributedString alloc] initWithString:[textAttributes applyTextAttributesToText:text] attributes:textAttributes.effectiveTextAttributes]; [attributedText appendAttributedString:rawTextAttributedString]; } diff --git a/Libraries/Text/BaseText/RCTBaseTextViewManager.m b/Libraries/Text/BaseText/RCTBaseTextViewManager.m index a5d0b2c63..2cf43ebf3 100644 --- a/Libraries/Text/BaseText/RCTBaseTextViewManager.m +++ b/Libraries/Text/BaseText/RCTBaseTextViewManager.m @@ -51,5 +51,6 @@ RCT_REMAP_SHADOW_PROPERTY(textShadowRadius, textAttributes.textShadowRadius, CGF RCT_REMAP_SHADOW_PROPERTY(textShadowColor, textAttributes.textShadowColor, UIColor) // Special RCT_REMAP_SHADOW_PROPERTY(isHighlighted, textAttributes.isHighlighted, BOOL) +RCT_REMAP_SHADOW_PROPERTY(textTransform, textAttributes.textTransform, RCTTextTransform) @end diff --git a/Libraries/Text/RCTConvert+Text.h b/Libraries/Text/RCTConvert+Text.h index 4cead0948..81a3c7ccf 100644 --- a/Libraries/Text/RCTConvert+Text.h +++ b/Libraries/Text/RCTConvert+Text.h @@ -6,6 +6,7 @@ */ #import +#import NS_ASSUME_NONNULL_BEGIN @@ -13,6 +14,7 @@ NS_ASSUME_NONNULL_BEGIN + (UITextAutocorrectionType)UITextAutocorrectionType:(nullable id)json; + (UITextSpellCheckingType)UITextSpellCheckingType:(nullable id)json; ++ (RCTTextTransform)RCTTextTransform:(nullable id)json; @end diff --git a/Libraries/Text/RCTConvert+Text.m b/Libraries/Text/RCTConvert+Text.m index d4d1b167c..aaf7c1383 100644 --- a/Libraries/Text/RCTConvert+Text.m +++ b/Libraries/Text/RCTConvert+Text.m @@ -25,4 +25,11 @@ UITextSpellCheckingTypeNo; } +RCT_ENUM_CONVERTER(RCTTextTransform, (@{ + @"none": @(RCTTextTransformNone), + @"capitalize": @(RCTTextTransformCapitalize), + @"uppercase": @(RCTTextTransformUppercase), + @"lowercase": @(RCTTextTransformLowercase), +}), RCTTextTransformUndefined, integerValue) + @end diff --git a/Libraries/Text/RCTText.xcodeproj/project.pbxproj b/Libraries/Text/RCTText.xcodeproj/project.pbxproj index 8fc301593..9765d035f 100644 --- a/Libraries/Text/RCTText.xcodeproj/project.pbxproj +++ b/Libraries/Text/RCTText.xcodeproj/project.pbxproj @@ -104,6 +104,8 @@ 5956B1A7200FF35C008D9D16 /* RCTVirtualTextShadowView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B12E200FEBAA008D9D16 /* RCTVirtualTextShadowView.m */; }; 5956B1A8200FF35C008D9D16 /* RCTVirtualTextViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5956B12B200FEBAA008D9D16 /* RCTVirtualTextViewManager.m */; }; 5C245F39205E216A00D936E9 /* RCTInputAccessoryShadowView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C245F37205E216A00D936E9 /* RCTInputAccessoryShadowView.m */; }; + 5970936920845EFC00D163A7 /* RCTTextTransform.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5970936820845DDE00D163A7 /* RCTTextTransform.h */; }; + 5970936A20845F0600D163A7 /* RCTTextTransform.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5970936820845DDE00D163A7 /* RCTTextTransform.h */; }; 8F2807C7202D2B6B005D65E6 /* RCTInputAccessoryViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 8F2807C1202D2B6A005D65E6 /* RCTInputAccessoryViewManager.m */; }; 8F2807C8202D2B6B005D65E6 /* RCTInputAccessoryView.m in Sources */ = {isa = PBXBuildFile; fileRef = 8F2807C3202D2B6A005D65E6 /* RCTInputAccessoryView.m */; }; 8F2807C9202D2B6B005D65E6 /* RCTInputAccessoryViewContent.m in Sources */ = {isa = PBXBuildFile; fileRef = 8F2807C5202D2B6B005D65E6 /* RCTInputAccessoryViewContent.m */; }; @@ -116,6 +118,7 @@ dstPath = include/RCTText; dstSubfolderSpec = 16; files = ( + 5970936920845EFC00D163A7 /* RCTTextTransform.h in Copy Headers */, 5956B160200FF324008D9D16 /* RCTBaseTextShadowView.h in Copy Headers */, 5956B161200FF324008D9D16 /* RCTBaseTextViewManager.h in Copy Headers */, 5956B162200FF324008D9D16 /* RCTRawTextShadowView.h in Copy Headers */, @@ -151,6 +154,7 @@ dstPath = include/RCTText; dstSubfolderSpec = 16; files = ( + 5970936A20845F0600D163A7 /* RCTTextTransform.h in Copy Headers */, 5956B179200FF338008D9D16 /* RCTBaseTextShadowView.h in Copy Headers */, 5956B17A200FF338008D9D16 /* RCTBaseTextViewManager.h in Copy Headers */, 5956B17B200FF338008D9D16 /* RCTRawTextShadowView.h in Copy Headers */, @@ -235,6 +239,7 @@ 5956B12F200FEBAA008D9D16 /* RCTConvert+Text.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RCTConvert+Text.m"; sourceTree = ""; }; 5C245F37205E216A00D936E9 /* RCTInputAccessoryShadowView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTInputAccessoryShadowView.m; sourceTree = ""; }; 5C245F38205E216A00D936E9 /* RCTInputAccessoryShadowView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTInputAccessoryShadowView.h; sourceTree = ""; }; + 5970936820845DDE00D163A7 /* RCTTextTransform.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTextTransform.h; sourceTree = ""; }; 8F2807C1202D2B6A005D65E6 /* RCTInputAccessoryViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTInputAccessoryViewManager.m; sourceTree = ""; }; 8F2807C2202D2B6A005D65E6 /* RCTInputAccessoryViewContent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTInputAccessoryViewContent.h; sourceTree = ""; }; 8F2807C3202D2B6A005D65E6 /* RCTInputAccessoryView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTInputAccessoryView.m; sourceTree = ""; }; @@ -254,6 +259,7 @@ 5956B12F200FEBAA008D9D16 /* RCTConvert+Text.m */, 5956B11A200FEBA9008D9D16 /* RCTTextAttributes.h */, 5956B120200FEBA9008D9D16 /* RCTTextAttributes.m */, + 5970936820845DDE00D163A7 /* RCTTextTransform.h */, 5956B121200FEBAA008D9D16 /* Text */, 5956B0FF200FEBA9008D9D16 /* TextInput */, 5956B12A200FEBAA008D9D16 /* VirtualText */, diff --git a/Libraries/Text/RCTTextAttributes.h b/Libraries/Text/RCTTextAttributes.h index df9171c85..802211909 100644 --- a/Libraries/Text/RCTTextAttributes.h +++ b/Libraries/Text/RCTTextAttributes.h @@ -8,6 +8,7 @@ #import #import +#import NS_ASSUME_NONNULL_BEGIN @@ -50,6 +51,7 @@ extern NSString *const RCTTextAttributesTagAttributeName; @property (nonatomic, assign) BOOL isHighlighted; @property (nonatomic, strong, nullable) NSNumber *tag; @property (nonatomic, assign) UIUserInterfaceLayoutDirection layoutDirection; +@property (nonatomic, assign) RCTTextTransform textTransform; #pragma mark - Inheritance @@ -78,6 +80,11 @@ extern NSString *const RCTTextAttributesTagAttributeName; - (UIColor *)effectiveForegroundColor; - (UIColor *)effectiveBackgroundColor; +/** + * Text transformed per 'none', 'uppercase', 'lowercase', 'capitalize' + */ +- (NSString *)applyTextAttributesToText:(NSString *)text; + @end NS_ASSUME_NONNULL_END diff --git a/Libraries/Text/RCTTextAttributes.m b/Libraries/Text/RCTTextAttributes.m index d74e0ab76..4309138d0 100644 --- a/Libraries/Text/RCTTextAttributes.m +++ b/Libraries/Text/RCTTextAttributes.m @@ -28,6 +28,7 @@ NSString *const RCTTextAttributesTagAttributeName = @"RCTTextAttributesTagAttrib _baseWritingDirection = NSWritingDirectionNatural; _textShadowRadius = NAN; _opacity = NAN; + _textTransform = RCTTextTransformUndefined; } return self; @@ -73,6 +74,7 @@ NSString *const RCTTextAttributesTagAttributeName = @"RCTTextAttributesTagAttrib _isHighlighted = textAttributes->_isHighlighted || _isHighlighted; // * _tag = textAttributes->_tag ?: _tag; _layoutDirection = textAttributes->_layoutDirection != UIUserInterfaceLayoutDirectionLeftToRight ? textAttributes->_layoutDirection : _layoutDirection; + _textTransform = textAttributes->_textTransform != RCTTextTransformUndefined ? textAttributes->_textTransform : _textTransform; } - (NSDictionary *)effectiveTextAttributes @@ -214,6 +216,21 @@ NSString *const RCTTextAttributesTagAttributeName = @"RCTTextAttributesTagAttrib return effectiveBackgroundColor ?: [UIColor clearColor]; } +- (NSString *)applyTextAttributesToText:(NSString *)text +{ + switch (_textTransform) { + case RCTTextTransformUndefined: + case RCTTextTransformNone: + return text; + case RCTTextTransformLowercase: + return [text lowercaseString]; + case RCTTextTransformUppercase: + return [text uppercaseString]; + case RCTTextTransformCapitalize: + return [text capitalizedString]; + } +} + - (RCTTextAttributes *)copyWithZone:(NSZone *)zone { RCTTextAttributes *textAttributes = [RCTTextAttributes new]; @@ -263,7 +280,8 @@ NSString *const RCTTextAttributesTagAttributeName = @"RCTTextAttributesTagAttrib // Special RCTTextAttributesCompareOthers(_isHighlighted) && RCTTextAttributesCompareObjects(_tag) && - RCTTextAttributesCompareOthers(_layoutDirection); + RCTTextAttributesCompareOthers(_layoutDirection) && + RCTTextAttributesCompareOthers(_textTransform); } @end diff --git a/Libraries/Text/RCTTextTransform.h b/Libraries/Text/RCTTextTransform.h new file mode 100644 index 000000000..eddebd0f0 --- /dev/null +++ b/Libraries/Text/RCTTextTransform.h @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import + +typedef NS_ENUM(NSInteger, RCTTextTransform) { + RCTTextTransformUndefined = 0, + RCTTextTransformNone, + RCTTextTransformCapitalize, + RCTTextTransformUppercase, + RCTTextTransformLowercase, +}; diff --git a/Libraries/Text/TextStylePropTypes.js b/Libraries/Text/TextStylePropTypes.js index c5e67ca99..872497ecf 100644 --- a/Libraries/Text/TextStylePropTypes.js +++ b/Libraries/Text/TextStylePropTypes.js @@ -84,6 +84,12 @@ const TextStylePropTypes = { * @platform ios */ textDecorationColor: ColorPropType, + /** + * @platform ios + */ + textTransform: ReactPropTypes.oneOf( + ['none' /*default*/, 'capitalize', 'uppercase', 'lowercase'] + ), /** * @platform ios */ diff --git a/RNTester/js/TextExample.ios.js b/RNTester/js/TextExample.ios.js index e5510390d..939774aa0 100644 --- a/RNTester/js/TextExample.ios.js +++ b/RNTester/js/TextExample.ios.js @@ -860,6 +860,42 @@ exports.examples = [ title: 'Text `alignItems: \'baseline\'` style', render: function() { return ; + } + }, + { + title: 'Transform', + render: function() { + return ( + + + This text should be uppercased. + + + This TEXT SHOULD be lowercased. + + + This text should be CAPITALIZED. + + + Mixed:{' '} + + uppercase{' '} + + + LoWeRcAsE{' '} + + + capitalize each word + + + Should be "ABC": + abc + + Should be "AbC": + abc + + + ); }, }, ];