制作多行,使用 React-Native 扩展 TextInput
making a multiline, expanding TextInput with React-Native
我正在开发一个 react-native 应用程序,需要一个 TextInput,它与 iOS 上的 "messages" 应用程序中的文本视图具有相似的功能——它应该从一行开始,然后优雅地扩展到更多行直到达到某个限制(例如 5 行文本),然后根据需要开始滚动到最新行。
查看了 SlackTextViewController
但 a) 似乎它有很多我不想要的东西 b) 我想尝试在 React 中保留尽可能多的代码(并且objective-C/swift) 尽可能。
编辑:只想强调我更喜欢 REACT (JAVASCRIPT) 代码,如上所述,而不是 Objective-C 或 Swift.
实现 UITextView
的委托方法 textViewDidChange
并使用 rect
- (void)textViewDidChange:(UITextView *)textView {
CGSize constraintSize = CGSizeMake(textView.frame.size.width, MAXFLOAT);
CGRect textRect = [textView.text boundingRectWithSize:constraintSize
options:NSStringDrawingUsesLineFragmentOrigin
attributes:@{NSFontAttributeName:textView.font}
context:nil];
NSLog(@"Frame:%@", NSStringFromCGRect(textRect));
CGRect newRect = textView.frame;
newRect.size.height = textRect.size.height;
textView.frame = newRect;
}
我今天尝试了两种不同的方法。两者都不是最好的,但我想我会记录下我的努力,以防它们有帮助。它们都确实具有您正在寻找的效果,尽管有时会因所有异步通信而延迟。
1) 离屏文字高度
因此,就在 TextInput 下,我添加了一个具有相同字体和填充等的常规文本字段。我在输入上注册了 onChange
侦听器并调用了 setState({text: event.nativeEvent.text})
。文本文件的价值来自国家。两者都有 onLayout
听众。基本上,目标是从(不受限制的)文本中获取 TextInput 的高度。然后我将文本方式隐藏在屏幕外
https://gist.github.com/bleonard/f7d748e89ad2a485ec34
2) 原生模块
真的,我只需要 real UITextView 中内容的高度。所以我向 RCTUIManager 添加了一个类别,因为那里已经有几种有用的方法。我摆脱了隐藏的文本视图。所以onChange
,我要求高度并通过状态以相同的方式使用它。
https://gist.github.com/bleonard/6770fbfe0394a34c864b
3) Github PR
我真正希望的是这个 PR 能被接受。它看起来会自动执行类似的操作。
将 multiline={true}
添加到 TextInput 将允许在文本数量超过可用 space 时滚动。然后,您可以通过从 onChange 属性访问事件的 nativeEvent.contentSize.height 来更改 TextInput 的高度。
class Comment extends Component {
state = {
text: '',
height: 25
}
onTextChange(event) {
const { contentSize, text } = event.nativeEvent;
this.setState({
text: text,
height: contentSize.height > 100 ? 100 : contentSize.height
});
}
render() {
return (
<TextInput
multiline
style={{ height: this.state.height }}
onChange={this.onTextChange.bind(this)}
value={this.state.text}
/>
);
}
}
截至 17 年 10 月,Wix 有一个很好的组件可以执行此操作:
https://github.com/wix/react-native-autogrow-textinput
用法可以很简单:
<AutoGrowingTextInput
style={styles.textInput}
placeholder="Enter text"
value={this.state.text}
onChangeText={this._handleChangeText}
/>
还有一些额外的道具,例如 minHeight
和 maxHeight
。
我在 RN 0.47.2 上使用它
另一个解决方案是检查 '\n'
符号并设置 numberOfLines
属性。
适合我。
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import {TextInput} from 'react-native';
export default class TextInputAutogrow extends Component {
constructor(props) {
super(props);
this._ref = null;
this.bindRef = this.bindRef.bind(this);
this.onChangeText = this.onChangeText.bind(this);
this.state = {
numberOfLines: this.getNumberOfLines()
};
}
bindRef(c) {
this._ref = c;
this.props.innerRef && this.props.innerRef(c);
}
getText() {
return typeof this.props.value === 'string' ?
this.props.value :
(
typeof this.props.defaultValue === 'string' ?
this.props.defaultValue :
''
);
}
getNumberOfLines(value) {
if (value === undefined) {
value = this.getText();
}
return Math.max(this.props.numberOfLines, value.split('\n').length - 1) + 1;
}
onChangeText(value) {
this.setState({numberOfLines: this.getNumberOfLines(value)})
}
render() {
return (
<TextInput
{...this.props}
ref={this.bindRef}
numberOfLines={this.state.numberOfLines}
onChangeText={this.onChangeText}
/>
)
}
}
TextInputAutogrow.propTypes = {
...TextInput.propTypes,
innerRef: PropTypes.func,
};
TextInputAutogrow.defaultProps = {
numberOfLines: 4,
};
@Groovietunes 几乎全部正确,但截至 26 年 6 月 19 日,事情与他最初的回答有所不同。
首先,textinputs 上的 onChange
道具不再 return 是 contentSize
对象,因此 event.nativeEvent.contentSize
将 return undefined
.
解决这个问题的方法是使用 onContentSizeChange
道具。但是有一个陷阱; onContentSizeChange
仅在组件安装时运行一次,因此无法在组件变化时动态获取高度。
为了解决这个问题,我们需要确保 value
道具设置在道具层次结构中的 onContentSizeChange
之后,以便随着 textinput
的增长继续获取内容大小。
最后我得到了一个看起来像这样的组件
<TextInput
placeholder={this.props.placeholder}
autoFocus={this.props.autoFocus}
style={[
styles.inputFlat,
{ height: this.state.height > 40 ? this.state.height : 40 }
]}
ref="inputFlat"
onFocus={() => {
this.toggleInput();
this.toggleButton();
}}
multiline={this.props.multiline}
onBlur={() => {
this.toggleInput();
this.toggleButton();
}}
onChangeText={this.props.onChangeText}
onContentSizeChange={event => {
const { contentSize } = event.nativeEvent;
this.setState({
height: contentSize.height
});
}}
value={this.state.value}
/>
如果由于某种原因它在查看此答案时似乎已损坏,请参阅 this 文档以获取任何更新
任何人都想要一个简单的解决方案,使用新版本的 RN,目前使用 0.63.4,
如果它是多行的,团队将 autoGrow 添加到 TextInput,就是这种情况。
不要对组件或父组件使用 高度 (StyeSheet 属性) View/Views
改用minHeight、maxHeight,如果启用了多行,TextInput 将增长直到达到maxHeight ,然后将是可滚动的。
不需要维护任何计算和内部状态。
同样适用于 minWidth、maxWidth 如果您需要扩展得更宽。
我正在开发一个 react-native 应用程序,需要一个 TextInput,它与 iOS 上的 "messages" 应用程序中的文本视图具有相似的功能——它应该从一行开始,然后优雅地扩展到更多行直到达到某个限制(例如 5 行文本),然后根据需要开始滚动到最新行。
查看了 SlackTextViewController
但 a) 似乎它有很多我不想要的东西 b) 我想尝试在 React 中保留尽可能多的代码(并且objective-C/swift) 尽可能。
编辑:只想强调我更喜欢 REACT (JAVASCRIPT) 代码,如上所述,而不是 Objective-C 或 Swift.
实现 UITextView
的委托方法 textViewDidChange
并使用 rect
- (void)textViewDidChange:(UITextView *)textView {
CGSize constraintSize = CGSizeMake(textView.frame.size.width, MAXFLOAT);
CGRect textRect = [textView.text boundingRectWithSize:constraintSize
options:NSStringDrawingUsesLineFragmentOrigin
attributes:@{NSFontAttributeName:textView.font}
context:nil];
NSLog(@"Frame:%@", NSStringFromCGRect(textRect));
CGRect newRect = textView.frame;
newRect.size.height = textRect.size.height;
textView.frame = newRect;
}
我今天尝试了两种不同的方法。两者都不是最好的,但我想我会记录下我的努力,以防它们有帮助。它们都确实具有您正在寻找的效果,尽管有时会因所有异步通信而延迟。
1) 离屏文字高度
因此,就在 TextInput 下,我添加了一个具有相同字体和填充等的常规文本字段。我在输入上注册了 onChange
侦听器并调用了 setState({text: event.nativeEvent.text})
。文本文件的价值来自国家。两者都有 onLayout
听众。基本上,目标是从(不受限制的)文本中获取 TextInput 的高度。然后我将文本方式隐藏在屏幕外
https://gist.github.com/bleonard/f7d748e89ad2a485ec34
2) 原生模块
真的,我只需要 real UITextView 中内容的高度。所以我向 RCTUIManager 添加了一个类别,因为那里已经有几种有用的方法。我摆脱了隐藏的文本视图。所以onChange
,我要求高度并通过状态以相同的方式使用它。
https://gist.github.com/bleonard/6770fbfe0394a34c864b
3) Github PR
我真正希望的是这个 PR 能被接受。它看起来会自动执行类似的操作。
将 multiline={true}
添加到 TextInput 将允许在文本数量超过可用 space 时滚动。然后,您可以通过从 onChange 属性访问事件的 nativeEvent.contentSize.height 来更改 TextInput 的高度。
class Comment extends Component {
state = {
text: '',
height: 25
}
onTextChange(event) {
const { contentSize, text } = event.nativeEvent;
this.setState({
text: text,
height: contentSize.height > 100 ? 100 : contentSize.height
});
}
render() {
return (
<TextInput
multiline
style={{ height: this.state.height }}
onChange={this.onTextChange.bind(this)}
value={this.state.text}
/>
);
}
}
截至 17 年 10 月,Wix 有一个很好的组件可以执行此操作:
https://github.com/wix/react-native-autogrow-textinput
用法可以很简单:
<AutoGrowingTextInput
style={styles.textInput}
placeholder="Enter text"
value={this.state.text}
onChangeText={this._handleChangeText}
/>
还有一些额外的道具,例如 minHeight
和 maxHeight
。
我在 RN 0.47.2 上使用它
另一个解决方案是检查 '\n'
符号并设置 numberOfLines
属性。
适合我。
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import {TextInput} from 'react-native';
export default class TextInputAutogrow extends Component {
constructor(props) {
super(props);
this._ref = null;
this.bindRef = this.bindRef.bind(this);
this.onChangeText = this.onChangeText.bind(this);
this.state = {
numberOfLines: this.getNumberOfLines()
};
}
bindRef(c) {
this._ref = c;
this.props.innerRef && this.props.innerRef(c);
}
getText() {
return typeof this.props.value === 'string' ?
this.props.value :
(
typeof this.props.defaultValue === 'string' ?
this.props.defaultValue :
''
);
}
getNumberOfLines(value) {
if (value === undefined) {
value = this.getText();
}
return Math.max(this.props.numberOfLines, value.split('\n').length - 1) + 1;
}
onChangeText(value) {
this.setState({numberOfLines: this.getNumberOfLines(value)})
}
render() {
return (
<TextInput
{...this.props}
ref={this.bindRef}
numberOfLines={this.state.numberOfLines}
onChangeText={this.onChangeText}
/>
)
}
}
TextInputAutogrow.propTypes = {
...TextInput.propTypes,
innerRef: PropTypes.func,
};
TextInputAutogrow.defaultProps = {
numberOfLines: 4,
};
@Groovietunes 几乎全部正确,但截至 26 年 6 月 19 日,事情与他最初的回答有所不同。
首先,textinputs 上的 onChange
道具不再 return 是 contentSize
对象,因此 event.nativeEvent.contentSize
将 return undefined
.
解决这个问题的方法是使用 onContentSizeChange
道具。但是有一个陷阱; onContentSizeChange
仅在组件安装时运行一次,因此无法在组件变化时动态获取高度。
为了解决这个问题,我们需要确保 value
道具设置在道具层次结构中的 onContentSizeChange
之后,以便随着 textinput
的增长继续获取内容大小。
最后我得到了一个看起来像这样的组件
<TextInput
placeholder={this.props.placeholder}
autoFocus={this.props.autoFocus}
style={[
styles.inputFlat,
{ height: this.state.height > 40 ? this.state.height : 40 }
]}
ref="inputFlat"
onFocus={() => {
this.toggleInput();
this.toggleButton();
}}
multiline={this.props.multiline}
onBlur={() => {
this.toggleInput();
this.toggleButton();
}}
onChangeText={this.props.onChangeText}
onContentSizeChange={event => {
const { contentSize } = event.nativeEvent;
this.setState({
height: contentSize.height
});
}}
value={this.state.value}
/>
如果由于某种原因它在查看此答案时似乎已损坏,请参阅 this 文档以获取任何更新
任何人都想要一个简单的解决方案,使用新版本的 RN,目前使用 0.63.4, 如果它是多行的,团队将 autoGrow 添加到 TextInput,就是这种情况。
不要对组件或父组件使用 高度 (StyeSheet 属性) View/Views
改用minHeight、maxHeight,如果启用了多行,TextInput 将增长直到达到maxHeight ,然后将是可滚动的。
不需要维护任何计算和内部状态。
同样适用于 minWidth、maxWidth 如果您需要扩展得更宽。