Skip to content

Instantly share code, notes, and snippets.

@f15gdsy
Last active August 31, 2015 14:49
Show Gist options
  • Save f15gdsy/49ddd29e39963e0cc5f2 to your computer and use it in GitHub Desktop.
Save f15gdsy/49ddd29e39963e0cc5f2 to your computer and use it in GitHub Desktop.
An TextInput component for React Native that auto scales the height itself as the user types.
var React = require('react-native')
var {
TextInput,
StyleSheet
} = React;
var RCTUIManager = require('NativeModules').UIManager;
var findNodeHandle = require('findNodeHandle');
var _ = require('lodash');
var ExpandingTextInput = React.createClass({
propTypes: _.assign(React.TextInput.propTypes, {
minHeight: React.PropTypes.number,
maxHeight: React.PropTypes.number
}),
getDefaultProps: function () {
return {
minHeight: 45,
maxHeight: Infinity,
placeholder: 'Enter Text...',
onChangeText: function () {},
};
},
getInitialState: function() {
return {
height: this.props.minHeight
};
},
onMeasureTextHeight: function(height) {
if (this.isMounted()) {
var minH = this.props.minHeight;
var maxH = this.props.maxHeight;
// it's not clear to me why this is needed but it seems to help
// the height looks good at 50 and Xcode says it's 38
height += 12;
if (height < minH) height = minH;
if (height > maxH) height = maxH;
if (this.state.height !== height) {
this.setState({height: height});
}
}
},
resetHeight: function() {
RCTUIManager.measureTextHeight(
findNodeHandle(this.refs.input.refs.input),
this.onMeasureTextHeight
);
},
onChangeText: function(text) {
this.resetHeight();
this.props.onChangeText(text);
},
render: function() {
var {style, onChangeText, multiline, ...others} = this.props;
return (
<TextInput
ref = 'input'
onChangeText = {this.onChangeText}
multiline = {true}
style={[defaultStyles.input, style, {height: this.state.height}]}
{...others}
/>
);
}
});
var defaultStyles = StyleSheet.create({
input: {
fontSize: 12
}
});
module.exports = ExpandingTextInput;
//
// RCTUIManager+TextView.m
// Tasker
//
// Created by Brian Leonard on 7/31/15.
//
#import <UIKit/UIKit.h>
#import "RCTUIManager.h"
#import "RCTSparseArray.h"
#import "UIView+React.h"
@interface RCTUIManager (TextView)
@end
@implementation RCTUIManager (TextView)
/**
* Returns information about the content inside of a RCTTextView
*/
RCT_EXPORT_METHOD(measureTextHeight:(nonnull NSNumber *)reactTag
callback:(RCTResponseSenderBlock)callback)
{
if (!callback) {
RCTLogError(@"Called measure with no callback");
return;
}
[self addUIBlock:^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
UIView *view = viewRegistry[reactTag];
if (!view) {
RCTLogError(@"measureTextContent cannot find view with tag #%@", reactTag);
return;
}
UIView *rootView = view;
while (rootView && ![rootView isReactRootView]) {
rootView = rootView.superview;
}
UITextView *textView = nil;
NSArray *subviews = [view subviews];
for(UIView *subview in subviews) {
if ([subview isKindOfClass:[UITextView class]]) {
UITextView * test = (UITextView *)subview;
if (![test isHidden]) { // placeholder is hidden
textView = test;
break;
}
}
}
if (!textView) {
RCTLogError(@"measureTextContent cannot find UITextView from tag #%@", reactTag);
return;
}
// http://stackoverflow.com/questions/19046969/uitextview-content-size-different-in-ios7
CGFloat measuredHeight;
if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_6_1)
{
// This is the code for iOS 7. contentSize no longer returns the correct value, so
// we have to calculate it.
//
// This is partly borrowed from HPGrowingTextView, but I've replaced the
// magic fudge factors with the calculated values (having worked out where
// they came from)
CGRect frame = textView.bounds;
// Take account of the padding added around the text.
UIEdgeInsets textContainerInsets = textView.textContainerInset;
UIEdgeInsets contentInsets = textView.contentInset;
CGFloat leftRightPadding = textContainerInsets.left + textContainerInsets.right + textView.textContainer.lineFragmentPadding * 2 + contentInsets.left + contentInsets.right;
CGFloat topBottomPadding = textContainerInsets.top + textContainerInsets.bottom + contentInsets.top + contentInsets.bottom;
frame.size.width -= leftRightPadding;
frame.size.height -= topBottomPadding;
NSString *textToMeasure = textView.text;
if ([textToMeasure hasSuffix:@"\n"])
{
textToMeasure = [NSString stringWithFormat:@"%@-", textView.text];
}
// NSString class method: boundingRectWithSize:options:attributes:context is
// available only on ios7.0 sdk.
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
[paragraphStyle setLineBreakMode:NSLineBreakByWordWrapping];
NSDictionary *attributes = @{ NSFontAttributeName: textView.font, NSParagraphStyleAttributeName : paragraphStyle };
CGRect size = [textToMeasure boundingRectWithSize:CGSizeMake(CGRectGetWidth(frame), MAXFLOAT)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:attributes
context:nil];
measuredHeight = ceilf(CGRectGetHeight(size) + topBottomPadding);
}
else
{
measuredHeight = textView.contentSize.height;
}
callback(@[ @(measuredHeight) ]);
}];
}
@end
@f15gdsy
Copy link
Author

f15gdsy commented Aug 30, 2015

This code is based on this answer, and made modifications so that it's easier to use.
Just treat it as a normal TextInput component with additional props, minHeight, and maxHeight.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment