Skip to content

Commit

Permalink
iOS textTransform style support
Browse files Browse the repository at this point in the history
Summary:
Issue [#2088](#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](facebook/react-native-website#254)
facebook/react-native-website#254

[IOS] [ENHANCEMENT] [Text] - added textTransform style property enabling declarative casing transformations
Closes #18387

Differential Revision: D7583315

Pulled By: shergin

fbshipit-source-id: a5d22aea2aa4f494b7b25a055abe64799ccbaa79
  • Loading branch information
TomSwift authored and facebook-github-bot committed Apr 16, 2018
1 parent 82bd433 commit 8621d4b
Show file tree
Hide file tree
Showing 11 changed files with 102 additions and 2 deletions.
1 change: 1 addition & 0 deletions Libraries/StyleSheet/StyleSheetTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
|}>;

Expand Down
2 changes: 1 addition & 1 deletion Libraries/Text/BaseText/RCTBaseTextShadowView.m
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ - (NSAttributedString *)attributedTextWithBaseTextAttributes:(nullable RCTTextAt
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];
}
Expand Down
1 change: 1 addition & 0 deletions Libraries/Text/BaseText/RCTBaseTextViewManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,6 @@ - (RCTShadowView *)shadowView
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
2 changes: 2 additions & 0 deletions Libraries/Text/RCTConvert+Text.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
*/

#import <React/RCTConvert.h>
#import <RCTText/RCTTextTransform.h>

NS_ASSUME_NONNULL_BEGIN

@interface RCTConvert (Text)

+ (UITextAutocorrectionType)UITextAutocorrectionType:(nullable id)json;
+ (UITextSpellCheckingType)UITextSpellCheckingType:(nullable id)json;
+ (RCTTextTransform)RCTTextTransform:(nullable id)json;

@end

Expand Down
7 changes: 7 additions & 0 deletions Libraries/Text/RCTConvert+Text.m
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,11 @@ + (UITextSpellCheckingType)UITextSpellCheckingType:(id)json
UITextSpellCheckingTypeNo;
}

RCT_ENUM_CONVERTER(RCTTextTransform, (@{
@"none": @(RCTTextTransformNone),
@"capitalize": @(RCTTextTransformCapitalize),
@"uppercase": @(RCTTextTransformUppercase),
@"lowercase": @(RCTTextTransformLowercase),
}), RCTTextTransformUndefined, integerValue)

@end
6 changes: 6 additions & 0 deletions Libraries/Text/RCTText.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand All @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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>"; };
Expand All @@ -254,6 +259,7 @@
5956B12F200FEBAA008D9D16 /* RCTConvert+Text.m */,
5956B11A200FEBA9008D9D16 /* RCTTextAttributes.h */,
5956B120200FEBA9008D9D16 /* RCTTextAttributes.m */,
5970936820845DDE00D163A7 /* RCTTextTransform.h */,
5956B121200FEBAA008D9D16 /* Text */,
5956B0FF200FEBA9008D9D16 /* TextInput */,
5956B12A200FEBAA008D9D16 /* VirtualText */,
Expand Down
7 changes: 7 additions & 0 deletions Libraries/Text/RCTTextAttributes.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#import <UIKit/UIKit.h>

#import <React/RCTTextDecorationLineType.h>
#import <RCTText/RCTTextTransform.h>

NS_ASSUME_NONNULL_BEGIN

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
20 changes: 19 additions & 1 deletion Libraries/Text/RCTTextAttributes.m
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ - (instancetype)init
_baseWritingDirection = NSWritingDirectionNatural;
_textShadowRadius = NAN;
_opacity = NAN;
_textTransform = RCTTextTransformUndefined;
}

return self;
Expand Down Expand Up @@ -73,6 +74,7 @@ - (void)applyTextAttributes:(RCTTextAttributes *)textAttributes
_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
Expand Down Expand Up @@ -214,6 +216,21 @@ - (UIColor *)effectiveBackgroundColor
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];
Expand Down Expand Up @@ -263,7 +280,8 @@ - (BOOL)isEqual:(RCTTextAttributes *)textAttributes
// Special
RCTTextAttributesCompareOthers(_isHighlighted) &&
RCTTextAttributesCompareObjects(_tag) &&
RCTTextAttributesCompareOthers(_layoutDirection);
RCTTextAttributesCompareOthers(_layoutDirection) &&
RCTTextAttributesCompareOthers(_textTransform);
}

@end
16 changes: 16 additions & 0 deletions Libraries/Text/RCTTextTransform.h
Original file line number Diff line number Diff line change
@@ -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,
};
6 changes: 6 additions & 0 deletions Libraries/Text/TextStylePropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ const TextStylePropTypes = {
* @platform ios
*/
textDecorationColor: ColorPropType,
/**
* @platform ios
*/
textTransform: ReactPropTypes.oneOf(
['none' /*default*/, 'capitalize', 'uppercase', 'lowercase']
),
/**
* @platform ios
*/
Expand Down
36 changes: 36 additions & 0 deletions RNTester/js/TextExample.ios.js
Original file line number Diff line number Diff line change
Expand Up @@ -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>
);
},
},
];

0 comments on commit 8621d4b

Please sign in to comment.