251 lines
7.1 KiB
Mathematica
Raw Normal View History

/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTUITextView.h"
#import <React/RCTUtils.h>
#import <React/UIView+React.h>
#import "RCTBackedTextInputDelegateAdapter.h"
@implementation RCTUITextView
{
UILabel *_placeholderView;
UITextView *_detachedTextView;
RCTBackedTextViewDelegateAdapter *_textInputDelegateAdapter;
}
static UIFont *defaultPlaceholderFont()
{
return [UIFont systemFontOfSize:17];
}
static UIColor *defaultPlaceholderColor()
{
// Default placeholder color from UITextField.
return [UIColor colorWithRed:0 green:0 blue:0.0980392 alpha:0.22];
}
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(textDidChange)
name:UITextViewTextDidChangeNotification
object:self];
_placeholderView = [[UILabel alloc] initWithFrame:self.bounds];
_placeholderView.isAccessibilityElement = NO;
_placeholderView.numberOfLines = 0;
_placeholderView.textColor = defaultPlaceholderColor();
[self addSubview:_placeholderView];
_textInputDelegateAdapter = [[RCTBackedTextViewDelegateAdapter alloc] initWithTextView:self];
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
iOS: Improve accessibility of multiline TextInputs Summary: Fixes #13691. Prior to this change, VoiceOver generally didn't say anything when selecting a multiline TextInput. The only exception was VoiceOver would announce the TextInput's placeholder if it had one set. After this change, VoiceOver announces the following for multiline TextInputs: - The TextInput's content. - The TextInput's placeholder if the TextInput doesn't have any content in it. - The fact that the TextInput is a textfield. - The cursor position if the TextInput is being edited. This is similar to the behavior of single line TextInputs. This change achieves this by disabling `RCTTextView` as an accessibility element. `RCTTextView` is a subclass of `RCTView` so VoiceOver doesn't recognize this as a textfield. Instead, VoiceOver now sees the child `RCTUITextView` which is a subclass of `UITextView` so VoiceOver does recognize it as a textfield. Additionally, an `accessibilityLabel` implementation was added to `RCTUITextView` in order to take the value of the placeholder into account. Verified the announcements of TextInputs with various props: - No placeholder and no content - Just a placeholder - Just content - Both a placeholder and content Did this for both singe line inputs and multiline inputs. For setting content in multiline inputs, I tested both using the `value` prop and passing children. All other props being equal, these configurations resulted in similar announcements. I verified that the following accessibility props work the same on singleline and multiline TextInputs: - `accessible` - `accessibilityLabel` - `accessibilityTraits` - `accessibilityViewIsModal` - `onAccessibilityTap` - `onMagicTap` Additionally, my team has been using this change in our app. Adam Comella Microsoft Corp. Closes https://github.com/facebook/react-native/pull/14200 Differential Revision: D5185727 Pulled By: shergin fbshipit-source-id: 94271e6c8b089eb82006b52fe7917649d69e74af
2017-06-05 16:08:27 -07:00
- (NSString *)accessibilityLabel
{
NSMutableString *accessibilityLabel = [NSMutableString new];
Add option to hide context menu for TextInput #17335 Summary: <!-- Thank you for sending the PR! We appreciate you spending the time to work on these changes. Help us understand your motivation by explaining why you decided to make this change. You can learn more about contributing to React Native here: http://facebook.github.io/react-native/docs/contributing.html Happy contributing! --> There is currently no way to disable to context menu that automatically appears over a TextInput. This is especially troublesome if you would like to disable the user from pasting text into certain fields. This PR adds a `contextMenuHidden` property to TextInput that will hide it. I'm not sure if testing is necessary here. I would be happy to investigate further on how this would be tested, if deemed necessary! https://github.com/facebook/react-native-website/pull/95 <!-- Help reviewers and the release process by writing your own release notes **INTERNAL and MINOR tagged notes will not be included in the next version's final release notes.** CATEGORY [----------] TYPE [ CLI ] [-------------] LOCATION [ DOCS ] [ BREAKING ] [-------------] [ GENERAL ] [ BUGFIX ] [-{Component}-] [ INTERNAL ] [ ENHANCEMENT ] [ {File} ] [ IOS ] [ FEATURE ] [ {Directory} ] |-----------| [ ANDROID ] [ MINOR ] [ {Framework} ] - | {Message} | [----------] [-------------] [-------------] |-----------| [CATEGORY] [TYPE] [LOCATION] - MESSAGE EXAMPLES: [IOS] [BREAKING] [FlatList] - Change a thing that breaks other things [ANDROID] [BUGFIX] [TextInput] - Did a thing to TextInput [CLI] [FEATURE] [local-cli/info/info.js] - CLI easier to do things with [DOCS] [BUGFIX] [GettingStarted.md] - Accidentally a thing/word [GENERAL] [ENHANCEMENT] [Yoga] - Added new yoga thing/position [INTERNAL] [FEATURE] [./scripts] - Added thing to script that nobody will see --> [FEATURE][TextInput] - Added `contextMenuHidden` property Closes https://github.com/facebook/react-native/pull/18125 Differential Revision: D7101888 Pulled By: hramos fbshipit-source-id: fe36603a3fbdcefbd644251a7ea894ac7e23e5b8
2018-02-27 17:29:40 -08:00
iOS: Improve accessibility of multiline TextInputs Summary: Fixes #13691. Prior to this change, VoiceOver generally didn't say anything when selecting a multiline TextInput. The only exception was VoiceOver would announce the TextInput's placeholder if it had one set. After this change, VoiceOver announces the following for multiline TextInputs: - The TextInput's content. - The TextInput's placeholder if the TextInput doesn't have any content in it. - The fact that the TextInput is a textfield. - The cursor position if the TextInput is being edited. This is similar to the behavior of single line TextInputs. This change achieves this by disabling `RCTTextView` as an accessibility element. `RCTTextView` is a subclass of `RCTView` so VoiceOver doesn't recognize this as a textfield. Instead, VoiceOver now sees the child `RCTUITextView` which is a subclass of `UITextView` so VoiceOver does recognize it as a textfield. Additionally, an `accessibilityLabel` implementation was added to `RCTUITextView` in order to take the value of the placeholder into account. Verified the announcements of TextInputs with various props: - No placeholder and no content - Just a placeholder - Just content - Both a placeholder and content Did this for both singe line inputs and multiline inputs. For setting content in multiline inputs, I tested both using the `value` prop and passing children. All other props being equal, these configurations resulted in similar announcements. I verified that the following accessibility props work the same on singleline and multiline TextInputs: - `accessible` - `accessibilityLabel` - `accessibilityTraits` - `accessibilityViewIsModal` - `onAccessibilityTap` - `onMagicTap` Additionally, my team has been using this change in our app. Adam Comella Microsoft Corp. Closes https://github.com/facebook/react-native/pull/14200 Differential Revision: D5185727 Pulled By: shergin fbshipit-source-id: 94271e6c8b089eb82006b52fe7917649d69e74af
2017-06-05 16:08:27 -07:00
NSString *superAccessibilityLabel = [super accessibilityLabel];
if (superAccessibilityLabel.length > 0) {
[accessibilityLabel appendString:superAccessibilityLabel];
}
Add option to hide context menu for TextInput #17335 Summary: <!-- Thank you for sending the PR! We appreciate you spending the time to work on these changes. Help us understand your motivation by explaining why you decided to make this change. You can learn more about contributing to React Native here: http://facebook.github.io/react-native/docs/contributing.html Happy contributing! --> There is currently no way to disable to context menu that automatically appears over a TextInput. This is especially troublesome if you would like to disable the user from pasting text into certain fields. This PR adds a `contextMenuHidden` property to TextInput that will hide it. I'm not sure if testing is necessary here. I would be happy to investigate further on how this would be tested, if deemed necessary! https://github.com/facebook/react-native-website/pull/95 <!-- Help reviewers and the release process by writing your own release notes **INTERNAL and MINOR tagged notes will not be included in the next version's final release notes.** CATEGORY [----------] TYPE [ CLI ] [-------------] LOCATION [ DOCS ] [ BREAKING ] [-------------] [ GENERAL ] [ BUGFIX ] [-{Component}-] [ INTERNAL ] [ ENHANCEMENT ] [ {File} ] [ IOS ] [ FEATURE ] [ {Directory} ] |-----------| [ ANDROID ] [ MINOR ] [ {Framework} ] - | {Message} | [----------] [-------------] [-------------] |-----------| [CATEGORY] [TYPE] [LOCATION] - MESSAGE EXAMPLES: [IOS] [BREAKING] [FlatList] - Change a thing that breaks other things [ANDROID] [BUGFIX] [TextInput] - Did a thing to TextInput [CLI] [FEATURE] [local-cli/info/info.js] - CLI easier to do things with [DOCS] [BUGFIX] [GettingStarted.md] - Accidentally a thing/word [GENERAL] [ENHANCEMENT] [Yoga] - Added new yoga thing/position [INTERNAL] [FEATURE] [./scripts] - Added thing to script that nobody will see --> [FEATURE][TextInput] - Added `contextMenuHidden` property Closes https://github.com/facebook/react-native/pull/18125 Differential Revision: D7101888 Pulled By: hramos fbshipit-source-id: fe36603a3fbdcefbd644251a7ea894ac7e23e5b8
2018-02-27 17:29:40 -08:00
if (self.placeholder.length > 0 && self.attributedText.string.length == 0) {
iOS: Improve accessibility of multiline TextInputs Summary: Fixes #13691. Prior to this change, VoiceOver generally didn't say anything when selecting a multiline TextInput. The only exception was VoiceOver would announce the TextInput's placeholder if it had one set. After this change, VoiceOver announces the following for multiline TextInputs: - The TextInput's content. - The TextInput's placeholder if the TextInput doesn't have any content in it. - The fact that the TextInput is a textfield. - The cursor position if the TextInput is being edited. This is similar to the behavior of single line TextInputs. This change achieves this by disabling `RCTTextView` as an accessibility element. `RCTTextView` is a subclass of `RCTView` so VoiceOver doesn't recognize this as a textfield. Instead, VoiceOver now sees the child `RCTUITextView` which is a subclass of `UITextView` so VoiceOver does recognize it as a textfield. Additionally, an `accessibilityLabel` implementation was added to `RCTUITextView` in order to take the value of the placeholder into account. Verified the announcements of TextInputs with various props: - No placeholder and no content - Just a placeholder - Just content - Both a placeholder and content Did this for both singe line inputs and multiline inputs. For setting content in multiline inputs, I tested both using the `value` prop and passing children. All other props being equal, these configurations resulted in similar announcements. I verified that the following accessibility props work the same on singleline and multiline TextInputs: - `accessible` - `accessibilityLabel` - `accessibilityTraits` - `accessibilityViewIsModal` - `onAccessibilityTap` - `onMagicTap` Additionally, my team has been using this change in our app. Adam Comella Microsoft Corp. Closes https://github.com/facebook/react-native/pull/14200 Differential Revision: D5185727 Pulled By: shergin fbshipit-source-id: 94271e6c8b089eb82006b52fe7917649d69e74af
2017-06-05 16:08:27 -07:00
if (accessibilityLabel.length > 0) {
[accessibilityLabel appendString:@" "];
}
[accessibilityLabel appendString:self.placeholder];
iOS: Improve accessibility of multiline TextInputs Summary: Fixes #13691. Prior to this change, VoiceOver generally didn't say anything when selecting a multiline TextInput. The only exception was VoiceOver would announce the TextInput's placeholder if it had one set. After this change, VoiceOver announces the following for multiline TextInputs: - The TextInput's content. - The TextInput's placeholder if the TextInput doesn't have any content in it. - The fact that the TextInput is a textfield. - The cursor position if the TextInput is being edited. This is similar to the behavior of single line TextInputs. This change achieves this by disabling `RCTTextView` as an accessibility element. `RCTTextView` is a subclass of `RCTView` so VoiceOver doesn't recognize this as a textfield. Instead, VoiceOver now sees the child `RCTUITextView` which is a subclass of `UITextView` so VoiceOver does recognize it as a textfield. Additionally, an `accessibilityLabel` implementation was added to `RCTUITextView` in order to take the value of the placeholder into account. Verified the announcements of TextInputs with various props: - No placeholder and no content - Just a placeholder - Just content - Both a placeholder and content Did this for both singe line inputs and multiline inputs. For setting content in multiline inputs, I tested both using the `value` prop and passing children. All other props being equal, these configurations resulted in similar announcements. I verified that the following accessibility props work the same on singleline and multiline TextInputs: - `accessible` - `accessibilityLabel` - `accessibilityTraits` - `accessibilityViewIsModal` - `onAccessibilityTap` - `onMagicTap` Additionally, my team has been using this change in our app. Adam Comella Microsoft Corp. Closes https://github.com/facebook/react-native/pull/14200 Differential Revision: D5185727 Pulled By: shergin fbshipit-source-id: 94271e6c8b089eb82006b52fe7917649d69e74af
2017-06-05 16:08:27 -07:00
}
Add option to hide context menu for TextInput #17335 Summary: <!-- Thank you for sending the PR! We appreciate you spending the time to work on these changes. Help us understand your motivation by explaining why you decided to make this change. You can learn more about contributing to React Native here: http://facebook.github.io/react-native/docs/contributing.html Happy contributing! --> There is currently no way to disable to context menu that automatically appears over a TextInput. This is especially troublesome if you would like to disable the user from pasting text into certain fields. This PR adds a `contextMenuHidden` property to TextInput that will hide it. I'm not sure if testing is necessary here. I would be happy to investigate further on how this would be tested, if deemed necessary! https://github.com/facebook/react-native-website/pull/95 <!-- Help reviewers and the release process by writing your own release notes **INTERNAL and MINOR tagged notes will not be included in the next version's final release notes.** CATEGORY [----------] TYPE [ CLI ] [-------------] LOCATION [ DOCS ] [ BREAKING ] [-------------] [ GENERAL ] [ BUGFIX ] [-{Component}-] [ INTERNAL ] [ ENHANCEMENT ] [ {File} ] [ IOS ] [ FEATURE ] [ {Directory} ] |-----------| [ ANDROID ] [ MINOR ] [ {Framework} ] - | {Message} | [----------] [-------------] [-------------] |-----------| [CATEGORY] [TYPE] [LOCATION] - MESSAGE EXAMPLES: [IOS] [BREAKING] [FlatList] - Change a thing that breaks other things [ANDROID] [BUGFIX] [TextInput] - Did a thing to TextInput [CLI] [FEATURE] [local-cli/info/info.js] - CLI easier to do things with [DOCS] [BUGFIX] [GettingStarted.md] - Accidentally a thing/word [GENERAL] [ENHANCEMENT] [Yoga] - Added new yoga thing/position [INTERNAL] [FEATURE] [./scripts] - Added thing to script that nobody will see --> [FEATURE][TextInput] - Added `contextMenuHidden` property Closes https://github.com/facebook/react-native/pull/18125 Differential Revision: D7101888 Pulled By: hramos fbshipit-source-id: fe36603a3fbdcefbd644251a7ea894ac7e23e5b8
2018-02-27 17:29:40 -08:00
iOS: Improve accessibility of multiline TextInputs Summary: Fixes #13691. Prior to this change, VoiceOver generally didn't say anything when selecting a multiline TextInput. The only exception was VoiceOver would announce the TextInput's placeholder if it had one set. After this change, VoiceOver announces the following for multiline TextInputs: - The TextInput's content. - The TextInput's placeholder if the TextInput doesn't have any content in it. - The fact that the TextInput is a textfield. - The cursor position if the TextInput is being edited. This is similar to the behavior of single line TextInputs. This change achieves this by disabling `RCTTextView` as an accessibility element. `RCTTextView` is a subclass of `RCTView` so VoiceOver doesn't recognize this as a textfield. Instead, VoiceOver now sees the child `RCTUITextView` which is a subclass of `UITextView` so VoiceOver does recognize it as a textfield. Additionally, an `accessibilityLabel` implementation was added to `RCTUITextView` in order to take the value of the placeholder into account. Verified the announcements of TextInputs with various props: - No placeholder and no content - Just a placeholder - Just content - Both a placeholder and content Did this for both singe line inputs and multiline inputs. For setting content in multiline inputs, I tested both using the `value` prop and passing children. All other props being equal, these configurations resulted in similar announcements. I verified that the following accessibility props work the same on singleline and multiline TextInputs: - `accessible` - `accessibilityLabel` - `accessibilityTraits` - `accessibilityViewIsModal` - `onAccessibilityTap` - `onMagicTap` Additionally, my team has been using this change in our app. Adam Comella Microsoft Corp. Closes https://github.com/facebook/react-native/pull/14200 Differential Revision: D5185727 Pulled By: shergin fbshipit-source-id: 94271e6c8b089eb82006b52fe7917649d69e74af
2017-06-05 16:08:27 -07:00
return accessibilityLabel;
}
#pragma mark - Properties
- (void)setPlaceholder:(NSString *)placeholder
{
_placeholder = placeholder;
_placeholderView.text = _placeholder;
}
- (void)setPlaceholderColor:(UIColor *)placeholderColor
{
_placeholderColor = placeholderColor;
_placeholderView.textColor = _placeholderColor ?: defaultPlaceholderColor();
}
- (void)textDidChange
{
_textWasPasted = NO;
[self invalidatePlaceholderVisibility];
}
#pragma mark - Overrides
- (void)setFont:(UIFont *)font
{
[super setFont:font];
_placeholderView.font = font ?: defaultPlaceholderFont();
}
- (void)setTextAlignment:(NSTextAlignment)textAlignment
{
[super setTextAlignment:textAlignment];
_placeholderView.textAlignment = textAlignment;
}
- (void)setText:(NSString *)text
{
[super setText:text];
[self textDidChange];
}
- (void)setAttributedText:(NSAttributedString *)attributedText
{
[super setAttributedText:attributedText];
[self textDidChange];
}
#pragma mark - Overrides
- (void)setSelectedTextRange:(UITextRange *)selectedTextRange notifyDelegate:(BOOL)notifyDelegate
{
if (!notifyDelegate) {
// We have to notify an adapter that following selection change was initiated programmatically,
// so the adapter must not generate a notification for it.
[_textInputDelegateAdapter skipNextTextInputDidChangeSelectionEventWithTextRange:selectedTextRange];
}
[super setSelectedTextRange:selectedTextRange];
}
- (void)paste:(id)sender
{
[super paste:sender];
_textWasPasted = YES;
}
- (void)setContentOffset:(CGPoint)contentOffset animated:(__unused BOOL)animated
{
// Turning off scroll animation.
// This fixes the problem also known as "flaky scrolling".
[super setContentOffset:contentOffset animated:NO];
}
#pragma mark - Layout
- (CGFloat)preferredMaxLayoutWidth
{
// Returning size DOES contain `textContainerInset` (aka `padding`).
return _preferredMaxLayoutWidth ?: self.placeholderSize.width;
}
- (CGSize)placeholderSize
{
UIEdgeInsets textContainerInset = self.textContainerInset;
NSString *placeholder = self.placeholder ?: @"";
CGSize placeholderSize = [placeholder sizeWithAttributes:@{NSFontAttributeName: self.font ?: defaultPlaceholderFont()}];
placeholderSize = CGSizeMake(RCTCeilPixelValue(placeholderSize.width), RCTCeilPixelValue(placeholderSize.height));
placeholderSize.width += textContainerInset.left + textContainerInset.right;
placeholderSize.height += textContainerInset.top + textContainerInset.bottom;
// Returning size DOES contain `textContainerInset` (aka `padding`; as `sizeThatFits:` does).
return placeholderSize;
}
- (CGSize)contentSize
{
CGSize contentSize = super.contentSize;
CGSize placeholderSize = self.placeholderSize;
// When a text input is empty, it actually displays a placehoder.
// So, we have to consider `placeholderSize` as a minimum `contentSize`.
// Returning size DOES contain `textContainerInset` (aka `padding`).
return CGSizeMake(
MAX(contentSize.width, placeholderSize.width),
MAX(contentSize.height, placeholderSize.height));
}
- (void)layoutSubviews
{
[super layoutSubviews];
CGRect textFrame = UIEdgeInsetsInsetRect(self.bounds, self.textContainerInset);
CGFloat placeholderHeight = [_placeholderView sizeThatFits:textFrame.size].height;
textFrame.size.height = MIN(placeholderHeight, textFrame.size.height);
_placeholderView.frame = textFrame;
}
- (CGSize)intrinsicContentSize
{
// Returning size DOES contain `textContainerInset` (aka `padding`).
return [self sizeThatFits:CGSizeMake(self.preferredMaxLayoutWidth, CGFLOAT_MAX)];
}
- (CGSize)sizeThatFits:(CGSize)size
{
// Returned fitting size depends on text size and placeholder size.
CGSize textSize = [self fixedSizeThatFits:size];
CGSize placeholderSize = self.placeholderSize;
// Returning size DOES contain `textContainerInset` (aka `padding`).
return CGSizeMake(MAX(textSize.width, placeholderSize.width), MAX(textSize.height, placeholderSize.height));
}
- (CGSize)fixedSizeThatFits:(CGSize)size
{
// UITextView on iOS 8 has a bug that automatically scrolls to the top
// when calling `sizeThatFits:`. Use a copy so that self is not screwed up.
static BOOL useCustomImplementation = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
useCustomImplementation = ![[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){9,0,0}];
});
if (!useCustomImplementation) {
return [super sizeThatFits:size];
}
if (!_detachedTextView) {
_detachedTextView = [UITextView new];
}
_detachedTextView.attributedText = self.attributedText;
_detachedTextView.font = self.font;
_detachedTextView.textContainerInset = self.textContainerInset;
return [_detachedTextView sizeThatFits:size];
}
Add option to hide context menu for TextInput #17335 Summary: <!-- Thank you for sending the PR! We appreciate you spending the time to work on these changes. Help us understand your motivation by explaining why you decided to make this change. You can learn more about contributing to React Native here: http://facebook.github.io/react-native/docs/contributing.html Happy contributing! --> There is currently no way to disable to context menu that automatically appears over a TextInput. This is especially troublesome if you would like to disable the user from pasting text into certain fields. This PR adds a `contextMenuHidden` property to TextInput that will hide it. I'm not sure if testing is necessary here. I would be happy to investigate further on how this would be tested, if deemed necessary! https://github.com/facebook/react-native-website/pull/95 <!-- Help reviewers and the release process by writing your own release notes **INTERNAL and MINOR tagged notes will not be included in the next version's final release notes.** CATEGORY [----------] TYPE [ CLI ] [-------------] LOCATION [ DOCS ] [ BREAKING ] [-------------] [ GENERAL ] [ BUGFIX ] [-{Component}-] [ INTERNAL ] [ ENHANCEMENT ] [ {File} ] [ IOS ] [ FEATURE ] [ {Directory} ] |-----------| [ ANDROID ] [ MINOR ] [ {Framework} ] - | {Message} | [----------] [-------------] [-------------] |-----------| [CATEGORY] [TYPE] [LOCATION] - MESSAGE EXAMPLES: [IOS] [BREAKING] [FlatList] - Change a thing that breaks other things [ANDROID] [BUGFIX] [TextInput] - Did a thing to TextInput [CLI] [FEATURE] [local-cli/info/info.js] - CLI easier to do things with [DOCS] [BUGFIX] [GettingStarted.md] - Accidentally a thing/word [GENERAL] [ENHANCEMENT] [Yoga] - Added new yoga thing/position [INTERNAL] [FEATURE] [./scripts] - Added thing to script that nobody will see --> [FEATURE][TextInput] - Added `contextMenuHidden` property Closes https://github.com/facebook/react-native/pull/18125 Differential Revision: D7101888 Pulled By: hramos fbshipit-source-id: fe36603a3fbdcefbd644251a7ea894ac7e23e5b8
2018-02-27 17:29:40 -08:00
#pragma mark - Context Menu
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
if (_contextMenuHidden) {
return NO;
}
return [super canPerformAction:action withSender:sender];
}
#pragma mark - Placeholder
- (void)invalidatePlaceholderVisibility
{
BOOL isVisible = _placeholder.length != 0 && self.attributedText.length == 0;
_placeholderView.hidden = !isVisible;
}
@end