Last active
August 31, 2015 14:49
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.