Skip to content

Commit

Permalink
[RN][macos] Fix disable scrolling for multiline text input
Browse files Browse the repository at this point in the history
Summary:
# Context

RN iOS supports disabling scrolling via `scrollEnabled` prop for multiline text inputs.
https://reactnative.dev/docs/0.68/textinput#scrollenabled-ios

This does [not work](microsoft#925) on MacOS

Since MacOS does not use `UITextView` which inherits from `UIScrollView`, the view manager property was set but not actually passed down to the underlying scroll view.

`RCTMultilineTextInputView` creates a `RCTUIScrollView` which is where we need to disable scrolling.
https://www.internalfb.com/code/archon_react_native_macos/[fde4113acd89fb13ee11636c48b59eac49c21bae]/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputView.m?lines=31

`RCTUIScrollView` inherits from `NSScrollView` which does not have a `scrollEnabled` property, but there is an api we can use to disable scrolling when the property is set.

https://developer.apple.com/documentation/appkit/nsscrollview/1403494-scrollwheel

# Usage

NOTE: Only works with `multiline={true}`

```
<TextInput multiline={true} scrollEnabled={false} ... />
```

# Change

* Only expose the `scrollEnabled` property on `RCTMultilineTextInputView`.
  * `RCTSinglelineTextInputView` does not have scrolling so this property is unused.
* `RCTMultilineTextInputView` delegates the `scrollEnabled` to it's underlying `_scrollView`
* `RCTUIScrollView` defaults initial `scrollEnabled` to `YES`
* `RCTUIScrollView` disables scrolling when the `scrollEnabled` is `NO`

Test Plan:
## Zeratul

```
MDSTextInput
        {...textInputProps}
        {...(IS_MAC
          ? {multiline: true, blurOnSubmit: true, scrollEnabled: false}
          : {})} // workaround for T110399243
```

|before|after|
| https://pxl.cl/27gQp|https://pxl.cl/27gQr|

## RN Tester

|before|after|
|https://pxl.cl/27gRd|https://pxl.cl/27gRh|

Reviewers: lyahdav, ericroz, ackchiu, #seller_expansion

Reviewed By: lyahdav

Differential Revision: https://phabricator.intern.facebook.com/D37659010

Tasks: T120776572

Tags: marketplace, marketplace_seller_expansion

# Conflicts:
#	Libraries/Text/TextInput/Multiline/RCTMultilineTextInputView.m
#	Libraries/Text/TextInput/Multiline/RCTMultilineTextInputViewManager.m
#	Libraries/Text/TextInput/RCTBaseTextInputViewManager.m

# Conflicts:
#	Libraries/Text/TextInput/Multiline/RCTMultilineTextInputView.m
#	React/Base/RCTUIKit.h
#	React/Base/macOS/RCTUIKit.m

# Conflicts:
#	Libraries/Text/TextInput/RCTBaseTextInputViewManager.m

# Conflicts:
#	Libraries/Text/TextInput/Multiline/RCTMultilineTextInputView.h
#	Libraries/Text/TextInput/Multiline/RCTMultilineTextInputView.m
  • Loading branch information
Shawn Dempsey committed Nov 21, 2022
1 parent 99b53c5 commit 84fc10c
Show file tree
Hide file tree
Showing 9 changed files with 70 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ NS_ASSUME_NONNULL_BEGIN
@interface RCTMultilineTextInputView : RCTBaseTextInputView

#if TARGET_OS_OSX // [TODO(macOS GH#774)
@property (nonatomic, assign) BOOL scrollEnabled;
- (void)setReadablePasteBoardTypes:(NSArray<NSPasteboardType> *)readablePasteboardTypes;
#endif // ]TODO(macOS GH#774)
@end
Expand Down
23 changes: 20 additions & 3 deletions Libraries/Text/TextInput/Multiline/RCTMultilineTextInputView.m
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ @implementation RCTMultilineTextInputView
{
#if TARGET_OS_OSX // [TODO(macOS GH#774)
RCTUIScrollView *_scrollView;
RCTClipView *_clipView;
#endif // ]TODO(macOS GH#774)
RCTUITextView *_backedTextInputView;
}
Expand All @@ -36,6 +37,9 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge
_scrollView.hasVerticalRuler = NO;
_scrollView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;

_clipView = [[RCTClipView alloc] initWithFrame:_scrollView.frame];
[_scrollView setContentView:_clipView];

_backedTextInputView.verticallyResizable = YES;
_backedTextInputView.horizontallyResizable = YES;
_backedTextInputView.textContainer.containerSize = NSMakeSize(CGFLOAT_MAX, CGFLOAT_MAX);
Expand Down Expand Up @@ -99,6 +103,22 @@ - (void)setEnableFocusRing:(BOOL)enableFocusRing {
}
}

