From 6f7632c5d24935417bc4d61d68335c867c308761 Mon Sep 17 00:00:00 2001 From: Brent Vatne Date: Thu, 23 Apr 2015 18:22:23 -0700 Subject: [PATCH 1/9] Add support for multiline TextInput via UITextView --- React/React.xcodeproj/project.pbxproj | 12 +++ React/Views/RCTTextView.h | 21 +++++ React/Views/RCTTextView.m | 117 ++++++++++++++++++++++++++ React/Views/RCTTextViewManager.h | 15 ++++ React/Views/RCTTextViewManager.m | 63 ++++++++++++++ 5 files changed, 228 insertions(+) create mode 100644 React/Views/RCTTextView.h create mode 100644 React/Views/RCTTextView.m create mode 100644 React/Views/RCTTextViewManager.h create mode 100644 React/Views/RCTTextViewManager.m diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index 3364cce76fbfe8..abb3f1b9ebb1c7 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -64,6 +64,8 @@ 83CBBA691A601EF300E9B192 /* RCTEventDispatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA661A601EF300E9B192 /* RCTEventDispatcher.m */; }; 83CBBA981A6020BB00E9B192 /* RCTTouchHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA971A6020BB00E9B192 /* RCTTouchHandler.m */; }; 83CBBACC1A6023D300E9B192 /* RCTConvert.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBACB1A6023D300E9B192 /* RCTConvert.m */; }; + BBE9C5C21AE9D24200D3069B /* RCTTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = BBE9C5BF1AE9D24200D3069B /* RCTTextView.m */; }; + BBE9C5C31AE9D24200D3069B /* RCTTextViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = BBE9C5C11AE9D24200D3069B /* RCTTextViewManager.m */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -206,6 +208,10 @@ 83CBBA971A6020BB00E9B192 /* RCTTouchHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTouchHandler.m; sourceTree = ""; }; 83CBBACA1A6023D300E9B192 /* RCTConvert.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTConvert.h; sourceTree = ""; }; 83CBBACB1A6023D300E9B192 /* RCTConvert.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTConvert.m; sourceTree = ""; }; + BBE9C5BE1AE9D24200D3069B /* RCTTextView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTextView.h; sourceTree = ""; }; + BBE9C5BF1AE9D24200D3069B /* RCTTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTextView.m; sourceTree = ""; }; + BBE9C5C01AE9D24200D3069B /* RCTTextViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTextViewManager.h; sourceTree = ""; }; + BBE9C5C11AE9D24200D3069B /* RCTTextViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTextViewManager.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -317,6 +323,10 @@ 13B080151A69489C00A75B9A /* RCTTextField.m */, 13B080161A69489C00A75B9A /* RCTTextFieldManager.h */, 13B080171A69489C00A75B9A /* RCTTextFieldManager.m */, + BBE9C5BE1AE9D24200D3069B /* RCTTextView.h */, + BBE9C5BF1AE9D24200D3069B /* RCTTextView.m */, + BBE9C5C01AE9D24200D3069B /* RCTTextViewManager.h */, + BBE9C5C11AE9D24200D3069B /* RCTTextViewManager.m */, 13B080181A69489C00A75B9A /* RCTUIActivityIndicatorViewManager.h */, 13B080191A69489C00A75B9A /* RCTUIActivityIndicatorViewManager.m */, 13E0674F1A70F44B002CDEE1 /* RCTView.h */, @@ -484,6 +494,7 @@ buildActionMask = 2147483647; files = ( 13456E961ADAD482009F94A7 /* RCTConvert+MapKit.m in Sources */, + BBE9C5C21AE9D24200D3069B /* RCTTextView.m in Sources */, 13723B501A82FD3C00F88898 /* RCTStatusBarManager.m in Sources */, 000E6CEB1AB0E980000CDF4D /* RCTSourceCode.m in Sources */, 13B0801E1A69489C00A75B9A /* RCTTextField.m in Sources */, @@ -530,6 +541,7 @@ 137327E81AA5CF210034F82E /* RCTTabBarItem.m in Sources */, 13E067551A70F44B002CDEE1 /* RCTShadowView.m in Sources */, 58114A171AAE854800E7D092 /* RCTPickerManager.m in Sources */, + BBE9C5C31AE9D24200D3069B /* RCTTextViewManager.m in Sources */, 13B0801A1A69489C00A75B9A /* RCTNavigator.m in Sources */, 830BA4551A8E3BDA00D53203 /* RCTCache.m in Sources */, 137327E71AA5CF210034F82E /* RCTTabBar.m in Sources */, diff --git a/React/Views/RCTTextView.h b/React/Views/RCTTextView.h new file mode 100644 index 00000000000000..5295f5f7238f68 --- /dev/null +++ b/React/Views/RCTTextView.h @@ -0,0 +1,21 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +@class RCTEventDispatcher; + +@interface RCTTextView : UITextView + +@property (nonatomic, assign) BOOL autoCorrect; +@property (nonatomic, assign) UIEdgeInsets contentInset; + +- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER; + +@end diff --git a/React/Views/RCTTextView.m b/React/Views/RCTTextView.m new file mode 100644 index 00000000000000..d08123b6ca5cf1 --- /dev/null +++ b/React/Views/RCTTextView.m @@ -0,0 +1,117 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTTextView.h" + +#import "RCTConvert.h" +#import "RCTEventDispatcher.h" +#import "RCTUtils.h" +#import "UIView+React.h" + +@implementation RCTTextView +{ + RCTEventDispatcher *_eventDispatcher; + BOOL _jsRequestingFirstResponder; +} + +- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher +{ + if ((self = [super initWithFrame:CGRectZero])) { + + _eventDispatcher = eventDispatcher; + + [self.notificationCenter addObserver:self + selector:@selector(_textViewBeginEditing) + name:UITextViewTextDidBeginEditingNotification + object:self]; + + [self.notificationCenter addObserver:self + selector:@selector(_textViewDidChange) + name:UITextViewTextDidChangeNotification + object:self]; + + [self.notificationCenter addObserver:self + selector:@selector(_textViewEndEditing) + name:UITextViewTextDidEndEditingNotification + object:self]; + } + return self; +} + +- (void)setText:(NSString *)text +{ + if (![text isEqualToString:self.text]) { + [super setText:text]; + } +} + +- (void)setAutoCorrect:(BOOL)autoCorrect +{ + self.autocorrectionType = (autoCorrect ? UITextAutocorrectionTypeYes : UITextAutocorrectionTypeNo); +} + +- (BOOL)autoCorrect +{ + return self.autocorrectionType == UITextAutocorrectionTypeYes; +} + +#define RCT_TEXT_EVENT_HANDLER(delegateMethod, eventName) \ +- (void)delegateMethod \ +{ \ + [_eventDispatcher sendTextEventWithType:eventName \ + reactTag:self.reactTag \ + text:self.text]; \ +} + +RCT_TEXT_EVENT_HANDLER(_textViewDidChange, RCTTextEventTypeChange) +RCT_TEXT_EVENT_HANDLER(_textViewEndEditing, RCTTextEventTypeEnd) + +- (void)_textViewBeginEditing +{ + [_eventDispatcher sendTextEventWithType:RCTTextEventTypeFocus + reactTag:self.reactTag + text:self.text]; +} + +- (BOOL)becomeFirstResponder +{ + _jsRequestingFirstResponder = YES; + BOOL result = [super becomeFirstResponder]; + _jsRequestingFirstResponder = NO; + return result; +} + +- (BOOL)resignFirstResponder +{ + BOOL result = [super resignFirstResponder]; + if (result) + { + [_eventDispatcher sendTextEventWithType:RCTTextEventTypeBlur + reactTag:self.reactTag + text:self.text]; + } + return result; +} + +- (BOOL)canBecomeFirstResponder +{ + return _jsRequestingFirstResponder; +} + +- (NSNotificationCenter *)notificationCenter +{ + return [NSNotificationCenter defaultCenter]; +} + +- (void)dealloc +{ + [self.notificationCenter removeObserver:self]; +} + +@end diff --git a/React/Views/RCTTextViewManager.h b/React/Views/RCTTextViewManager.h new file mode 100644 index 00000000000000..f35992443f1e8c --- /dev/null +++ b/React/Views/RCTTextViewManager.h @@ -0,0 +1,15 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTViewManager.h" + +@interface RCTTextViewManager : RCTViewManager + +@end + diff --git a/React/Views/RCTTextViewManager.m b/React/Views/RCTTextViewManager.m new file mode 100644 index 00000000000000..8db042e07a441c --- /dev/null +++ b/React/Views/RCTTextViewManager.m @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTTextViewManager.h" + +#import "RCTBridge.h" +#import "RCTConvert.h" +#import "RCTShadowView.h" +#import "RCTSparseArray.h" +#import "RCTTextView.h" + +@implementation RCTTextViewManager + +RCT_EXPORT_MODULE() + +- (UIView *)view +{ + return [[RCTTextView alloc] initWithEventDispatcher:self.bridge.eventDispatcher]; +} + +RCT_EXPORT_VIEW_PROPERTY(autoCorrect, BOOL) +RCT_EXPORT_VIEW_PROPERTY(placeholder, NSString) +RCT_EXPORT_VIEW_PROPERTY(text, NSString) +RCT_REMAP_VIEW_PROPERTY(clearTextOnFocus, clearsOnBeginEditing, BOOL) +RCT_EXPORT_VIEW_PROPERTY(keyboardType, UIKeyboardType) +RCT_EXPORT_VIEW_PROPERTY(returnKeyType, UIReturnKeyType) +RCT_EXPORT_VIEW_PROPERTY(enablesReturnKeyAutomatically, BOOL) +RCT_EXPORT_VIEW_PROPERTY(secureTextEntry, BOOL) +RCT_REMAP_VIEW_PROPERTY(color, textColor, UIColor) +RCT_REMAP_VIEW_PROPERTY(autoCapitalize, autocapitalizationType, UITextAutocapitalizationType) +RCT_CUSTOM_VIEW_PROPERTY(fontSize, CGFloat, RCTTextView) +{ + view.font = [RCTConvert UIFont:view.font withSize:json ?: @(defaultView.font.pointSize)]; +} +RCT_CUSTOM_VIEW_PROPERTY(fontWeight, NSString, RCTTextView) +{ + view.font = [RCTConvert UIFont:view.font withWeight:json]; // defaults to normal +} +RCT_CUSTOM_VIEW_PROPERTY(fontStyle, NSString, RCTTextView) +{ + view.font = [RCTConvert UIFont:view.font withStyle:json]; // defaults to normal +} +RCT_CUSTOM_VIEW_PROPERTY(fontFamily, NSString, RCTTextView) +{ + view.font = [RCTConvert UIFont:view.font withFamily:json ?: defaultView.font.familyName]; +} + +- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(RCTShadowView *)shadowView +{ + NSNumber *reactTag = shadowView.reactTag; + UIEdgeInsets padding = shadowView.paddingAsInsets; + return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { + ((RCTTextView *)viewRegistry[reactTag]).contentInset = padding; + }; +} + +@end From 0216bf6044c51d5fce4cbfa9bd3949a1c5e8cf9f Mon Sep 17 00:00:00 2001 From: Brent Vatne Date: Mon, 27 Apr 2015 19:53:16 -0700 Subject: [PATCH 2/9] Various fixes for RCTTextView - Change to put UITextView inside of RCTView to be able to set padding - Fix issues with setting fonts - RCTConvert would lose properties along the way. --- .../Text/RCTText.xcodeproj/project.pbxproj | 12 + {React/Views => Libraries/Text}/RCTTextView.h | 8 +- Libraries/Text/RCTTextView.m | 224 ++++++++++++++++++ .../Text}/RCTTextViewManager.h | 0 .../Text}/RCTTextViewManager.m | 3 +- React/Base/RCTConvert.m | 71 +++--- React/React.xcodeproj/project.pbxproj | 12 - React/Views/RCTTextView.m | 117 --------- 8 files changed, 288 insertions(+), 159 deletions(-) rename {React/Views => Libraries/Text}/RCTTextView.h (69%) create mode 100644 Libraries/Text/RCTTextView.m rename {React/Views => Libraries/Text}/RCTTextViewManager.h (100%) rename {React/Views => Libraries/Text}/RCTTextViewManager.m (97%) delete mode 100644 React/Views/RCTTextView.m diff --git a/Libraries/Text/RCTText.xcodeproj/project.pbxproj b/Libraries/Text/RCTText.xcodeproj/project.pbxproj index 3c4bcf5bae846f..e3924e4ce0acff 100644 --- a/Libraries/Text/RCTText.xcodeproj/project.pbxproj +++ b/Libraries/Text/RCTText.xcodeproj/project.pbxproj @@ -12,6 +12,8 @@ 58B511D01A9E6C5C00147676 /* RCTShadowText.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B511CB1A9E6C5C00147676 /* RCTShadowText.m */; }; 58B511D11A9E6C5C00147676 /* RCTTextManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B511CD1A9E6C5C00147676 /* RCTTextManager.m */; }; 58B512161A9E6EFF00147676 /* RCTText.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B512141A9E6EFF00147676 /* RCTText.m */; }; + BBD12D921AEF2D7900F7DEDD /* RCTTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = BBD12D8F1AEF2D7900F7DEDD /* RCTTextView.m */; }; + BBD12D931AEF2D7900F7DEDD /* RCTTextViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = BBD12D911AEF2D7900F7DEDD /* RCTTextViewManager.m */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -38,6 +40,10 @@ 58B511CD1A9E6C5C00147676 /* RCTTextManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTextManager.m; sourceTree = ""; }; 58B512141A9E6EFF00147676 /* RCTText.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTText.m; sourceTree = ""; }; 58B512151A9E6EFF00147676 /* RCTText.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTText.h; sourceTree = ""; }; + BBD12D8E1AEF2D7900F7DEDD /* RCTTextView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTextView.h; sourceTree = ""; }; + BBD12D8F1AEF2D7900F7DEDD /* RCTTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTextView.m; sourceTree = ""; }; + BBD12D901AEF2D7900F7DEDD /* RCTTextViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTextViewManager.h; sourceTree = ""; }; + BBD12D911AEF2D7900F7DEDD /* RCTTextViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTextViewManager.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -64,6 +70,10 @@ 58B512141A9E6EFF00147676 /* RCTText.m */, 58B511CC1A9E6C5C00147676 /* RCTTextManager.h */, 58B511CD1A9E6C5C00147676 /* RCTTextManager.m */, + BBD12D8E1AEF2D7900F7DEDD /* RCTTextView.h */, + BBD12D8F1AEF2D7900F7DEDD /* RCTTextView.m */, + BBD12D901AEF2D7900F7DEDD /* RCTTextViewManager.h */, + BBD12D911AEF2D7900F7DEDD /* RCTTextViewManager.m */, 58B5119C1A9E6C1200147676 /* Products */, ); indentWidth = 2; @@ -135,8 +145,10 @@ buildActionMask = 2147483647; files = ( 58B511D11A9E6C5C00147676 /* RCTTextManager.m in Sources */, + BBD12D921AEF2D7900F7DEDD /* RCTTextView.m in Sources */, 58B511CE1A9E6C5C00147676 /* RCTRawTextManager.m in Sources */, 58B512161A9E6EFF00147676 /* RCTText.m in Sources */, + BBD12D931AEF2D7900F7DEDD /* RCTTextViewManager.m in Sources */, 58B511CF1A9E6C5C00147676 /* RCTShadowRawText.m in Sources */, 58B511D01A9E6C5C00147676 /* RCTShadowText.m in Sources */, ); diff --git a/React/Views/RCTTextView.h b/Libraries/Text/RCTTextView.h similarity index 69% rename from React/Views/RCTTextView.h rename to Libraries/Text/RCTTextView.h index 5295f5f7238f68..47e20aed4b042c 100644 --- a/React/Views/RCTTextView.h +++ b/Libraries/Text/RCTTextView.h @@ -8,14 +8,20 @@ */ #import +#import "RCTView.h" +#import "UIView+React.h" @class RCTEventDispatcher; -@interface RCTTextView : UITextView +@interface RCTTextView : RCTView @property (nonatomic, assign) BOOL autoCorrect; @property (nonatomic, assign) UIEdgeInsets contentInset; +@property (nonatomic, assign) BOOL automaticallyAdjustContentInsets; +@property (nonatomic, strong) UIColor *placeholderTextColor; +@property (nonatomic, assign) UIFont *font; - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER; + @end diff --git a/Libraries/Text/RCTTextView.m b/Libraries/Text/RCTTextView.m new file mode 100644 index 00000000000000..154bced36f7370 --- /dev/null +++ b/Libraries/Text/RCTTextView.m @@ -0,0 +1,224 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTTextView.h" + +#import "RCTConvert.h" +#import "RCTEventDispatcher.h" +#import "RCTUtils.h" +#import "UIView+React.h" + +@implementation RCTTextView +{ + RCTEventDispatcher *_eventDispatcher; + BOOL _jsRequestingFirstResponder; + NSString *_placeholder; + UILabel *_placeholderView; + UITextView *_textView; +} + +- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher +{ + if ((self = [super initWithFrame:CGRectZero])) { + _contentInset = UIEdgeInsetsZero; + _eventDispatcher = eventDispatcher; + _placeholderTextColor = [self defaultPlaceholderTextColor]; + + _textView = [[UITextView alloc] initWithFrame:self.bounds]; + _textView.backgroundColor = [UIColor clearColor]; + [self updateBounds]; + [self addSubview:_textView]; + [self subscribeToTextViewChanges]; + } + + return self; +} + +- (void)subscribeToTextViewChanges +{ + [self.notificationCenter addObserver:self + selector:@selector(_textViewBeginEditing) + name:UITextViewTextDidBeginEditingNotification + object:_textView]; + + [self.notificationCenter addObserver:self + selector:@selector(_textViewDidChange) + name:UITextViewTextDidChangeNotification + object:_textView]; + + [self.notificationCenter addObserver:self + selector:@selector(_textViewEndEditing) + name:UITextViewTextDidEndEditingNotification + object:_textView]; +} + +- (void)updateBounds +{ + // Add padding to top and left of placeholder label to make it match up with UITextView + UIEdgeInsets placeholderInset = UIEdgeInsetsMake(_contentInset.top + 7, _contentInset.left + 3, _contentInset.bottom, _contentInset.right); + [_textView setFrame:UIEdgeInsetsInsetRect(self.bounds, _contentInset)]; + [_placeholderView setFrame:UIEdgeInsetsInsetRect(self.bounds, placeholderInset)]; + // Bump the placeholder view up to the top + [_placeholderView sizeToFit]; +} + +- (void)setFont:(UIFont *)font +{ + _font = font; + _textView.font = _font; + [self _setupPlaceholder]; +} + +- (void)setTextColor:(UIColor *)textColor +{ + _textView.textColor = textColor; +} + +- (void)setPlaceholder:(NSString *)placeholder +{ + _placeholder = placeholder; + [self _setupPlaceholder]; +} + +- (void)setPlaceholderTextColor:(UIColor *)placeholderTextColor +{ + if (placeholderTextColor) { + _placeholderTextColor = placeholderTextColor; + } else { + _placeholderTextColor = [self defaultPlaceholderTextColor]; + } + + [self _setupPlaceholder]; +} + +- (void)_setupPlaceholder +{ + [_placeholderView removeFromSuperview]; + _placeholderView = nil; + + if (_placeholder) { + _placeholderView = [[UILabel alloc] initWithFrame:self.bounds]; + _placeholderView.backgroundColor = [UIColor clearColor]; + _placeholderView.attributedText = [[NSAttributedString alloc] initWithString:_placeholder + attributes:@{ NSFontAttributeName : (_textView.font ? _textView.font : [self defaultPlaceholderFont]), + NSForegroundColorAttributeName : _placeholderTextColor }]; + [self insertSubview:_placeholderView belowSubview:_textView]; + [self _setPlaceholderVisibility]; + } +} + +- (void)setContentInset:(UIEdgeInsets)contentInset +{ + _contentInset = contentInset; + [self updateBounds]; +} + +- (void)setText:(NSString *)text +{ + if (![text isEqualToString:_textView.text]) { + [_textView setText:text]; + [self _setPlaceholderVisibility]; + } +} + +- (void)_setPlaceholderVisibility +{ + if (_textView.text.length > 0) { + [_placeholderView setHidden:YES]; + } else { + [_placeholderView setHidden:NO]; + } +} + +- (void)setAutoCorrect:(BOOL)autoCorrect +{ + _textView.autocorrectionType = (autoCorrect ? UITextAutocorrectionTypeYes : UITextAutocorrectionTypeNo); +} + +- (BOOL)autoCorrect +{ + return _textView.autocorrectionType == UITextAutocorrectionTypeYes; +} + +- (void)_textViewDidChange +{ + [self _setPlaceholderVisibility]; + [_eventDispatcher sendTextEventWithType:RCTTextEventTypeChange + reactTag:self.reactTag + text:_textView.text]; + +} + +- (void)_textViewEndEditing +{ + [_eventDispatcher sendTextEventWithType:RCTTextEventTypeEnd + reactTag:self.reactTag + text:_textView.text]; +} + +- (void)_textViewBeginEditing +{ + [_eventDispatcher sendTextEventWithType:RCTTextEventTypeFocus + reactTag:self.reactTag + text:_textView.text]; +} + +- (BOOL)becomeFirstResponder +{ + _jsRequestingFirstResponder = YES; + BOOL result = [super becomeFirstResponder]; + _jsRequestingFirstResponder = NO; + return result; +} + +- (BOOL)resignFirstResponder +{ + BOOL result = [super resignFirstResponder]; + if (result) { + [_eventDispatcher sendTextEventWithType:RCTTextEventTypeBlur + reactTag:self.reactTag + text:_textView.text]; + } + + return result; +} + +- (void)layoutSubviews +{ + [super layoutSubviews]; + [self updateBounds]; +} + + +- (BOOL)canBecomeFirstResponder +{ + return _jsRequestingFirstResponder; +} + +- (NSNotificationCenter *)notificationCenter +{ + return [NSNotificationCenter defaultCenter]; +} + +- (UIFont*)defaultPlaceholderFont +{ + return [UIFont fontWithName:@"Helvetica" size:17]; +} + +- (UIColor*)defaultPlaceholderTextColor +{ + return [UIColor colorWithRed:0.0/255.0 green:0.0/255.0 blue:0.098/255.0 alpha:0.22]; +} + +- (void)dealloc +{ + [self.notificationCenter removeObserver:self]; +} + +@end diff --git a/React/Views/RCTTextViewManager.h b/Libraries/Text/RCTTextViewManager.h similarity index 100% rename from React/Views/RCTTextViewManager.h rename to Libraries/Text/RCTTextViewManager.h diff --git a/React/Views/RCTTextViewManager.m b/Libraries/Text/RCTTextViewManager.m similarity index 97% rename from React/Views/RCTTextViewManager.m rename to Libraries/Text/RCTTextViewManager.m index 8db042e07a441c..80e80c656e5172 100644 --- a/React/Views/RCTTextViewManager.m +++ b/Libraries/Text/RCTTextViewManager.m @@ -11,9 +11,9 @@ #import "RCTBridge.h" #import "RCTConvert.h" +#import "RCTTextView.h" #import "RCTShadowView.h" #import "RCTSparseArray.h" -#import "RCTTextView.h" @implementation RCTTextViewManager @@ -26,6 +26,7 @@ - (UIView *)view RCT_EXPORT_VIEW_PROPERTY(autoCorrect, BOOL) RCT_EXPORT_VIEW_PROPERTY(placeholder, NSString) +RCT_EXPORT_VIEW_PROPERTY(placeholderTextColor, UIColor) RCT_EXPORT_VIEW_PROPERTY(text, NSString) RCT_REMAP_VIEW_PROPERTY(clearTextOnFocus, clearsOnBeginEditing, BOOL) RCT_EXPORT_VIEW_PROPERTY(keyboardType, UIKeyboardType) diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index f1ed77298dafdb..ef2ed11676dd87 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -685,11 +685,6 @@ + (UIFont *)UIFont:(UIFont *)font withStyle:(id)json return [self UIFont:font withFamily:nil size:nil weight:nil style:json]; } -+ (UIFont *)UIFont:(UIFont *)font withFamily:(id)json -{ - return [self UIFont:font withFamily:json size:nil weight:nil style:nil]; -} - + (UIFont *)UIFont:(UIFont *)font withFamily:(id)family size:(id)size weight:(id)weight style:(id)style { @@ -698,51 +693,71 @@ + (UIFont *)UIFont:(UIFont *)font withFamily:(id)family const RCTFontWeight RCTDefaultFontWeight = UIFontWeightRegular; const CGFloat RCTDefaultFontSize = 14; - // Get existing properties + // Initialize properties to defaults + CGFloat fontSize = RCTDefaultFontSize; + RCTFontWeight fontWeight = RCTDefaultFontWeight; + NSString *fontFamily = RCTDefaultFontFamily; BOOL isItalic = NO; BOOL isCondensed = NO; - RCTFontWeight fontWeight = RCTDefaultFontWeight; + + // Get existing properties from given font if (font) { - family = font.familyName; + fontFamily = font.familyName ?: RCTDefaultFontFamily; + fontSize = font.pointSize ?: RCTDefaultFontSize; fontWeight = RCTWeightOfFont(font); isItalic = RCTFontIsItalic(font); isCondensed = RCTFontIsCondensed(font); } - // Get font style - if (style) { - isItalic = [self RCTFontStyle:style]; + // Get font family + if (family) { + fontFamily = [self NSString:family]; } - // Get font size - CGFloat fontSize = [self CGFloat:size] ?: RCTDefaultFontSize; - - // Get font family - NSString *familyName = [self NSString:family] ?: RCTDefaultFontFamily; - if ([UIFont fontNamesForFamilyName:familyName].count == 0) { - font = [UIFont fontWithName:familyName size:fontSize]; + // Gracefully handle being given a font name rather than font family, for + // example: "Helvetica Light Oblique" rather than just "Helvetica". + if ([UIFont fontNamesForFamilyName:fontFamily].count == 0) { + font = [UIFont fontWithName:fontFamily size:fontSize]; if (font) { // It's actually a font name, not a font family name, // but we'll do what was meant, not what was said. - familyName = font.familyName; - NSDictionary *traits = [font.fontDescriptor objectForKey:UIFontDescriptorTraitsAttribute]; - fontWeight = [traits[UIFontWeightTrait] doubleValue]; + fontFamily = font.familyName; + fontWeight = RCTWeightOfFont(font); + isItalic = RCTFontIsItalic(font); + isCondensed = RCTFontIsCondensed(font); } else { // Not a valid font or family - RCTLogError(@"Unrecognized font family '%@'", familyName); - familyName = RCTDefaultFontFamily; + RCTLogError(@"Unrecognized font family '%@'", fontFamily); + fontFamily = RCTDefaultFontFamily; } } + // Get font style + if (style) { + isItalic = [self RCTFontStyle:style]; + } + + // Get font size + if (size) { + fontSize = [self CGFloat:size]; + } + // Get font weight if (weight) { fontWeight = [self RCTFontWeight:weight]; } - // Get closest match - UIFont *bestMatch = font; - CGFloat closestWeight = font ? RCTWeightOfFont(font) : INFINITY; - for (NSString *name in [UIFont fontNamesForFamilyName:familyName]) { + // Get the closest font that matches the given weight for the fontFamily + UIFont *bestMatch = [UIFont fontWithName:font.fontName size: fontSize]; + CGFloat closestWeight; + + if (font && [font.familyName isEqualToString: fontFamily]) { + closestWeight = RCTWeightOfFont(font); + } else { + closestWeight = INFINITY; + } + + for (NSString *name in [UIFont fontNamesForFamilyName:fontFamily]) { UIFont *match = [UIFont fontWithName:name size:fontSize]; if (isItalic == RCTFontIsItalic(match) && isCondensed == RCTFontIsCondensed(match)) { @@ -758,7 +773,7 @@ + (UIFont *)UIFont:(UIFont *)font withFamily:(id)family if (!bestMatch) { RCTLogError(@"Could not find font with family: '%@', size: %@, \ weight: %@, style: %@", family, size, weight, style); - bestMatch = [UIFont fontWithName:[[UIFont fontNamesForFamilyName:familyName] firstObject] + bestMatch = [UIFont fontWithName:[[UIFont fontNamesForFamilyName:fontFamily] firstObject] size:fontSize]; } diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index abb3f1b9ebb1c7..3364cce76fbfe8 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -64,8 +64,6 @@ 83CBBA691A601EF300E9B192 /* RCTEventDispatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA661A601EF300E9B192 /* RCTEventDispatcher.m */; }; 83CBBA981A6020BB00E9B192 /* RCTTouchHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA971A6020BB00E9B192 /* RCTTouchHandler.m */; }; 83CBBACC1A6023D300E9B192 /* RCTConvert.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBACB1A6023D300E9B192 /* RCTConvert.m */; }; - BBE9C5C21AE9D24200D3069B /* RCTTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = BBE9C5BF1AE9D24200D3069B /* RCTTextView.m */; }; - BBE9C5C31AE9D24200D3069B /* RCTTextViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = BBE9C5C11AE9D24200D3069B /* RCTTextViewManager.m */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -208,10 +206,6 @@ 83CBBA971A6020BB00E9B192 /* RCTTouchHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTouchHandler.m; sourceTree = ""; }; 83CBBACA1A6023D300E9B192 /* RCTConvert.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTConvert.h; sourceTree = ""; }; 83CBBACB1A6023D300E9B192 /* RCTConvert.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTConvert.m; sourceTree = ""; }; - BBE9C5BE1AE9D24200D3069B /* RCTTextView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTextView.h; sourceTree = ""; }; - BBE9C5BF1AE9D24200D3069B /* RCTTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTextView.m; sourceTree = ""; }; - BBE9C5C01AE9D24200D3069B /* RCTTextViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTextViewManager.h; sourceTree = ""; }; - BBE9C5C11AE9D24200D3069B /* RCTTextViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTextViewManager.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -323,10 +317,6 @@ 13B080151A69489C00A75B9A /* RCTTextField.m */, 13B080161A69489C00A75B9A /* RCTTextFieldManager.h */, 13B080171A69489C00A75B9A /* RCTTextFieldManager.m */, - BBE9C5BE1AE9D24200D3069B /* RCTTextView.h */, - BBE9C5BF1AE9D24200D3069B /* RCTTextView.m */, - BBE9C5C01AE9D24200D3069B /* RCTTextViewManager.h */, - BBE9C5C11AE9D24200D3069B /* RCTTextViewManager.m */, 13B080181A69489C00A75B9A /* RCTUIActivityIndicatorViewManager.h */, 13B080191A69489C00A75B9A /* RCTUIActivityIndicatorViewManager.m */, 13E0674F1A70F44B002CDEE1 /* RCTView.h */, @@ -494,7 +484,6 @@ buildActionMask = 2147483647; files = ( 13456E961ADAD482009F94A7 /* RCTConvert+MapKit.m in Sources */, - BBE9C5C21AE9D24200D3069B /* RCTTextView.m in Sources */, 13723B501A82FD3C00F88898 /* RCTStatusBarManager.m in Sources */, 000E6CEB1AB0E980000CDF4D /* RCTSourceCode.m in Sources */, 13B0801E1A69489C00A75B9A /* RCTTextField.m in Sources */, @@ -541,7 +530,6 @@ 137327E81AA5CF210034F82E /* RCTTabBarItem.m in Sources */, 13E067551A70F44B002CDEE1 /* RCTShadowView.m in Sources */, 58114A171AAE854800E7D092 /* RCTPickerManager.m in Sources */, - BBE9C5C31AE9D24200D3069B /* RCTTextViewManager.m in Sources */, 13B0801A1A69489C00A75B9A /* RCTNavigator.m in Sources */, 830BA4551A8E3BDA00D53203 /* RCTCache.m in Sources */, 137327E71AA5CF210034F82E /* RCTTabBar.m in Sources */, diff --git a/React/Views/RCTTextView.m b/React/Views/RCTTextView.m deleted file mode 100644 index d08123b6ca5cf1..00000000000000 --- a/React/Views/RCTTextView.m +++ /dev/null @@ -1,117 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -#import "RCTTextView.h" - -#import "RCTConvert.h" -#import "RCTEventDispatcher.h" -#import "RCTUtils.h" -#import "UIView+React.h" - -@implementation RCTTextView -{ - RCTEventDispatcher *_eventDispatcher; - BOOL _jsRequestingFirstResponder; -} - -- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher -{ - if ((self = [super initWithFrame:CGRectZero])) { - - _eventDispatcher = eventDispatcher; - - [self.notificationCenter addObserver:self - selector:@selector(_textViewBeginEditing) - name:UITextViewTextDidBeginEditingNotification - object:self]; - - [self.notificationCenter addObserver:self - selector:@selector(_textViewDidChange) - name:UITextViewTextDidChangeNotification - object:self]; - - [self.notificationCenter addObserver:self - selector:@selector(_textViewEndEditing) - name:UITextViewTextDidEndEditingNotification - object:self]; - } - return self; -} - -- (void)setText:(NSString *)text -{ - if (![text isEqualToString:self.text]) { - [super setText:text]; - } -} - -- (void)setAutoCorrect:(BOOL)autoCorrect -{ - self.autocorrectionType = (autoCorrect ? UITextAutocorrectionTypeYes : UITextAutocorrectionTypeNo); -} - -- (BOOL)autoCorrect -{ - return self.autocorrectionType == UITextAutocorrectionTypeYes; -} - -#define RCT_TEXT_EVENT_HANDLER(delegateMethod, eventName) \ -- (void)delegateMethod \ -{ \ - [_eventDispatcher sendTextEventWithType:eventName \ - reactTag:self.reactTag \ - text:self.text]; \ -} - -RCT_TEXT_EVENT_HANDLER(_textViewDidChange, RCTTextEventTypeChange) -RCT_TEXT_EVENT_HANDLER(_textViewEndEditing, RCTTextEventTypeEnd) - -- (void)_textViewBeginEditing -{ - [_eventDispatcher sendTextEventWithType:RCTTextEventTypeFocus - reactTag:self.reactTag - text:self.text]; -} - -- (BOOL)becomeFirstResponder -{ - _jsRequestingFirstResponder = YES; - BOOL result = [super becomeFirstResponder]; - _jsRequestingFirstResponder = NO; - return result; -} - -- (BOOL)resignFirstResponder -{ - BOOL result = [super resignFirstResponder]; - if (result) - { - [_eventDispatcher sendTextEventWithType:RCTTextEventTypeBlur - reactTag:self.reactTag - text:self.text]; - } - return result; -} - -- (BOOL)canBecomeFirstResponder -{ - return _jsRequestingFirstResponder; -} - -- (NSNotificationCenter *)notificationCenter -{ - return [NSNotificationCenter defaultCenter]; -} - -- (void)dealloc -{ - [self.notificationCenter removeObserver:self]; -} - -@end From 7f5159af9b38ff09ff57fd47677a54d7ddbc6272 Mon Sep 17 00:00:00 2001 From: Brent Vatne Date: Mon, 27 Apr 2015 21:42:47 -0700 Subject: [PATCH 3/9] Add back method that was accidentally removed --- React/Base/RCTConvert.m | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index ef2ed11676dd87..7680ee952f38a0 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -685,6 +685,11 @@ + (UIFont *)UIFont:(UIFont *)font withStyle:(id)json return [self UIFont:font withFamily:nil size:nil weight:nil style:json]; } ++ (UIFont *)UIFont:(UIFont *)font withFamily:(id)json +{ + return [self UIFont:font withFamily:json size:nil weight:nil style:nil]; +} + + (UIFont *)UIFont:(UIFont *)font withFamily:(id)family size:(id)size weight:(id)weight style:(id)style { From cdef1c55f6d46992f26bcec4cc389ad36a3de680 Mon Sep 17 00:00:00 2001 From: Brent Vatne Date: Mon, 27 Apr 2015 22:25:45 -0700 Subject: [PATCH 4/9] Add multiline TextInput example and tweak placeholder - Use UIScrollView instead of UILabel for placeholder to ensure that placement of the placeholder text is identical to the underlying text --- Examples/UIExplorer/TextInputExample.js | 23 ++++++++++++++++++++--- Libraries/Text/RCTTextView.m | 25 +++++++++++++------------ 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/Examples/UIExplorer/TextInputExample.js b/Examples/UIExplorer/TextInputExample.js index e0ae1b46517070..0429261441262e 100644 --- a/Examples/UIExplorer/TextInputExample.js +++ b/Examples/UIExplorer/TextInputExample.js @@ -88,9 +88,9 @@ var styles = StyleSheet.create({ height: 26, borderWidth: 0.5, borderColor: '#0f0f0f', - padding: 4, flex: 1, fontSize: 13, + padding: 4, }, multiline: { borderWidth: 0.5, @@ -98,6 +98,7 @@ var styles = StyleSheet.create({ flex: 1, fontSize: 13, height: 50, + padding: 4, }, eventLabel: { margin: 3, @@ -118,7 +119,7 @@ var styles = StyleSheet.create({ }); exports.title = ''; -exports.description = 'Single-line text inputs.'; +exports.description = 'Single and multi-line text inputs.'; exports.examples = [ { title: 'Auto-focus', @@ -313,7 +314,7 @@ exports.examples = [ }, { title: 'Clear and select', - render: function () { + render: function() { return ( @@ -336,4 +337,20 @@ exports.examples = [ ); } }, + { + title: 'Multiline', + render: function() { + return ( + + + + + + ) + } + } ]; diff --git a/Libraries/Text/RCTTextView.m b/Libraries/Text/RCTTextView.m index 154bced36f7370..e35933a1075a9a 100644 --- a/Libraries/Text/RCTTextView.m +++ b/Libraries/Text/RCTTextView.m @@ -19,7 +19,7 @@ @implementation RCTTextView RCTEventDispatcher *_eventDispatcher; BOOL _jsRequestingFirstResponder; NSString *_placeholder; - UILabel *_placeholderView; + UITextView *_placeholderView; UITextView *_textView; } @@ -32,7 +32,6 @@ - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher _textView = [[UITextView alloc] initWithFrame:self.bounds]; _textView.backgroundColor = [UIColor clearColor]; - [self updateBounds]; [self addSubview:_textView]; [self subscribeToTextViewChanges]; } @@ -58,14 +57,15 @@ - (void)subscribeToTextViewChanges object:_textView]; } -- (void)updateBounds +- (void)updateFrames { - // Add padding to top and left of placeholder label to make it match up with UITextView - UIEdgeInsets placeholderInset = UIEdgeInsetsMake(_contentInset.top + 7, _contentInset.left + 3, _contentInset.bottom, _contentInset.right); - [_textView setFrame:UIEdgeInsetsInsetRect(self.bounds, _contentInset)]; - [_placeholderView setFrame:UIEdgeInsetsInsetRect(self.bounds, placeholderInset)]; - // Bump the placeholder view up to the top - [_placeholderView sizeToFit]; + // Adjust the insets so that they are as close as possible to single-line + // RCTTextField defaults + UIEdgeInsets adjustedInset = UIEdgeInsetsMake(_contentInset.top - 5, _contentInset.left - 4, + _contentInset.bottom, _contentInset.right); + + [_textView setFrame:UIEdgeInsetsInsetRect(self.bounds, adjustedInset)]; + [_placeholderView setFrame:UIEdgeInsetsInsetRect(self.bounds, adjustedInset)]; } - (void)setFont:(UIFont *)font @@ -103,8 +103,9 @@ - (void)_setupPlaceholder _placeholderView = nil; if (_placeholder) { - _placeholderView = [[UILabel alloc] initWithFrame:self.bounds]; + _placeholderView = [[UITextView alloc] initWithFrame:self.bounds]; _placeholderView.backgroundColor = [UIColor clearColor]; + _placeholderView.scrollEnabled = false; _placeholderView.attributedText = [[NSAttributedString alloc] initWithString:_placeholder attributes:@{ NSFontAttributeName : (_textView.font ? _textView.font : [self defaultPlaceholderFont]), NSForegroundColorAttributeName : _placeholderTextColor }]; @@ -116,7 +117,7 @@ - (void)_setupPlaceholder - (void)setContentInset:(UIEdgeInsets)contentInset { _contentInset = contentInset; - [self updateBounds]; + [self updateFrames]; } - (void)setText:(NSString *)text @@ -192,7 +193,7 @@ - (BOOL)resignFirstResponder - (void)layoutSubviews { [super layoutSubviews]; - [self updateBounds]; + [self updateFrames]; } From 697939f0598cc6f993bc49b56b06a6cfa9e13ab7 Mon Sep 17 00:00:00 2001 From: Brent Vatne Date: Mon, 27 Apr 2015 22:49:38 -0700 Subject: [PATCH 5/9] Set RCTTextView as UITextView delegate rather than use NSNotificationCenter --- Libraries/Text/RCTTextView.h | 2 +- Libraries/Text/RCTTextView.m | 52 +++++++++--------------------------- 2 files changed, 13 insertions(+), 41 deletions(-) diff --git a/Libraries/Text/RCTTextView.h b/Libraries/Text/RCTTextView.h index 47e20aed4b042c..ccd5a184180cd0 100644 --- a/Libraries/Text/RCTTextView.h +++ b/Libraries/Text/RCTTextView.h @@ -13,7 +13,7 @@ @class RCTEventDispatcher; -@interface RCTTextView : RCTView +@interface RCTTextView : RCTView @property (nonatomic, assign) BOOL autoCorrect; @property (nonatomic, assign) UIEdgeInsets contentInset; diff --git a/Libraries/Text/RCTTextView.m b/Libraries/Text/RCTTextView.m index e35933a1075a9a..86ebe085f7d852 100644 --- a/Libraries/Text/RCTTextView.m +++ b/Libraries/Text/RCTTextView.m @@ -32,31 +32,13 @@ - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher _textView = [[UITextView alloc] initWithFrame:self.bounds]; _textView.backgroundColor = [UIColor clearColor]; + _textView.delegate = self; [self addSubview:_textView]; - [self subscribeToTextViewChanges]; } return self; } -- (void)subscribeToTextViewChanges -{ - [self.notificationCenter addObserver:self - selector:@selector(_textViewBeginEditing) - name:UITextViewTextDidBeginEditingNotification - object:_textView]; - - [self.notificationCenter addObserver:self - selector:@selector(_textViewDidChange) - name:UITextViewTextDidChangeNotification - object:_textView]; - - [self.notificationCenter addObserver:self - selector:@selector(_textViewEndEditing) - name:UITextViewTextDidEndEditingNotification - object:_textView]; -} - - (void)updateFrames { // Adjust the insets so that they are as close as possible to single-line @@ -147,27 +129,27 @@ - (BOOL)autoCorrect return _textView.autocorrectionType == UITextAutocorrectionTypeYes; } -- (void)_textViewDidChange +- (void)textViewDidBeginEditing:(UITextView *)textView { - [self _setPlaceholderVisibility]; - [_eventDispatcher sendTextEventWithType:RCTTextEventTypeChange + [_eventDispatcher sendTextEventWithType:RCTTextEventTypeFocus reactTag:self.reactTag - text:_textView.text]; - + text:textView.text]; } -- (void)_textViewEndEditing +- (void)textViewDidChange:(UITextView *)textView { - [_eventDispatcher sendTextEventWithType:RCTTextEventTypeEnd + [self _setPlaceholderVisibility]; + [_eventDispatcher sendTextEventWithType:RCTTextEventTypeChange reactTag:self.reactTag - text:_textView.text]; + text:textView.text]; + } -- (void)_textViewBeginEditing +- (void)textViewDidEndEditing:(UITextView *)textView { - [_eventDispatcher sendTextEventWithType:RCTTextEventTypeFocus + [_eventDispatcher sendTextEventWithType:RCTTextEventTypeEnd reactTag:self.reactTag - text:_textView.text]; + text:textView.text]; } - (BOOL)becomeFirstResponder @@ -202,11 +184,6 @@ - (BOOL)canBecomeFirstResponder return _jsRequestingFirstResponder; } -- (NSNotificationCenter *)notificationCenter -{ - return [NSNotificationCenter defaultCenter]; -} - - (UIFont*)defaultPlaceholderFont { return [UIFont fontWithName:@"Helvetica" size:17]; @@ -217,9 +194,4 @@ - (UIColor*)defaultPlaceholderTextColor return [UIColor colorWithRed:0.0/255.0 green:0.0/255.0 blue:0.098/255.0 alpha:0.22]; } -- (void)dealloc -{ - [self.notificationCenter removeObserver:self]; -} - @end From bf0589ba55e01c5c5ff7aa84e037e9d669b27adb Mon Sep 17 00:00:00 2001 From: Brent Vatne Date: Tue, 28 Apr 2015 10:22:07 -0700 Subject: [PATCH 6/9] Revert changes to RCTConvert, leave for separate diff --- React/Base/RCTConvert.m | 66 ++++++++++++++--------------------------- 1 file changed, 23 insertions(+), 43 deletions(-) diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index 7680ee952f38a0..f1ed77298dafdb 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -698,71 +698,51 @@ + (UIFont *)UIFont:(UIFont *)font withFamily:(id)family const RCTFontWeight RCTDefaultFontWeight = UIFontWeightRegular; const CGFloat RCTDefaultFontSize = 14; - // Initialize properties to defaults - CGFloat fontSize = RCTDefaultFontSize; - RCTFontWeight fontWeight = RCTDefaultFontWeight; - NSString *fontFamily = RCTDefaultFontFamily; + // Get existing properties BOOL isItalic = NO; BOOL isCondensed = NO; - - // Get existing properties from given font + RCTFontWeight fontWeight = RCTDefaultFontWeight; if (font) { - fontFamily = font.familyName ?: RCTDefaultFontFamily; - fontSize = font.pointSize ?: RCTDefaultFontSize; + family = font.familyName; fontWeight = RCTWeightOfFont(font); isItalic = RCTFontIsItalic(font); isCondensed = RCTFontIsCondensed(font); } - // Get font family - if (family) { - fontFamily = [self NSString:family]; + // Get font style + if (style) { + isItalic = [self RCTFontStyle:style]; } - // Gracefully handle being given a font name rather than font family, for - // example: "Helvetica Light Oblique" rather than just "Helvetica". - if ([UIFont fontNamesForFamilyName:fontFamily].count == 0) { - font = [UIFont fontWithName:fontFamily size:fontSize]; + // Get font size + CGFloat fontSize = [self CGFloat:size] ?: RCTDefaultFontSize; + + // Get font family + NSString *familyName = [self NSString:family] ?: RCTDefaultFontFamily; + if ([UIFont fontNamesForFamilyName:familyName].count == 0) { + font = [UIFont fontWithName:familyName size:fontSize]; if (font) { // It's actually a font name, not a font family name, // but we'll do what was meant, not what was said. - fontFamily = font.familyName; - fontWeight = RCTWeightOfFont(font); - isItalic = RCTFontIsItalic(font); - isCondensed = RCTFontIsCondensed(font); + familyName = font.familyName; + NSDictionary *traits = [font.fontDescriptor objectForKey:UIFontDescriptorTraitsAttribute]; + fontWeight = [traits[UIFontWeightTrait] doubleValue]; } else { // Not a valid font or family - RCTLogError(@"Unrecognized font family '%@'", fontFamily); - fontFamily = RCTDefaultFontFamily; + RCTLogError(@"Unrecognized font family '%@'", familyName); + familyName = RCTDefaultFontFamily; } } - // Get font style - if (style) { - isItalic = [self RCTFontStyle:style]; - } - - // Get font size - if (size) { - fontSize = [self CGFloat:size]; - } - // Get font weight if (weight) { fontWeight = [self RCTFontWeight:weight]; } - // Get the closest font that matches the given weight for the fontFamily - UIFont *bestMatch = [UIFont fontWithName:font.fontName size: fontSize]; - CGFloat closestWeight; - - if (font && [font.familyName isEqualToString: fontFamily]) { - closestWeight = RCTWeightOfFont(font); - } else { - closestWeight = INFINITY; - } - - for (NSString *name in [UIFont fontNamesForFamilyName:fontFamily]) { + // Get closest match + UIFont *bestMatch = font; + CGFloat closestWeight = font ? RCTWeightOfFont(font) : INFINITY; + for (NSString *name in [UIFont fontNamesForFamilyName:familyName]) { UIFont *match = [UIFont fontWithName:name size:fontSize]; if (isItalic == RCTFontIsItalic(match) && isCondensed == RCTFontIsCondensed(match)) { @@ -778,7 +758,7 @@ + (UIFont *)UIFont:(UIFont *)font withFamily:(id)family if (!bestMatch) { RCTLogError(@"Could not find font with family: '%@', size: %@, \ weight: %@, style: %@", family, size, weight, style); - bestMatch = [UIFont fontWithName:[[UIFont fontNamesForFamilyName:fontFamily] firstObject] + bestMatch = [UIFont fontWithName:[[UIFont fontNamesForFamilyName:familyName] firstObject] size:fontSize]; } From 9d115e27095a12d6f9baf6bf5b42c89977f264c2 Mon Sep 17 00:00:00 2001 From: Brent Vatne Date: Tue, 28 Apr 2015 12:05:32 -0700 Subject: [PATCH 7/9] [RCTConvert] Maintain properties of UIFont passed in to UIFont:font --- React/Base/RCTConvert.m | 45 +++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index f1ed77298dafdb..7c0387e88e3930 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -698,35 +698,48 @@ + (UIFont *)UIFont:(UIFont *)font withFamily:(id)family const RCTFontWeight RCTDefaultFontWeight = UIFontWeightRegular; const CGFloat RCTDefaultFontSize = 14; - // Get existing properties + // Initialize properties to defaults + CGFloat fontSize = RCTDefaultFontSize; + RCTFontWeight fontWeight = RCTDefaultFontWeight; + NSString *familyName = RCTDefaultFontFamily; BOOL isItalic = NO; BOOL isCondensed = NO; - RCTFontWeight fontWeight = RCTDefaultFontWeight; + if (font) { - family = font.familyName; + familyName = font.familyName ?: RCTDefaultFontFamily; + fontSize = font.pointSize ?: RCTDefaultFontSize; fontWeight = RCTWeightOfFont(font); isItalic = RCTFontIsItalic(font); isCondensed = RCTFontIsCondensed(font); } + // Get font size + fontSize = [self CGFloat:size] ?: fontSize; + + // Get font family + familyName = [self NSString:family] ?: familyName; + // Get font style if (style) { isItalic = [self RCTFontStyle:style]; } - // Get font size - CGFloat fontSize = [self CGFloat:size] ?: RCTDefaultFontSize; + // Get font weight + if (weight) { + fontWeight = [self RCTFontWeight:weight]; + } - // Get font family - NSString *familyName = [self NSString:family] ?: RCTDefaultFontFamily; + // Gracefully handle being given a font name rather than font family, for + // example: "Helvetica Light Oblique" rather than just "Helvetica". if ([UIFont fontNamesForFamilyName:familyName].count == 0) { font = [UIFont fontWithName:familyName size:fontSize]; if (font) { // It's actually a font name, not a font family name, // but we'll do what was meant, not what was said. familyName = font.familyName; - NSDictionary *traits = [font.fontDescriptor objectForKey:UIFontDescriptorTraitsAttribute]; - fontWeight = [traits[UIFontWeightTrait] doubleValue]; + fontWeight = RCTWeightOfFont(font); + isItalic = RCTFontIsItalic(font); + isCondensed = RCTFontIsCondensed(font); } else { // Not a valid font or family RCTLogError(@"Unrecognized font family '%@'", familyName); @@ -734,14 +747,16 @@ + (UIFont *)UIFont:(UIFont *)font withFamily:(id)family } } - // Get font weight - if (weight) { - fontWeight = [self RCTFontWeight:weight]; + // Get the closest font that matches the given weight for the fontFamily + UIFont *bestMatch = [UIFont fontWithName:font.fontName size: fontSize]; + CGFloat closestWeight; + + if (font && [font.familyName isEqualToString: familyName]) { + closestWeight = RCTWeightOfFont(font); + } else { + closestWeight = INFINITY; } - // Get closest match - UIFont *bestMatch = font; - CGFloat closestWeight = font ? RCTWeightOfFont(font) : INFINITY; for (NSString *name in [UIFont fontNamesForFamilyName:familyName]) { UIFont *match = [UIFont fontWithName:name size:fontSize]; if (isItalic == RCTFontIsItalic(match) && From 1c45fce78155c4543a2247961760ed67788148a4 Mon Sep 17 00:00:00 2001 From: Brent Vatne Date: Tue, 28 Apr 2015 12:10:44 -0700 Subject: [PATCH 8/9] Add example with multiline text and placeholder styled --- Examples/UIExplorer/TextInputExample.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Examples/UIExplorer/TextInputExample.js b/Examples/UIExplorer/TextInputExample.js index 0429261441262e..327b6f9d016092 100644 --- a/Examples/UIExplorer/TextInputExample.js +++ b/Examples/UIExplorer/TextInputExample.js @@ -100,6 +100,12 @@ var styles = StyleSheet.create({ height: 50, padding: 4, }, + multilineWithFontStyles: { + color: 'purple', + fontWeight: 'bold', + fontSize: 18, + fontFamily: 'Cochin', + }, eventLabel: { margin: 3, fontSize: 12, @@ -349,6 +355,14 @@ exports.examples = [ style={styles.multiline} /> + + + ) } From 0a17f610e378e1550ea1a3e4864b121b1d654d7c Mon Sep 17 00:00:00 2001 From: Brent Vatne Date: Tue, 28 Apr 2015 18:40:08 -0700 Subject: [PATCH 9/9] Add support for remaining RCTTextView attributes, include in example --- Examples/UIExplorer/TextInputExample.js | 35 ++++++++++++- Libraries/Components/TextInput/TextInput.js | 6 ++- Libraries/Text/RCTTextView.m | 58 +++++++++++++++++++-- Libraries/Text/RCTTextViewManager.m | 5 +- 4 files changed, 95 insertions(+), 9 deletions(-) diff --git a/Examples/UIExplorer/TextInputExample.js b/Examples/UIExplorer/TextInputExample.js index 327b6f9d016092..21b9606da3033d 100644 --- a/Examples/UIExplorer/TextInputExample.js +++ b/Examples/UIExplorer/TextInputExample.js @@ -105,6 +105,14 @@ var styles = StyleSheet.create({ fontWeight: 'bold', fontSize: 18, fontFamily: 'Cochin', + height: 80, + }, + multilineChild: { + width: 50, + height: 40, + position: 'absolute', + right: 5, + backgroundColor: 'red', }, eventLabel: { margin: 3, @@ -350,19 +358,42 @@ exports.examples = [ + + + + + + + + + ) } diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index dfd3ab1a128469..f93d4819f72509 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -38,6 +38,7 @@ var returnKeyTypeConsts = RCTUIManager.UIReturnKeyType; var RCTTextViewAttributes = merge(ReactIOSViewAttributes.UIView, { autoCorrect: true, autoCapitalize: true, + clearTextOnFocus: true, color: true, editable: true, fontFamily: true, @@ -48,6 +49,7 @@ var RCTTextViewAttributes = merge(ReactIOSViewAttributes.UIView, { returnKeyType: true, enablesReturnKeyAutomatically: true, secureTextEntry: true, + selectTextOnFocus: true, mostRecentEventCounter: true, placeholder: true, placeholderTextColor: true, @@ -58,8 +60,6 @@ var RCTTextFieldAttributes = merge(RCTTextViewAttributes, { caretHidden: true, enabled: true, clearButtonMode: true, - clearTextOnFocus: true, - selectTextOnFocus: true, }); var onlyMultiline = { @@ -498,6 +498,8 @@ var TextInput = React.createClass({ autoCapitalize={autoCapitalize} autoCorrect={this.props.autoCorrect} clearButtonMode={clearButtonMode} + selectTextOnFocus={this.props.selectTextOnFocus} + clearTextOnFocus={this.props.clearTextOnFocus} />; } diff --git a/Libraries/Text/RCTTextView.m b/Libraries/Text/RCTTextView.m index 86ebe085f7d852..31aea6b93c0f2f 100644 --- a/Libraries/Text/RCTTextView.m +++ b/Libraries/Text/RCTTextView.m @@ -18,6 +18,8 @@ @implementation RCTTextView { RCTEventDispatcher *_eventDispatcher; BOOL _jsRequestingFirstResponder; + BOOL _clearTextOnFocus; + BOOL _selectTextOnFocus; NSString *_placeholder; UITextView *_placeholderView; UITextView *_textView; @@ -79,6 +81,11 @@ - (void)setPlaceholderTextColor:(UIColor *)placeholderTextColor [self _setupPlaceholder]; } +- (void)setClearTextOnFocus:(BOOL)clearTextOnFocus +{ + _clearTextOnFocus = clearTextOnFocus; +} + - (void)_setupPlaceholder { [_placeholderView removeFromSuperview]; @@ -124,13 +131,58 @@ - (void)setAutoCorrect:(BOOL)autoCorrect _textView.autocorrectionType = (autoCorrect ? UITextAutocorrectionTypeYes : UITextAutocorrectionTypeNo); } +- (void)setEditable:(BOOL)editable +{ + _textView.editable = editable; +} + - (BOOL)autoCorrect { return _textView.autocorrectionType == UITextAutocorrectionTypeYes; } +- (void)setKeyboardType:(UIKeyboardType)keyboardType +{ + _textView.keyboardType = keyboardType; +} + +- (void)setReturnKeyType:(UIReturnKeyType)returnKeyType +{ + _textView.returnKeyType = returnKeyType; +} + +- (void)setEnablesReturnKeyAutomatically:(BOOL)enablesReturnKeyAutomatically +{ + _textView.enablesReturnKeyAutomatically = enablesReturnKeyAutomatically; +} + +- (void)setAutocapitalizationType:(BOOL)autocapitalizationType +{ + _textView.autocapitalizationType = autocapitalizationType; +} + +- (void)setSelectTextOnFocus:(BOOL)selectTextOnFocus +{ + _selectTextOnFocus = selectTextOnFocus; +} + +- (BOOL)textViewShouldBeginEditing:(UITextView *)textView { + if (_selectTextOnFocus) { + dispatch_async(dispatch_get_main_queue(), ^{ + [textView selectAll:nil]; + }); + } + return YES; +} + - (void)textViewDidBeginEditing:(UITextView *)textView { + if (_clearTextOnFocus) { + [_textView setText:@""]; + _textView.text = @""; + [self _setPlaceholderVisibility]; + } + [_eventDispatcher sendTextEventWithType:RCTTextEventTypeFocus reactTag:self.reactTag text:textView.text]; @@ -155,14 +207,15 @@ - (void)textViewDidEndEditing:(UITextView *)textView - (BOOL)becomeFirstResponder { _jsRequestingFirstResponder = YES; - BOOL result = [super becomeFirstResponder]; + BOOL result = [_textView becomeFirstResponder]; _jsRequestingFirstResponder = NO; return result; } - (BOOL)resignFirstResponder { - BOOL result = [super resignFirstResponder]; + [super resignFirstResponder]; + BOOL result = [_textView resignFirstResponder]; if (result) { [_eventDispatcher sendTextEventWithType:RCTTextEventTypeBlur reactTag:self.reactTag @@ -178,7 +231,6 @@ - (void)layoutSubviews [self updateFrames]; } - - (BOOL)canBecomeFirstResponder { return _jsRequestingFirstResponder; diff --git a/Libraries/Text/RCTTextViewManager.m b/Libraries/Text/RCTTextViewManager.m index 80e80c656e5172..400f10f4bd4706 100644 --- a/Libraries/Text/RCTTextViewManager.m +++ b/Libraries/Text/RCTTextViewManager.m @@ -25,14 +25,15 @@ - (UIView *)view } RCT_EXPORT_VIEW_PROPERTY(autoCorrect, BOOL) +RCT_EXPORT_VIEW_PROPERTY(editable, BOOL) RCT_EXPORT_VIEW_PROPERTY(placeholder, NSString) RCT_EXPORT_VIEW_PROPERTY(placeholderTextColor, UIColor) RCT_EXPORT_VIEW_PROPERTY(text, NSString) -RCT_REMAP_VIEW_PROPERTY(clearTextOnFocus, clearsOnBeginEditing, BOOL) +RCT_EXPORT_VIEW_PROPERTY(clearTextOnFocus, BOOL) +RCT_EXPORT_VIEW_PROPERTY(selectTextOnFocus, BOOL) RCT_EXPORT_VIEW_PROPERTY(keyboardType, UIKeyboardType) RCT_EXPORT_VIEW_PROPERTY(returnKeyType, UIReturnKeyType) RCT_EXPORT_VIEW_PROPERTY(enablesReturnKeyAutomatically, BOOL) -RCT_EXPORT_VIEW_PROPERTY(secureTextEntry, BOOL) RCT_REMAP_VIEW_PROPERTY(color, textColor, UIColor) RCT_REMAP_VIEW_PROPERTY(autoCapitalize, autocapitalizationType, UITextAutocapitalizationType) RCT_CUSTOM_VIEW_PROPERTY(fontSize, CGFloat, RCTTextView)