react-native/Libraries/Text/RCTBackedTextInputDelegateAdapter.m
Valentin Shergin ee9697e515 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
2017-07-18 14:46:22 -07:00

178 lines
5.6 KiB
Objective-C

/**
* 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