Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,6 +17,8 @@ @implementation RCTUITextField {
NSArray<UIBarButtonItemGroup *> *_initialValueLeadingBarButtonGroups;
NSArray<UIBarButtonItemGroup *> *_initialValueTrailingBarButtonGroups;
NSArray<NSString *> *_acceptDragAndDropTypes;
// Keep the requested tint while a transparent tint hides the caret.
UIColor *_requestedTintColor;
}

// This should not be needed but internal build were failing without it.
Expand DownExpand Up@@ -210,13 +212,31 @@ - (void)removeDictationResultPlaceholder:(id)placeholder willInsertResult:(BOOL)

#pragma mark - Caret Manipulation

- (CGRect)caretRectForPosition:(UITextPosition *)position
// Returning CGRectZero for a hidden caret causes oversized Metal surface errors on iOS
// 17. A transparent tint hides the caret without changing its bounds. Restore the tint
// for non-empty selections to preserve the selection highlight and handles.
- (void)setCaretHidden:(BOOL)caretHidden
{
if (_caretHidden) {
return CGRectZero;
if (_caretHidden == caretHidden) {
return;
}

return [super caretRectForPosition:position];
_caretHidden = caretHidden;
[self _updateAppliedTintColor];
}

// Do not override the getter. UIKit reads the applied caret color through `-tintColor`.
- (void)setTintColor:(UIColor *)tintColor
{
_requestedTintColor = tintColor;
[self _updateAppliedTintColor];
}

- (void)_updateAppliedTintColor
{
UITextRange *selectedTextRange = self.selectedTextRange;
BOOL shouldHideCaret = _caretHidden && (selectedTextRange == nil || selectedTextRange.isEmpty);
[super setTintColor:shouldHideCaret ? [UIColor clearColor] : _requestedTintColor];
}

#pragma mark - Positioning Overrides
Expand All@@ -239,6 +259,7 @@ - (CGRect)editingRectForBounds:(CGRect)bounds
- (void)setSelectedTextRange:(UITextRange *)selectedTextRange
{
[super setSelectedTextRange:selectedTextRange];
[self _updateAppliedTintColor];
[_textInputDelegateAdapter selectedTextRangeWasSet];
}
#pragma clang diagnostic pop
Expand All@@ -252,6 +273,7 @@ - (void)setSelectedTextRange:(UITextRange *)selectedTextRange notifyDelegate:(BO
}

[super setSelectedTextRange:selectedTextRange];
[self _updateAppliedTintColor];
}

- (void)scrollRangeToVisible:(NSRange)range
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -849,6 +849,10 @@ - (void)_setMultiline:(BOOL)multiline
UIView<RCTBackedTextInputViewProtocol> *backedTextInputView = multiline ? [RCTUITextView new] : [RCTUITextField new];
backedTextInputView.frame = _backedTextInputView.frame;
RCTCopyBackedTextInput(_backedTextInputView, backedTextInputView);
// The copied tint can be transparent when the source hides its caret. Restore the
// selection color from props after changing the input type.
backedTextInputView.tintColor =
RCTUIColorFromSharedColor(static_cast<const TextInputProps &>(*_props).selectionColor);
_backedTextInputView = backedTextInputView;
[self addSubview:_backedTextInputView];
}
Expand Down
65 changes: 65 additions & 0 deletions packages/react-native/React/Tests/Text/RCTUITextFieldTests.mm
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#import <React/RCTUITextField.h>
#import <XCTest/XCTest.h>

@interface RCTUITextFieldTests : XCTestCase
@end

@implementation RCTUITextFieldTests

- (void)testCaretHiddenMakesTheCaretTransparent
{
RCTUITextField *textField = [RCTUITextField new];
textField.tintColor = UIColor.redColor;

textField.caretHidden = YES;

XCTAssertEqualObjects(textField.tintColor, UIColor.clearColor);
}

- (void)testClearingCaretHiddenRestoresTheSelectionColor
{
RCTUITextField *textField = [RCTUITextField new];
textField.tintColor = UIColor.redColor;
textField.caretHidden = YES;

textField.caretHidden = NO;

XCTAssertEqualObjects(textField.tintColor, UIColor.redColor);
}

- (void)testSelectionColorSetWhileCaretHiddenIsAppliedOnceTheCaretIsShown
{
RCTUITextField *textField = [RCTUITextField new];
textField.caretHidden = YES;

textField.tintColor = UIColor.redColor;

XCTAssertEqualObjects(textField.tintColor, UIColor.clearColor);

textField.caretHidden = NO;

XCTAssertEqualObjects(textField.tintColor, UIColor.redColor);
}

- (void)testSelectionColorIsAppliedToANonEmptySelection
{
RCTUITextField *textField = [RCTUITextField new];
textField.attributedText = [[NSAttributedString alloc] initWithString:@"Hello"];
textField.tintColor = UIColor.redColor;
textField.caretHidden = YES;

UITextPosition *start = textField.beginningOfDocument;
UITextPosition *end = [textField positionFromPosition:start offset:textField.attributedText.length];
[textField setSelectedTextRange:[textField textRangeFromPosition:start toPosition:end] notifyDelegate:NO];

XCTAssertEqualObjects(textField.tintColor, UIColor.redColor);
}

@end
Loading