From bc1ea548d0017f131c36a30ce06bf4d512cb2f8c Mon Sep 17 00:00:00 2001 From: Valentin Shergin Date: Mon, 3 Apr 2017 15:11:02 -0700 Subject: [PATCH] Better TextInput: Simplified focus/first-responder management on iOS Summary: Pair `reactWillMakeFirstResponder` and `reactDidMakeFirstResponder` was replaced with just `reactFocus` method which is supposed to incapsulate all "focus" and "focus-later-if-needed" functionality. Reviewed By: mmmulani Differential Revision: D4664626 fbshipit-source-id: 8d3b7935ca26d32ba1d1826a585cce0396fcc885 --- Libraries/Text/RCTTextField.m | 31 +++++-------------------------- Libraries/Text/RCTTextView.m | 34 +++++++++------------------------- Libraries/Text/RCTUITextView.m | 28 ++-------------------------- React/Modules/RCTUIManager.m | 7 ++----- React/Views/UIView+React.h | 9 +++++++-- React/Views/UIView+React.m | 33 +++++++++++++++++++++++++++++++-- 6 files changed, 56 insertions(+), 86 deletions(-) diff --git a/Libraries/Text/RCTTextField.m b/Libraries/Text/RCTTextField.m index 0ea13179bb40d6..a82f55bbae42b6 100644 --- a/Libraries/Text/RCTTextField.m +++ b/Libraries/Text/RCTTextField.m @@ -48,7 +48,6 @@ - (BOOL)textFieldShouldEndEditing:(RCTTextField *)textField { @implementation RCTTextField { RCTEventDispatcher *_eventDispatcher; - BOOL _jsRequestingFirstResponder; NSInteger _nativeEventCount; BOOL _submitted; UITextRange *_previousSelectionRange; @@ -94,16 +93,6 @@ - (void)sendKeyValueForString:(NSString *)string eventCount:_nativeEventCount]; } -- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator -{ - [super didUpdateFocusInContext:context withAnimationCoordinator:coordinator]; - if(context.nextFocusedView == self) { - _jsRequestingFirstResponder = YES; - } else { - _jsRequestingFirstResponder = NO; - } -} - // This method is overridden for `onKeyPress`. The manager // will not send a keyPress for text that was pasted. - (void)paste:(id)sender @@ -285,21 +274,6 @@ - (void)sendSelectionEvent } } -- (BOOL)canBecomeFirstResponder -{ - return _jsRequestingFirstResponder; -} - -- (void)reactWillMakeFirstResponder -{ - _jsRequestingFirstResponder = YES; -} - -- (void)reactDidMakeFirstResponder -{ - _jsRequestingFirstResponder = NO; -} - - (BOOL)resignFirstResponder { BOOL result = [super resignFirstResponder]; @@ -314,6 +288,11 @@ - (BOOL)resignFirstResponder return result; } +- (void)didMoveToWindow +{ + [self reactFocusIfNeeded]; +} + #pragma mark - UITextFieldDelegate (Proxied) - (BOOL)shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string diff --git a/Libraries/Text/RCTTextView.m b/Libraries/Text/RCTTextView.m index dc0d289fc22205..f4329d0ef0a2bc 100644 --- a/Libraries/Text/RCTTextView.m +++ b/Libraries/Text/RCTTextView.m @@ -332,7 +332,7 @@ - (BOOL)textView:(RCTUITextView *)textView shouldChangeTextInRange:(NSRange)rang text:self.text key:nil eventCount:_nativeEventCount]; - [self resignFirstResponder]; + [_textView resignFirstResponder]; return NO; } } @@ -541,40 +541,24 @@ - (void)textViewDidEndEditing:(UITextView *)textView eventCount:_nativeEventCount]; } -#pragma mark - UIResponder +#pragma mark - Focus control deledation -- (BOOL)isFirstResponder +- (void)reactFocus { - return [_textView isFirstResponder]; + [_textView reactFocus]; } -- (BOOL)canBecomeFirstResponder +- (void)reactBlur { - return [_textView canBecomeFirstResponder]; + [_textView reactBlur]; } -- (void)reactWillMakeFirstResponder +- (void)didMoveToWindow { - [_textView reactWillMakeFirstResponder]; + [_textView reactFocusIfNeeded]; } -- (BOOL)becomeFirstResponder -{ - return [_textView becomeFirstResponder]; -} - -- (void)reactDidMakeFirstResponder -{ - [_textView reactDidMakeFirstResponder]; -} - -- (BOOL)resignFirstResponder -{ - [super resignFirstResponder]; - return [_textView resignFirstResponder]; -} - -#pragma mark - Content Size +#pragma mark - Content size - (CGSize)contentSize { diff --git a/Libraries/Text/RCTUITextView.m b/Libraries/Text/RCTUITextView.m index 67ae7e5798ba2f..8288ea71fc8963 100644 --- a/Libraries/Text/RCTUITextView.m +++ b/Libraries/Text/RCTUITextView.m @@ -9,9 +9,10 @@ #import "RCTUITextView.h" +#import + @implementation RCTUITextView { - BOOL _jsRequestingFirstResponder; UILabel *_placeholderView; UITextView *_detachedTextView; } @@ -69,31 +70,6 @@ - (void)textDidChange [self invalidatePlaceholderVisibility]; } -#pragma mark - UIResponder - -- (void)reactWillMakeFirstResponder -{ - _jsRequestingFirstResponder = YES; -} - -- (BOOL)canBecomeFirstResponder -{ - return _jsRequestingFirstResponder; -} - -- (void)reactDidMakeFirstResponder -{ - _jsRequestingFirstResponder = NO; -} - -- (void)didMoveToWindow -{ - if (_jsRequestingFirstResponder) { - [self becomeFirstResponder]; - [self reactDidMakeFirstResponder]; - } -} - #pragma mark - Overrides - (void)setFont:(UIFont *)font diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index bd1e72bb6f4f5f..ea7e59803e0587 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -1077,10 +1077,7 @@ - (void)synchronouslyUpdateViewOnUIThread:(NSNumber *)reactTag { [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { UIView *newResponder = viewRegistry[reactTag]; - [newResponder reactWillMakeFirstResponder]; - if ([newResponder becomeFirstResponder]) { - [newResponder reactDidMakeFirstResponder]; - } + [newResponder reactFocus]; }]; } @@ -1088,7 +1085,7 @@ - (void)synchronouslyUpdateViewOnUIThread:(NSNumber *)reactTag { [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry){ UIView *currentResponder = viewRegistry[reactTag]; - [currentResponder resignFirstResponder]; + [currentResponder reactBlur]; }]; } diff --git a/React/Views/UIView+React.h b/React/Views/UIView+React.h index a5950af7bd5be3..899110438224a5 100644 --- a/React/Views/UIView+React.h +++ b/React/Views/UIView+React.h @@ -71,11 +71,16 @@ */ - (void)reactAddControllerToClosestParent:(UIViewController *)controller; +/** + * Focus manipulation. + */ +- (void)reactFocus; +- (void)reactFocusIfNeeded; +- (void)reactBlur; + /** * Responder overrides - to be deprecated. */ -- (void)reactWillMakeFirstResponder; -- (void)reactDidMakeFirstResponder; - (BOOL)reactRespondsToTouch:(UITouch *)touch; #if RCT_DEV diff --git a/React/Views/UIView+React.m b/React/Views/UIView+React.m index 43502719f22c17..4489fde306b70c 100644 --- a/React/Views/UIView+React.m +++ b/React/Views/UIView+React.m @@ -211,11 +211,40 @@ - (void)reactAddControllerToClosestParent:(UIViewController *)controller } } +/** + * Focus manipulation. + */ +- (BOOL)reactIsFocusNeeded +{ + return [(NSNumber *)objc_getAssociatedObject(self, @selector(reactIsFocusNeeded)) boolValue]; +} + +- (void)setReactIsFocusNeeded:(BOOL)isFocusNeeded +{ + objc_setAssociatedObject(self, @selector(reactIsFocusNeeded), @(isFocusNeeded), OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (void)reactFocus { + if (![self becomeFirstResponder]) { + self.reactIsFocusNeeded = YES; + } +} + +- (void)reactFocusIfNeeded { + if (self.reactIsFocusNeeded) { + if ([self becomeFirstResponder]) { + self.reactIsFocusNeeded = NO; + } + } +} + +- (void)reactBlur { + [self resignFirstResponder]; +} + /** * Responder overrides - to be deprecated. */ -- (void)reactWillMakeFirstResponder {}; -- (void)reactDidMakeFirstResponder {}; - (BOOL)reactRespondsToTouch:(__unused UITouch *)touch { return YES;