Skip to content

Instantly share code, notes, and snippets.

@f15gdsy
Last active August 31, 2015 14:49
Show Gist options
  • Select an option

  • Save f15gdsy/49ddd29e39963e0cc5f2 to your computer and use it in GitHub Desktop.

Select an option

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
Copy Markdown
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