RCTUITextField.m 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. /*
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. */
  7. #import <React/RCTUITextField.h>
  8. #import <React/RCTUtils.h>
  9. #import <React/UIView+React.h>
  10. #import <React/RCTBackedTextInputDelegateAdapter.h>
  11. #import <React/RCTTextAttributes.h>
  12. @implementation RCTUITextField {
  13. RCTBackedTextFieldDelegateAdapter *_textInputDelegateAdapter;
  14. NSDictionary<NSAttributedStringKey, id> *_defaultTextAttributes;
  15. }
  16. - (instancetype)initWithFrame:(CGRect)frame
  17. {
  18. if (self = [super initWithFrame:frame]) {
  19. [[NSNotificationCenter defaultCenter] addObserver:self
  20. selector:@selector(_textDidChange)
  21. name:UITextFieldTextDidChangeNotification
  22. object:self];
  23. _textInputDelegateAdapter = [[RCTBackedTextFieldDelegateAdapter alloc] initWithTextField:self];
  24. _scrollEnabled = YES;
  25. }
  26. return self;
  27. }
  28. - (void)_textDidChange
  29. {
  30. _textWasPasted = NO;
  31. }
  32. #pragma mark - Accessibility
  33. - (void)setIsAccessibilityElement:(BOOL)isAccessibilityElement
  34. {
  35. // UITextField is accessible by default (some nested views are) and disabling that is not supported.
  36. // On iOS accessible elements cannot be nested, therefore enabling accessibility for some container view
  37. // (even in a case where this view is a part of public API of TextInput on iOS) shadows some features implemented inside the component.
  38. }
  39. #pragma mark - Properties
  40. - (void)setTextContainerInset:(UIEdgeInsets)textContainerInset
  41. {
  42. _textContainerInset = textContainerInset;
  43. [self setNeedsLayout];
  44. }
  45. - (void)setPlaceholder:(NSString *)placeholder
  46. {
  47. [super setPlaceholder:placeholder];
  48. [self _updatePlaceholder];
  49. }
  50. - (void)setPlaceholderColor:(UIColor *)placeholderColor
  51. {
  52. _placeholderColor = placeholderColor;
  53. [self _updatePlaceholder];
  54. }
  55. - (void)setDefaultTextAttributes:(NSDictionary<NSAttributedStringKey, id> *)defaultTextAttributes
  56. {
  57. if ([_defaultTextAttributes isEqualToDictionary:defaultTextAttributes]) {
  58. return;
  59. }
  60. _defaultTextAttributes = defaultTextAttributes;
  61. [super setDefaultTextAttributes:defaultTextAttributes];
  62. [self _updatePlaceholder];
  63. }
  64. - (NSDictionary<NSAttributedStringKey, id> *)defaultTextAttributes
  65. {
  66. return _defaultTextAttributes;
  67. }
  68. - (void)_updatePlaceholder
  69. {
  70. self.attributedPlaceholder = [[NSAttributedString alloc] initWithString:self.placeholder ?: @""
  71. attributes:[self _placeholderTextAttributes]];
  72. }
  73. - (BOOL)isEditable
  74. {
  75. return self.isEnabled;
  76. }
  77. - (void)setEditable:(BOOL)editable
  78. {
  79. self.enabled = editable;
  80. }
  81. - (void)setSecureTextEntry:(BOOL)secureTextEntry
  82. {
  83. if (self.secureTextEntry == secureTextEntry) {
  84. return;
  85. }
  86. [super setSecureTextEntry:secureTextEntry];
  87. // Fix for trailing whitespate issue
  88. // Read more:
  89. // https://stackoverflow.com/questions/14220187/uitextfield-has-trailing-whitespace-after-securetextentry-toggle/22537788#22537788
  90. NSAttributedString *originalText = [self.attributedText copy];
  91. self.attributedText = [NSAttributedString new];
  92. self.attributedText = originalText;
  93. }
  94. #pragma mark - Placeholder
  95. - (NSDictionary<NSAttributedStringKey, id> *)_placeholderTextAttributes
  96. {
  97. NSMutableDictionary<NSAttributedStringKey, id> *textAttributes = [_defaultTextAttributes mutableCopy] ?: [NSMutableDictionary new];
  98. if (self.placeholderColor) {
  99. [textAttributes setValue:self.placeholderColor forKey:NSForegroundColorAttributeName];
  100. } else {
  101. [textAttributes removeObjectForKey:NSForegroundColorAttributeName];
  102. }
  103. return textAttributes;
  104. }
  105. #pragma mark - Context Menu
  106. - (BOOL)canPerformAction:(SEL)action withSender:(id)sender
  107. {
  108. if (_contextMenuHidden) {
  109. return NO;
  110. }
  111. return [super canPerformAction:action withSender:sender];
  112. }
  113. #pragma mark - Caret Manipulation
  114. - (CGRect)caretRectForPosition:(UITextPosition *)position
  115. {
  116. if (_caretHidden) {
  117. return CGRectZero;
  118. }
  119. return [super caretRectForPosition:position];
  120. }
  121. #pragma mark - Positioning Overrides
  122. - (CGRect)textRectForBounds:(CGRect)bounds
  123. {
  124. return UIEdgeInsetsInsetRect([super textRectForBounds:bounds], _textContainerInset);
  125. }
  126. - (CGRect)editingRectForBounds:(CGRect)bounds
  127. {
  128. return [self textRectForBounds:bounds];
  129. }
  130. #pragma mark - Overrides
  131. #pragma clang diagnostic push
  132. #pragma clang diagnostic ignored "-Wdeprecated-implementations"
  133. // Overrides selectedTextRange setter to get notify when selectedTextRange changed.
  134. - (void)setSelectedTextRange:(UITextRange *)selectedTextRange
  135. {
  136. [super setSelectedTextRange:selectedTextRange];
  137. [_textInputDelegateAdapter selectedTextRangeWasSet];
  138. }
  139. #pragma clang diagnostic pop
  140. - (void)setSelectedTextRange:(UITextRange *)selectedTextRange notifyDelegate:(BOOL)notifyDelegate
  141. {
  142. if (!notifyDelegate) {
  143. // We have to notify an adapter that following selection change was initiated programmatically,
  144. // so the adapter must not generate a notification for it.
  145. [_textInputDelegateAdapter skipNextTextInputDidChangeSelectionEventWithTextRange:selectedTextRange];
  146. }
  147. [super setSelectedTextRange:selectedTextRange];
  148. }
  149. - (void)paste:(id)sender
  150. {
  151. [super paste:sender];
  152. _textWasPasted = YES;
  153. }
  154. #pragma mark - Layout
  155. - (CGSize)contentSize
  156. {
  157. // Returning size DOES contain `textContainerInset` (aka `padding`).
  158. return self.intrinsicContentSize;
  159. }
  160. - (CGSize)intrinsicContentSize
  161. {
  162. // Note: `placeholder` defines intrinsic size for `<TextInput>`.
  163. NSString *text = self.placeholder ?: @"";
  164. CGSize size = [text sizeWithAttributes:[self _placeholderTextAttributes]];
  165. size = CGSizeMake(RCTCeilPixelValue(size.width), RCTCeilPixelValue(size.height));
  166. size.width += _textContainerInset.left + _textContainerInset.right;
  167. size.height += _textContainerInset.top + _textContainerInset.bottom;
  168. // Returning size DOES contain `textContainerInset` (aka `padding`).
  169. return size;
  170. }
  171. - (CGSize)sizeThatFits:(CGSize)size
  172. {
  173. // All size values here contain `textContainerInset` (aka `padding`).
  174. CGSize intrinsicSize = self.intrinsicContentSize;
  175. return CGSizeMake(MIN(size.width, intrinsicSize.width), MIN(size.height, intrinsicSize.height));
  176. }
  177. @end