Skip to content

Commit 26c68ad

Browse files
Saadnajmiclaude
andauthored
fix: replace RCTUISecureTextField #include hack with proper subclass (#2895)
## Summary - Replaces the fragile preprocessor `#include .mm` trick in `RCTUISecureTextField` with proper OOP inheritance - `RCTUISecureTextField` now subclasses `RCTUITextField` directly and overrides `+cellClass` to return `RCTUISecureTextFieldCell : NSSecureTextFieldCell` - Removes all `RCT_SUBCLASS_SECURETEXTFIELD` preprocessor conditionals from `RCTUITextField.h/.mm` - Updates `isKindOfClass:[NSSecureTextField class]` checks to `[RCTUISecureTextField class]` in both Paper and Fabric code paths ## Motivation The old approach `#include`d the entire 734-line `RCTUITextField.mm` with a `#define` to swap class/superclass names — a preprocessor trick to work around a diamond inheritance problem. This caused duplicate-symbol linker errors on non-macOS SPM builds and was fragile and confusing. Since `NSSecureTextField`'s masking behavior lives entirely in `NSSecureTextFieldCell`, the proper fix is to subclass `RCTUITextField` and override just the cell class (~50 lines of duplicated cell code vs. 734 lines recompiled via include). ## Test plan - [ ] macOS build succeeds (RCTUISecureTextField inherits all behavior from RCTUITextField) - [ ] Secure text fields still mask input on macOS (masking comes from NSSecureTextFieldCell) - [ ] iOS/visionOS SPM builds pass without duplicate symbols or excludes - [ ] No regressions in text input behavior 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 3dc3479 commit 26c68ad

File tree

8 files changed

+98
-45
lines changed

8 files changed

+98
-45
lines changed

packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.mm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
#import <React/RCTBackedTextInputDelegateAdapter.h>
99
#import "RCTBackedTextInputViewProtocol.h" // [macOS
1010
#import "RCTBackedTextInputDelegate.h"
11-
#import "../RCTTextUIKit.h" // macOS]
11+
#import <React/RCTTextUIKit.h> // macOS]
1212

1313
#pragma mark - RCTBackedTextFieldDelegateAdapter (for UITextField)
1414

packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,11 @@
2121
#import <React/RCTTextAttributes.h>
2222
#import <React/RCTTextSelection.h>
2323
#import <React/RCTUITextView.h> // [macOS]
24-
#import "../RCTTextUIKit.h" // [macOS]
24+
#import <React/RCTTextUIKit.h> // [macOS]
2525
#import <React/RCTHandledKey.h> // [macOS]
26+
#if TARGET_OS_OSX // [macOS
27+
#import <React/RCTUISecureTextField.h>
28+
#endif // macOS]
2629

2730
/** Native iOS text field bottom keyboard offset amount */
2831
static const CGFloat kSingleLineKeyboardBottomOffset = 15.0;
@@ -194,7 +197,7 @@ - (BOOL)textOf:(NSAttributedString *)newText equals:(NSAttributedString *)oldTex
194197
BOOL shouldFallbackToBareTextComparison =
195198
// There are multiple Korean input sources (2-Set, 3-Set, etc). Check substring instead instead
196199
[[[self.backedTextInputView inputContext] selectedKeyboardInputSource] containsString:@"com.apple.inputmethod.Korean"] ||
197-
[self.backedTextInputView hasMarkedText] || [self.backedTextInputView isKindOfClass:[NSSecureTextField class]] ||
200+
[self.backedTextInputView hasMarkedText] || [self.backedTextInputView isKindOfClass:[RCTUISecureTextField class]] ||
198201
#endif // macOS]
199202
fontHasBeenUpdatedBySystem;
200203

packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.h

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,7 @@ NS_ASSUME_NONNULL_BEGIN
2020
#if !TARGET_OS_OSX // [macOS]
2121
@interface RCTUITextField : UITextField <RCTBackedTextInputViewProtocol>
2222
#else // [macOS
23-
#if RCT_SUBCLASS_SECURETEXTFIELD
24-
@interface RCTUISecureTextField : NSSecureTextField <RCTBackedTextInputViewProtocol>
25-
#else
2623
@interface RCTUITextField : NSTextField <RCTBackedTextInputViewProtocol>
27-
#endif // RCT_SUBCLASS_SECURETEXTFIELD
2824
#endif // macOS]
2925