- (void)setScrollEnabled:(BOOL)scrollEnabled
{
if (scrollEnabled) {
_scrollView.scrollEnabled = YES;
[_clipView setConstrainScrolling:NO];
} else {
_scrollView.scrollEnabled = NO;
[_clipView setConstrainScrolling:YES];
}
}

- (BOOL)scrollEnabled
{
return _scrollView.isScrollEnabled;
}

- (void)setReadablePasteBoardTypes:(NSArray<NSPasteboardType> *)readablePasteboardTypes {
[_backedTextInputView setReadablePasteBoardTypes:readablePasteboardTypes];
}
Expand All @@ -109,9 +129,6 @@ - (void)setReadablePasteBoardTypes:(NSArray<NSPasteboardType> *)readablePasteboa
- (void)scrollViewDidScroll:(RCTUIScrollView *)scrollView // TODO(macOS ISS#3536887)
{
RCTDirectEventBlock onScroll = self.onScroll;
#if TARGET_OS_OSX // [TODO(macOS GH#774)
[_scrollView setHasVerticalScroller:YES];
#endif // ]TODO(macOS GH#774)
if (onScroll) {
CGPoint contentOffset = scrollView.contentOffset;
CGSize contentSize = scrollView.contentSize;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ - (RCTUIView *)view // TODO(macOS ISS#3536887)

RCT_REMAP_NOT_OSX_VIEW_PROPERTY(dataDetectorTypes, backedTextInputView.dataDetectorTypes, UIDataDetectorTypes) // TODO(macOS GH#774)
RCT_REMAP_OSX_VIEW_PROPERTY(dataDetectorTypes, backedTextInputView.enabledTextCheckingTypes, NSTextCheckingTypes) // TODO(macOS GH#774)
RCT_EXPORT_VIEW_PROPERTY(scrollEnabled, BOOL)

#if TARGET_OS_OSX // [TODO(macOS GH#774)
RCT_CUSTOM_VIEW_PROPERTY(pastedTypes, NSArray<NSPasteboardType>*, RCTUITextView)
Expand Down
2 changes: 1 addition & 1 deletion Libraries/Text/TextInput/Multiline/RCTUITextView.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, strong, nullable) NSString *inputAccessoryViewID;

#if TARGET_OS_OSX // [TODO(macOS GH#774)
@property (nonatomic, assign) BOOL scrollEnabled;
@property (nonatomic, getter=isScrollEnabled) BOOL scrollEnabled;
@property (nonatomic, strong, nullable) RCTUIColor *selectionColor; // TODO(OSS Candidate ISS#2710739)
@property (nonatomic, assign) UIEdgeInsets textContainerInsets;
@property (nonatomic, copy) NSString *text;
Expand Down
1 change: 0 additions & 1 deletion Libraries/Text/TextInput/RCTBaseTextInputViewManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ @implementation RCTBaseTextInputViewManager
RCT_REMAP_OSX_VIEW_PROPERTY(spellCheck, backedTextInputView.continuousSpellCheckingEnabled, BOOL) // TODO(macOS GH#774)
RCT_REMAP_NOT_OSX_VIEW_PROPERTY(caretHidden, backedTextInputView.caretHidden, BOOL) // TODO(macOS GH#774)
RCT_REMAP_NOT_OSX_VIEW_PROPERTY(clearButtonMode, backedTextInputView.clearButtonMode, UITextFieldViewMode) // TODO(macOS GH#774)
RCT_REMAP_VIEW_PROPERTY(scrollEnabled, backedTextInputView.scrollEnabled, BOOL)
RCT_REMAP_NOT_OSX_VIEW_PROPERTY(secureTextEntry, backedTextInputView.secureTextEntry, BOOL) // TODO(macOS GH#774)
RCT_EXPORT_VIEW_PROPERTY(autoFocus, BOOL)
RCT_EXPORT_VIEW_PROPERTY(blurOnSubmit, BOOL)
Expand Down
8 changes: 8 additions & 0 deletions React/Base/RCTUIKit.h
Original file line number Diff line number Diff line change
Expand Up @@ -427,9 +427,17 @@ CGPathRef UIBezierPathCreateCGPathRef(UIBezierPath *path);
@property (nonatomic, assign) BOOL alwaysBounceVertical;
// macOS specific properties
@property (nonatomic, assign) BOOL enableFocusRing;
@property (nonatomic, assign, getter=isScrollEnabled) BOOL scrollEnabled;

@end

@interface RCTClipView : NSClipView // [TODO(macOS GH#774)

@property (nonatomic, assign) BOOL constrainScrolling;

@end // ]TODO(macOS GH#774)


NS_INLINE RCTPlatformView *RCTUIViewHitTestWithEvent(RCTPlatformView *view, CGPoint point, __unused UIEvent *event)
{
return [view hitTest:point];
Expand Down
32 changes: 32 additions & 0 deletions React/Base/macOS/RCTUIKit.m
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,15 @@ - (void)setEnableFocusRing:(BOOL)enableFocusRing {
}
}

- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
self.scrollEnabled = YES;
}

