Permalink
Browse files

Introducing `RCTBackedTextInputDelegate`

Summary:
Nothing behavioral changed in this diff; just moving code around.

`RCTBackedTextInputDelegate` is the new protocol which supposed to be common determinator among of UITextFieldDelegate and UITextViewDelegate
(and bunch of events and notifications around UITextInput and UITextView).

We need this reach two goals in the future:
 * Incapsulate UIKit imperfections related hack in dedicated protocol adapter. So, doing this we can fix more UIKit related bugs without touching real RN text handling logic. (Yes, we still have a bunch of bugs, which we cannot fix because it is undoable with the current architecture. This diff does NOT fix anything though.)
 * We can unify logic in RCTTextField and RCTTextView (even more!), moving it to a superclass. If we do so, we can fix another bunch of bugs related to RN imperfections. And have singleline/multiline inputs implementations even more consistent.

Reviewed By: mmmulani

Differential Revision: D5296041

fbshipit-source-id: 318fd850e946a3c34933002a6bde34a0a45a6293
  • Loading branch information...
shergin authored and facebook-github-bot committed Jul 18, 2017
1 parent 2a7bde0 commit ee9697e5155aa972564d5aac90ceeb9db100750d
@@ -0,0 +1,28 @@
/**
* 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 <UIKit/UIKit.h>
@protocol RCTBackedTextInputViewProtocol;
@protocol RCTBackedTextInputDelegate <NSObject>
- (BOOL)textInputShouldBeginEditing; // Return `NO` to disallow editing.
- (void)textInputDidBeginEditing;
- (BOOL)textInputShouldEndEditing; // Return `YES` to allow editing to stop and to resign first responder status. `NO` to disallow the editing session to end.
- (void)textInputDidEndEditing; // May be called if forced even if `textInputShouldEndEditing` returns `NO` (e.g. view removed from window) or `[textInput endEditing:YES]` called.
- (void)textInputDidEndEditingOnExit; // May be called right before `textInputShouldEndEditing` if "Submit" button was pressed.
- (BOOL)textInputShouldChangeTextInRange:(NSRange)range replacementText:(NSString *)string; // Return NO to not change text.
- (void)textInputDidChange;
- (void)textInputDidChangeSelection;
@end
@@ -0,0 +1,29 @@
/**
* 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 <UIKit/UIKit.h>
#import "RCTBackedTextInputViewProtocol.h"
#import "RCTBackedTextInputDelegate.h"
#pragma mark - RCTBackedTextFieldDelegateAdapter (for UITextField)
@interface RCTBackedTextFieldDelegateAdapter : NSObject
- (instancetype)initWithTextField:(UITextField<RCTBackedTextInputViewProtocol> *)backedTextInput;
@end
#pragma mark - RCTBackedTextViewDelegateAdapter (for UITextView)
@interface RCTBackedTextViewDelegateAdapter : NSObject
- (instancetype)initWithTextView:(UITextView<RCTBackedTextInputViewProtocol> *)backedTextInput;
@end
@@ -0,0 +1,177 @@
/**
* 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 "RCTBackedTextInputDelegateAdapter.h"
#pragma mark - RCTBackedTextFieldDelegateAdapter (for UITextField)
static void *TextFieldSelectionObservingContext = &TextFieldSelectionObservingContext;
@interface RCTBackedTextFieldDelegateAdapter () <UITextFieldDelegate>
@end
@implementation RCTBackedTextFieldDelegateAdapter {
__weak UITextField<RCTBackedTextInputViewProtocol> *_backedTextInput;
__unsafe_unretained UITextField<RCTBackedTextInputViewProtocol> *_unsafeBackedTextInput;
}
- (instancetype)initWithTextField:(UITextField<RCTBackedTextInputViewProtocol> *)backedTextInput
{
if (self = [super init]) {
_backedTextInput = backedTextInput;
_unsafeBackedTextInput = backedTextInput;
backedTextInput.delegate = self;
[_backedTextInput addTarget:self action:@selector(textFieldDidChange) forControlEvents:UIControlEventEditingChanged];
[_backedTextInput addTarget:self action:@selector(textFieldDidEndEditingOnExit) forControlEvents:UIControlEventEditingDidEndOnExit];
// We have to use `unsafe_unretained` pointer to `backedTextInput` for subscribing (and especially unsubscribing) for it
// because `weak` pointers do not KVO complient, unfortunately.
[_unsafeBackedTextInput addObserver:self forKeyPath:@"selectedTextRange" options:0 context:TextFieldSelectionObservingContext];
}
return self;
}
- (void)dealloc
{
[_backedTextInput removeTarget:self action:nil forControlEvents:UIControlEventEditingChanged];
[_backedTextInput removeTarget:self action:nil forControlEvents:UIControlEventEditingDidEndOnExit];
[_unsafeBackedTextInput removeObserver:self forKeyPath:@"selectedTextRange" context:TextFieldSelectionObservingContext];
}
#pragma mark - UITextFieldDelegate
- (BOOL)textFieldShouldBeginEditing:(__unused UITextField *)textField
{
return [_backedTextInput.textInputDelegate textInputShouldBeginEditing];
}
- (void)textFieldDidBeginEditing:(__unused UITextField *)textField
{
[_backedTextInput.textInputDelegate textInputDidBeginEditing];
}
- (BOOL)textFieldShouldEndEditing:(__unused UITextField *)textField
{
return [_backedTextInput.textInputDelegate textInputShouldEndEditing];
}
- (void)textFieldDidEndEditing:(__unused UITextField *)textField
{
[_backedTextInput.textInputDelegate textInputDidEndEditing];
}
- (BOOL)textField:(__unused UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
return [_backedTextInput.textInputDelegate textInputShouldChangeTextInRange:range replacementText:string];
}
#pragma mark - Key Value Observing
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(nullable id)object
change:(NSDictionary *)change
context:(void *)context
{
if (context == TextFieldSelectionObservingContext) {
if ([keyPath isEqualToString:@"selectedTextRange"]) {
[_backedTextInput.textInputDelegate textInputDidChangeSelection];
}
return;
}
[super observeValueForKeyPath:keyPath
ofObject:object
change:change
context:context];
}
#pragma mark - UIControlEventEditing* Family Events
- (void)textFieldDidChange
{
[_backedTextInput.textInputDelegate textInputDidChange];
}
- (void)textFieldDidEndEditingOnExit
{
[_backedTextInput.textInputDelegate textInputDidEndEditingOnExit];
}
#pragma mark - UIKeyboardInput (private UIKit protocol)
// This method allows us to detect a [Backspace] `keyPress`
// even when there is no more text in the `UITextField`.
- (BOOL)keyboardInputShouldDelete:(__unused UITextField *)textField
{
[_backedTextInput.textInputDelegate textInputShouldChangeTextInRange:NSMakeRange(0, 0) replacementText:@""];
return YES;
}
@end
#pragma mark - RCTBackedTextViewDelegateAdapter (for UITextView)
@interface RCTBackedTextViewDelegateAdapter () <UITextViewDelegate>
@end
@implementation RCTBackedTextViewDelegateAdapter {
__weak UITextView<RCTBackedTextInputViewProtocol> *_backedTextInput;
}
- (instancetype)initWithTextView:(UITextView<RCTBackedTextInputViewProtocol> *)backedTextInput
{
if (self = [super init]) {
_backedTextInput = backedTextInput;
backedTextInput.delegate = self;
}
return self;
}
#pragma mark - UITextViewDelegate
- (BOOL)textViewShouldBeginEditing:(__unused UITextView *)textView
{
return [_backedTextInput.textInputDelegate textInputShouldBeginEditing];
}
- (void)textViewDidBeginEditing:(__unused UITextView *)textView
{
[_backedTextInput.textInputDelegate textInputDidBeginEditing];
}
- (BOOL)textViewShouldEndEditing:(__unused UITextView *)textView
{
return [_backedTextInput.textInputDelegate textInputShouldEndEditing];
}
- (void)textViewDidEndEditing:(__unused UITextView *)textView
{
[_backedTextInput.textInputDelegate textInputDidEndEditing];
}
- (BOOL)textView:(__unused UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
return [_backedTextInput.textInputDelegate textInputShouldChangeTextInRange:range replacementText:text];
}
- (void)textViewDidChange:(__unused UITextView *)textView
{
[_backedTextInput.textInputDelegate textInputDidChange];
}
- (void)textViewDidChangeSelection:(__unused UITextView *)textView
{
[_backedTextInput.textInputDelegate textInputDidChangeSelection];
}
@end
@@ -9,6 +9,8 @@
#import <UIKit/UIKit.h>
@protocol RCTBackedTextInputDelegate;
@protocol RCTBackedTextInputViewProtocol <UITextInput>
@property (nonatomic, copy, nullable) NSString *text;
@@ -19,5 +21,6 @@
@property (nonatomic, strong, nullable) UIFont *font;
@property (nonatomic, assign) UIEdgeInsets textContainerInset;
@property (nonatomic, strong, nullable) UIView *inputAccessoryView;
@property (nonatomic, weak, nullable) id<RCTBackedTextInputDelegate> textInputDelegate;
@end
@@ -27,6 +27,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 */; };
598F41261F145D4900B8495B /* RCTBackedTextInputDelegateAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = 598F41251F145D4900B8495B /* RCTBackedTextInputDelegateAdapter.m */; };
598F41271F145D4900B8495B /* RCTBackedTextInputDelegateAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = 598F41251F145D4900B8495B /* RCTBackedTextInputDelegateAdapter.m */; };
599DF25F1F0306660079B53E /* RCTBackedTextInputViewProtocol.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 599DF25D1F0304B30079B53E /* RCTBackedTextInputViewProtocol.h */; };
599DF2611F0306C30079B53E /* RCTBackedTextInputViewProtocol.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 599DF25D1F0304B30079B53E /* RCTBackedTextInputViewProtocol.h */; };
599DF2641F03076D0079B53E /* RCTTextInput.m in Sources */ = {isa = PBXBuildFile; fileRef = 599DF2631F03076D0079B53E /* RCTTextInput.m */; };
@@ -95,6 +97,9 @@
58B511CD1A9E6C5C00147676 /* RCTTextManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTextManager.m; sourceTree = "<group>"; };
58B512141A9E6EFF00147676 /* RCTText.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTText.m; sourceTree = "<group>"; };
58B512151A9E6EFF00147676 /* RCTText.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTText.h; sourceTree = "<group>"; };
598F41231F145D4900B8495B /* RCTBackedTextInputDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBackedTextInputDelegate.h; sourceTree = "<group>"; };
598F41241F145D4900B8495B /* RCTBackedTextInputDelegateAdapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBackedTextInputDelegateAdapter.h; sourceTree = "<group>"; };
598F41251F145D4900B8495B /* RCTBackedTextInputDelegateAdapter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBackedTextInputDelegateAdapter.m; sourceTree = "<group>"; };
599DF25D1F0304B30079B53E /* RCTBackedTextInputViewProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBackedTextInputViewProtocol.h; sourceTree = "<group>"; };
599DF2621F03076D0079B53E /* RCTTextInput.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTextInput.h; sourceTree = "<group>"; };
599DF2631F03076D0079B53E /* RCTTextInput.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTextInput.m; sourceTree = "<group>"; };
@@ -115,6 +120,9 @@
isa = PBXGroup;
children = (
58B5119C1A9E6C1200147676 /* Products */,
598F41231F145D4900B8495B /* RCTBackedTextInputDelegate.h */,
598F41241F145D4900B8495B /* RCTBackedTextInputDelegateAdapter.h */,
598F41251F145D4900B8495B /* RCTBackedTextInputDelegateAdapter.m */,
599DF25D1F0304B30079B53E /* RCTBackedTextInputViewProtocol.h */,
AF3225F71DE5574F00D3E7E7 /* RCTConvert+Text.h */,
AF3225F81DE5574F00D3E7E7 /* RCTConvert+Text.m */,
@@ -244,6 +252,7 @@
2D3B5F361D9B106F00451313 /* RCTShadowText.m in Sources */,
2D3B5F3B1D9B106F00451313 /* RCTTextView.m in Sources */,
59AF89AB1EDCBCC700F004B1 /* RCTUITextField.m in Sources */,
598F41271F145D4900B8495B /* RCTBackedTextInputDelegateAdapter.m in Sources */,
2D3B5F3A1D9B106F00451313 /* RCTTextFieldManager.m in Sources */,
599DF2651F03076D0079B53E /* RCTTextInput.m in Sources */,
2D3B5F341D9B103100451313 /* RCTRawTextManager.m in Sources */,
@@ -267,6 +276,7 @@
19FC5C851D41A4120090108F /* RCTTextSelection.m in Sources */,
1362F1001B4D51F400E06D8C /* RCTTextField.m in Sources */,
59AF89AA1EDCBCC700F004B1 /* RCTUITextField.m in Sources */,
598F41261F145D4900B8495B /* RCTBackedTextInputDelegateAdapter.m in Sources */,
58B512161A9E6EFF00147676 /* RCTText.m in Sources */,
599DF2641F03076D0079B53E /* RCTTextInput.m in Sources */,
1362F1011B4D51F400E06D8C /* RCTTextFieldManager.m in Sources */,
@@ -26,6 +26,4 @@
@property (nonatomic, copy) RCTDirectEventBlock onSelectionChange;
@property (nonatomic, strong) RCTUITextField *textField;
@end
Oops, something went wrong.

0 comments on commit ee9697e

Please sign in to comment.