Skip to content

Commit 6daef14

Browse files
Nick LefeverSaadnajmi
authored andcommitted
[fabric] Add wrapper class for TextView with scroll callback support
Summary: The multiline text input view on macOS needs its own view hierarchy, wrapping the RCTUITextView in a scroll view to support all the features offered by the React TextInput component. This diff adds a wrapper class for RCTUITextView that provides the appropriate view hierarchy while still supporting the text input protocols required for text input. The wrapper forwards all unimplemented methods to the RCTUITextView so that it can be used as a direct substitute for the RCTUITextView. This allows us to reduce the custom changes need for macOS in RCTTextInputComponentView while re-using all the logic in RCTUITextView. Test Plan: Tested later in this stack. Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D51962394 Tasks: T167538822, T157889591 Tags: uikit-diff
1 parent 1ae9a89 commit 6daef14

File tree

2 files changed

+222
-0
lines changed

2 files changed

+222
-0
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and 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+
#if TARGET_OS_OSX // [macOS
9+
10+
#import <React/RCTUIKit.h>
11+
12+
#import "RCTTextUIKit.h"
13+
14+
#import <React/RCTBackedTextInputDelegate.h>
15+
#import <React/RCTBackedTextInputViewProtocol.h>
16+
17+
NS_ASSUME_NONNULL_BEGIN
18+
19+
@interface RCTWrappedTextView : RCTPlatformView <RCTBackedTextInputViewProtocol>
20+
21+
@property (nonatomic, weak) id<RCTBackedTextInputDelegate> textInputDelegate;
22+
@property (assign) BOOL hideVerticalScrollIndicator;
23+
24+
@end
25+
26+
NS_ASSUME_NONNULL_END
27+
28+
#endif // macOS]
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and 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+
#if TARGET_OS_OSX // [macOS
9+
10+
#import <React/RCTWrappedTextView.h>
11+
12+
#import <React/RCTUITextView.h>
13+
#import <React/RCTTextAttributes.h>
14+
15+
@implementation RCTWrappedTextView {
16+
RCTUITextView *_forwardingTextView;
17+
RCTUIScrollView *_scrollView;
18+
RCTClipView *_clipView;
19+
}
20+
21+
- (instancetype)initWithFrame:(CGRect)frame
22+
{
23+
if (self = [super initWithFrame:frame]) {
24+
self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
25+
26+
self.hideVerticalScrollIndicator = NO;
27+
28+
_scrollView = [[RCTUIScrollView alloc] initWithFrame:self.bounds];
29+
_scrollView.backgroundColor = [RCTUIColor clearColor];
30+
_scrollView.drawsBackground = NO;
31+
_scrollView.borderType = NSNoBorder;
32+
_scrollView.hasHorizontalRuler = NO;
33+
_scrollView.hasVerticalRuler = NO;
34+
_scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
35+
[_scrollView setHasVerticalScroller:YES];
36+
[_scrollView setHasHorizontalScroller:NO];
37+
38+
_clipView = [[RCTClipView alloc] initWithFrame:_scrollView.bounds];
39+
[_scrollView setContentView:_clipView];
40+
41+
_forwardingTextView = [[RCTUITextView alloc] initWithFrame:_scrollView.bounds];
42+
_forwardingTextView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
43+
_forwardingTextView.delegate = self;
44+
45+
_forwardingTextView.verticallyResizable = YES;
46+
_forwardingTextView.horizontallyResizable = YES;
47+
_forwardingTextView.textContainer.containerSize = NSMakeSize(FLT_MAX, FLT_MAX);
48+
_forwardingTextView.textContainer.widthTracksTextView = YES;
49+
_forwardingTextView.textInputDelegate = self;
50+
51+
_scrollView.documentView = _forwardingTextView;
52+
_scrollView.contentView.postsBoundsChangedNotifications = YES;
53+
54+
// Enable the focus ring by default
55+
_scrollView.enableFocusRing = YES;
56+
[self addSubview:_scrollView];
57+
58+
// a register for those notifications on the content view.
59+
[[NSNotificationCenter defaultCenter] addObserver:self
60+
selector:@selector(boundsDidChange:)
61+
name:NSViewBoundsDidChangeNotification
62+
object:_scrollView.contentView];
63+
}
64+
65+
return self;
66+
}
67+
68+
- (void)dealloc
69+
{
70+
[[NSNotificationCenter defaultCenter] removeObserver:self];
71+
}
72+
73+
- (BOOL)isFlipped
74+
{
75+
return YES;
76+
}
77+
78+
#pragma mark -
79+
#pragma mark Method forwarding to text view
80+
81+
- (void)forwardInvocation:(NSInvocation *)invocation
82+
{
83+
[invocation invokeWithTarget:_forwardingTextView];
84+
}
85+
86+
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
87+
{
88+
if ([_forwardingTextView respondsToSelector:selector]) {
89+
return [_forwardingTextView methodSignatureForSelector:selector];
90+
}
91+
92+
return [super methodSignatureForSelector:selector];
93+
}
94+
95+
- (void)boundsDidChange:(NSNotification *)notification
96+
{
97+
}
98+
99+
#pragma mark -
100+
#pragma mark First Responder forwarding
101+
102+
- (NSResponder *)responder
103+
{
104+
return _forwardingTextView;
105+
}
106+
107+
- (BOOL)acceptsFirstResponder
108+
{
109+
return _forwardingTextView.acceptsFirstResponder;
110+
}
111+
112+
- (BOOL)becomeFirstResponder
113+
{
114+
return [_forwardingTextView becomeFirstResponder];
115+
}
116+
117+
- (BOOL)resignFirstResponder
118+
{
119+
return [_forwardingTextView resignFirstResponder];
120+
}
121+
122+
#pragma mark -
123+
#pragma mark Text Input delegate forwarding
124+
125+
- (id<RCTBackedTextInputDelegate>)textInputDelegate
126+
{
127+
return _forwardingTextView.textInputDelegate;
128+
}
129+
130+
- (void)setTextInputDelegate:(id<RCTBackedTextInputDelegate>)textInputDelegate
131+
{
132+
_forwardingTextView.textInputDelegate = textInputDelegate;
133+
}
134+
135+
#pragma mark -
136+
#pragma mark Scrolling control
137+
138+
- (BOOL)scrollEnabled
139+
{
140+
return _scrollView.isScrollEnabled;
141+
}
142+
143+
- (void)setScrollEnabled:(BOOL)scrollEnabled
144+
{
145+
if (scrollEnabled) {
146+
_scrollView.scrollEnabled = YES;
147+
[_clipView setConstrainScrolling:NO];
148+
} else {
149+
_scrollView.scrollEnabled = NO;
150+
[_clipView setConstrainScrolling:YES];
151+
}
152+
}
153+
154+
- (BOOL)shouldShowVerticalScrollbar
155+
{
156+
// Hide vertical scrollbar if explicity set to NO
157+
if (self.hideVerticalScrollIndicator) {
158+
return NO;
159+
}
160+
161+
// Hide vertical scrollbar if attributed text overflows view
162+
CGSize textViewSize = [_forwardingTextView intrinsicContentSize];
163+
NSClipView *clipView = (NSClipView *)_scrollView.contentView;
164+
if (textViewSize.height > clipView.bounds.size.height) {
165+
return YES;
166+
};
167+
168+
return NO;
169+
}
170+
171+
- (void)textInputDidChange
172+
{
173+
[_scrollView setHasVerticalScroller:[self shouldShowVerticalScrollbar]];
174+
}
175+
176+
- (void)setAttributedText:(NSAttributedString *)attributedText
177+
{
178+
[_forwardingTextView setAttributedText:attributedText];
179+
[_scrollView setHasVerticalScroller:[self shouldShowVerticalScrollbar]];
180+
}
181+
182+
#pragma mark -
183+
#pragma mark Text Container Inset override for NSTextView
184+
185+
// This method is there to match the textContainerInset property on RCTUITextField
186+
- (void)setTextContainerInset:(UIEdgeInsets)textContainerInsets
187+
{
188+
// RCTUITextView has logic in setTextContainerInset[s] to convert th UIEdgeInsets to a valid NSSize struct
189+
_forwardingTextView.textContainerInsets = textContainerInsets;
190+
}
191+
192+
@end
193+
194+
#endif // macOS]

0 commit comments

Comments
 (0)