return self;
}

// UIScrollView properties missing from NSScrollView
- (CGPoint)contentOffset
{
Expand Down Expand Up @@ -557,3 +566,26 @@ BOOL RCTUIViewSetClipsToBounds(RCTPlatformView *view)

return clipsToBounds;
}

@implementation RCTClipView

- (instancetype)initWithFrame:(NSRect)frameRect
{
if (self = [super initWithFrame:frameRect]) {
self.constrainScrolling = NO;
self.drawsBackground = NO;
}

return self;
}

- (NSRect)constrainBoundsRect:(NSRect)proposedBounds
{
if (self.constrainScrolling) {
return NSMakeRect(0, 0, 0, 0);
}

return [super constrainBoundsRect:proposedBounds];
}

@end
3 changes: 1 addition & 2 deletions React/Views/ScrollView/RCTScrollView.m
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ @interface RCTCustomScrollView :
#else // [TODO(macOS GH#774)
+ (BOOL)isCompatibleWithResponsiveScrolling;
@property (nonatomic, assign, getter=isInverted) BOOL inverted;
@property (nonatomic, assign, getter=isScrollEnabled) BOOL scrollEnabled;
@property (nonatomic, strong) NSPanGestureRecognizer *panGestureRecognizer;
#endif // ]TODO(macOS GH#774)
@end
Expand Down Expand Up @@ -116,7 +115,7 @@ - (BOOL)isFlipped

- (void)scrollWheel:(NSEvent *)theEvent
{
if (!self.scrollEnabled) {
if (!self.isScrollEnabled) {
[[self nextResponder] scrollWheel:theEvent];
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,12 @@ exports.examples = ([
multiline={true}
style={styles.multiline}
/>
<TextInput
placeholder="multiline text input with scroll disabled"
multiline={true}
scrollEnabled={false}
style={styles.multiline}
/>
<TextInput
defaultValue="uneditable multiline text input with phone number detection: 88888888."
editable={false}
Expand Down

0 comments on commit 84fc10c

Please sign in to comment.