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 `<Text>` component area.   I then manually verified that the rendered content met my expectation.

Here is the markup that exercises my enhancement:

```
<View>
  <Text style={{ textTransform: 'uppercase'}}>
    This text should be uppercased.
  </Text>
  <Text style={{ textTransform: 'lowercase'}}>
    This TEXT SHOULD be lowercased.
  </Text>
  <Text style={{ textTransform: 'capitalize'}}>
    This text should be CAPITALIZED.
  </Text>
  <Text style={{ textTransform: 'capitalize'}}>
    Mixed:{' '}
    <Text style={{ textTransform: 'uppercase'}}>
      uppercase{' '}
    </Text>
    <Text style={{ textTransform: 'lowercase'}}>
      LoWeRcAsE{' '}
    </Text>
    <Text style={{ textTransform: 'capitalize'}}>
      capitalize each word
    </Text>
  </Text>
</View>
```

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
This commit is contained in:
TomSwift 2018-04-16 08:59:26 -07:00 committed by Facebook Github Bot
parent 82bd4337c9
commit 8621d4b797
11 changed files with 102 additions and 2 deletions

View File

@ -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',
|}>;

View File

@ -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];
}

View File

@ -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

View File

@ -6,6 +6,7 @@
*/
#import <React/RCTConvert.h>
#import <RCTText/RCTTextTransform.h>
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

View File

@ -25,4 +25,11 @@
UITextSpellCheckingTypeNo;
}
RCT_ENUM_CONVERTER(RCTTextTransform, (@{
@"none": @(RCTTextTransformNone),
@"capitalize": @(RCTTextTransformCapitalize),
@"uppercase": @(RCTTextTransformUppercase),
@"lowercase": @(RCTTextTransformLowercase),
}), RCTTextTransformUndefined, integerValue)
@end

View File

@ -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 = "<group>"; };
5C245F37205E216A00D936E9 /* RCTInputAccessoryShadowView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTInputAccessoryShadowView.m; sourceTree = "<group>"; };
5C245F38205E216A00D936E9 /* RCTInputAccessoryShadowView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTInputAccessoryShadowView.h; sourceTree = "<group>"; };
5970936820845DDE00D163A7 /* RCTTextTransform.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTextTransform.h; sourceTree = "<group>"; };
8F2807C1202D2B6A005D65E6 /* RCTInputAccessoryViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTInputAccessoryViewManager.m; sourceTree = "<group>"; };
8F2807C2202D2B6A005D65E6 /* RCTInputAccessoryViewContent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTInputAccessoryViewContent.h; sourceTree = "<group>"; };
8F2807C3202D2B6A005D65E6 /* RCTInputAccessoryView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTInputAccessoryView.m; sourceTree = "<group>"; };
@ -254,6 +259,7 @@
5956B12F200FEBAA008D9D16 /* RCTConvert+Text.m */,
5956B11A200FEBA9008D9D16 /* RCTTextAttributes.h */,
5956B120200FEBA9008D9D16 /* RCTTextAttributes.m */,
5970936820845DDE00D163A7 /* RCTTextTransform.h */,
5956B121200FEBAA008D9D16 /* Text */,
5956B0FF200FEBA9008D9D16 /* TextInput */,
5956B12A200FEBAA008D9D16 /* VirtualText */,

View File

@ -8,6 +8,7 @@
#import <UIKit/UIKit.h>
#import <React/RCTTextDecorationLineType.h>
#import <RCTText/RCTTextTransform.h>
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

View File

@ -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<NSAttributedStringKey, id> *)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

View File

@ -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 <Foundation/Foundation.h>
typedef NS_ENUM(NSInteger, RCTTextTransform) {
RCTTextTransformUndefined = 0,
RCTTextTransformNone,
RCTTextTransformCapitalize,
RCTTextTransformUppercase,
RCTTextTransformLowercase,
};

View File

@ -84,6 +84,12 @@ const TextStylePropTypes = {
* @platform ios
*/
textDecorationColor: ColorPropType,
/**
* @platform ios
*/
textTransform: ReactPropTypes.oneOf(
['none' /*default*/, 'capitalize', 'uppercase', 'lowercase']
),
/**
* @platform ios
*/

View File

@ -860,6 +860,42 @@ exports.examples = [
title: 'Text `alignItems: \'baseline\'` style',
render: function() {
return <TextBaseLineLayoutExample />;
}
},
{
title: 'Transform',
render: function() {
return (
<View>
<Text style={{ textTransform: 'uppercase'}}>
This text should be uppercased.
</Text>
<Text style={{ textTransform: 'lowercase'}}>
This TEXT SHOULD be lowercased.
</Text>
<Text style={{ textTransform: 'capitalize'}}>
This text should be CAPITALIZED.
</Text>
<Text style={{ textTransform: 'capitalize'}}>
Mixed:{' '}
<Text style={{ textTransform: 'uppercase'}}>
uppercase{' '}
</Text>
<Text style={{ textTransform: 'lowercase'}}>
LoWeRcAsE{' '}
</Text>
<Text style={{ textTransform: 'capitalize'}}>
capitalize each word
</Text>
</Text>
<Text>Should be "ABC":
<Text style={{ textTransform: 'uppercase' }}>a<Text>b</Text>c</Text>
</Text>
<Text>Should be "AbC":
<Text style={{ textTransform: 'uppercase' }}>a<Text style={{ textTransform: 'none' }}>b</Text>c</Text>
</Text>
</View>
);
},
},
];