3026

packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.mm

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,7 @@
1717

1818
#if TARGET_OS_OSX // [macOS
1919

20-
#if RCT_SUBCLASS_SECURETEXTFIELD
21-
#define RCTUITextFieldCell RCTUISecureTextFieldCell
22-
@interface RCTUISecureTextFieldCell : NSSecureTextFieldCell
23-
#else
2420
@interface RCTUITextFieldCell : NSTextFieldCell
25-
#endif
2621

2722
@property (nonatomic, assign) UIEdgeInsets textContainerInset;
2823
@property (nonatomic, getter=isAutomaticTextReplacementEnabled) BOOL automaticTextReplacementEnabled;
@@ -36,11 +31,6 @@ @interface RCTUITextFieldCell : NSTextFieldCell
3631

3732
@implementation RCTUITextFieldCell
3833

39-
- (void)setTextContainerInset:(UIEdgeInsets)textContainerInset
40-
{
41-
_textContainerInset = textContainerInset;
42-
}
43-
4434
- (NSRect)titleRectForBounds:(NSRect)rect
4535
{
4636
return UIEdgeInsetsInsetRect([super titleRectForBounds:rect], self.textContainerInset);
@@ -58,12 +48,9 @@ - (void)selectWithFrame:(NSRect)rect inView:(NSView *)controlView editor:(NSText
5848

5949
- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
6050
{
61-
if (self.drawsBackground) {
62-
if (self.backgroundColor && self.backgroundColor.alphaComponent > 0) {
63-
64-
[self.backgroundColor set];
65-
NSRectFill(cellFrame);
66-
}
51+
if (self.drawsBackground && self.backgroundColor && self.backgroundColor.alphaComponent > 0) {
52+
[self.backgroundColor set];
53+
NSRectFill(cellFrame);
6754
}
6855

6956
[super drawInteriorWithFrame:[self titleRectForBounds:cellFrame] inView:controlView];
@@ -86,11 +73,7 @@ - (NSText *)setUpFieldEditorAttributes:(NSText *)textObj
8673
@end
8774
#endif // macOS]
8875

89-
#ifdef RCT_SUBCLASS_SECURETEXTFIELD
90-
@implementation RCTUISecureTextField {
91-
#else
9276
@implementation RCTUITextField {
93-
#endif
9477
RCTBackedTextFieldDelegateAdapter *_textInputDelegateAdapter;
9578
NSDictionary<NSAttributedStringKey, id> *_defaultTextAttributes;
9679
#if !TARGET_OS_OSX // [macOS]

packages/react-native/Libraries/Text/TextInput/Singleline/macOS/RCTUISecureTextField.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
// [macOS]
99

1010
#if TARGET_OS_OSX
11-
#define RCT_SUBCLASS_SECURETEXTFIELD 1
12-
#endif
1311

14-
#include <React/RCTUITextField.h>
12+
#import <React/RCTUITextField.h>
13+
14+
@interface RCTUISecureTextField : RCTUITextField
15+
@end
1516

17+
#endif

packages/react-native/Libraries/Text/TextInput/Singleline/macOS/RCTUISecureTextField.m

Lines changed: 0 additions & 14 deletions
This file was deleted.
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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+
8+
// [macOS]
9+
10+
#if TARGET_OS_OSX
11+
12+
#import <React/RCTUISecureTextField.h>
13+
#import <React/RCTUIKit.h>
14+
15+
#pragma mark - RCTUISecureTextFieldCell
16+
17+
@interface RCTUISecureTextFieldCell : NSSecureTextFieldCell
18+
19+
@property (nonatomic, assign) UIEdgeInsets textContainerInset;
20+
@property (nonatomic, getter=isAutomaticTextReplacementEnabled) BOOL automaticTextReplacementEnabled;
21+
@property (nonatomic, getter=isAutomaticSpellingCorrectionEnabled) BOOL automaticSpellingCorrectionEnabled;
22+
@property (nonatomic, getter=isContinuousSpellCheckingEnabled) BOOL continuousSpellCheckingEnabled;
23+
@property (nonatomic, getter=isGrammarCheckingEnabled) BOOL grammarCheckingEnabled;
24+
@property (nonatomic, strong, nullable) RCTPlatformColor *selectionColor;
25+
@property (nonatomic, strong, nullable) RCTPlatformColor *insertionPointColor;
26+
27+
@end
28+
29+
@implementation RCTUISecureTextFieldCell
30+
31+
- (NSRect)titleRectForBounds:(NSRect)rect
32+
{
33+
return UIEdgeInsetsInsetRect([super titleRectForBounds:rect], self.textContainerInset);
34+
}
35+
36+
- (void)editWithFrame:(NSRect)rect inView:(NSView *)controlView editor:(NSText *)textObj delegate:(id)delegate event:(NSEvent *)event
37+
{
38+
[super editWithFrame:[self titleRectForBounds:rect] inView:controlView editor:textObj delegate:delegate event:event];
39+
}
40+
41+
- (void)selectWithFrame:(NSRect)rect inView:(NSView *)controlView editor:(NSText *)textObj delegate:(id)delegate start:(NSInteger)selStart length:(NSInteger)selLength
42+
{
43+
[super selectWithFrame:[self titleRectForBounds:rect] inView:controlView editor:textObj delegate:delegate start:selStart length:selLength];
44+
}
45+
46+
- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
47+
{
48+
if (self.drawsBackground && self.backgroundColor && self.backgroundColor.alphaComponent > 0) {
49+
[self.backgroundColor set];
50+
NSRectFill(cellFrame);
51+
}
52+
53+
[super drawInteriorWithFrame:[self titleRectForBounds:cellFrame] inView:controlView];
54+
}
55+
56+
- (NSText *)setUpFieldEditorAttributes:(NSText *)textObj
57+
{
58+
NSTextView *fieldEditor = (NSTextView *)[super setUpFieldEditorAttributes:textObj];
59+
fieldEditor.automaticSpellingCorrectionEnabled = self.isAutomaticSpellingCorrectionEnabled;
60+
fieldEditor.automaticTextReplacementEnabled = self.isAutomaticTextReplacementEnabled;
61+
fieldEditor.continuousSpellCheckingEnabled = self.isContinuousSpellCheckingEnabled;
62+
fieldEditor.grammarCheckingEnabled = self.isGrammarCheckingEnabled;
63+
NSMutableDictionary *selectTextAttributes = fieldEditor.selectedTextAttributes.mutableCopy;
64+
selectTextAttributes[NSBackgroundColorAttributeName] = self.selectionColor ?: [NSColor selectedControlColor];
65+
fieldEditor.selectedTextAttributes = selectTextAttributes;
66+
fieldEditor.insertionPointColor = self.insertionPointColor ?: [NSColor textColor];
67+
return fieldEditor;
68+
}
69+
70+
@end
71+
72+
#pragma mark - RCTUISecureTextField
73+
74+
@implementation RCTUISecureTextField
75+
76+
+ (Class)cellClass
77+
{
78+
return RCTUISecureTextFieldCell.class;
79+
}
80+
81+
@end
82+
83+
#endif // TARGET_OS_OSX

packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1219,7 +1219,7 @@ - (BOOL)_textOf:(NSAttributedString *)newText equals:(NSAttributedString *)oldTe
12191219
// There are multiple Korean input sources (2-Set, 3-Set, etc). Check substring instead instead
12201220
[[[_backedTextInputView inputContext] selectedKeyboardInputSource] containsString:@"com.apple.inputmethod.Korean"] ||
12211221
[_backedTextInputView hasMarkedText] ||
1222-
[_backedTextInputView isKindOfClass:[NSSecureTextField class]] ||
1222+
[_backedTextInputView isKindOfClass:[RCTUISecureTextField class]] ||
12231223
#endif // macOS]
12241224
fontHasBeenUpdatedBySystem;
12251225

0 commit comments

Comments
 (0)