2015-03-23 20:28:42 +00:00
|
|
|
/**
|
|
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
|
|
|
*
|
2018-02-17 02:24:55 +00:00
|
|
|
* This source code is licensed under the MIT license found in the
|
|
|
|
* LICENSE file in the root directory of this source tree.
|
2015-03-23 20:28:42 +00:00
|
|
|
*/
|
2015-02-20 04:10:52 +00:00
|
|
|
|
|
|
|
#import "RCTScrollViewManager.h"
|
|
|
|
|
2015-03-06 00:36:41 +00:00
|
|
|
#import "RCTBridge.h"
|
2015-02-20 04:10:52 +00:00
|
|
|
#import "RCTScrollView.h"
|
2016-09-26 13:11:49 +00:00
|
|
|
#import "RCTShadowView.h"
|
2015-03-11 02:03:59 +00:00
|
|
|
#import "RCTUIManager.h"
|
2015-02-20 04:10:52 +00:00
|
|
|
|
2015-07-07 14:38:24 +00:00
|
|
|
@interface RCTScrollView (Private)
|
2015-08-24 10:14:33 +00:00
|
|
|
|
2015-11-03 22:45:46 +00:00
|
|
|
- (NSArray<NSDictionary *> *)calculateChildFramesData;
|
2015-08-24 10:14:33 +00:00
|
|
|
|
2015-07-07 14:38:24 +00:00
|
|
|
@end
|
|
|
|
|
2015-06-05 15:46:17 +00:00
|
|
|
@implementation RCTConvert (UIScrollView)
|
|
|
|
|
|
|
|
RCT_ENUM_CONVERTER(UIScrollViewKeyboardDismissMode, (@{
|
|
|
|
@"none": @(UIScrollViewKeyboardDismissModeNone),
|
|
|
|
@"on-drag": @(UIScrollViewKeyboardDismissModeOnDrag),
|
|
|
|
@"interactive": @(UIScrollViewKeyboardDismissModeInteractive),
|
|
|
|
// Backwards compatibility
|
|
|
|
@"onDrag": @(UIScrollViewKeyboardDismissModeOnDrag),
|
|
|
|
}), UIScrollViewKeyboardDismissModeNone, integerValue)
|
|
|
|
|
2016-01-27 18:16:10 +00:00
|
|
|
RCT_ENUM_CONVERTER(UIScrollViewIndicatorStyle, (@{
|
|
|
|
@"default": @(UIScrollViewIndicatorStyleDefault),
|
|
|
|
@"black": @(UIScrollViewIndicatorStyleBlack),
|
|
|
|
@"white": @(UIScrollViewIndicatorStyleWhite),
|
|
|
|
}), UIScrollViewIndicatorStyleDefault, integerValue)
|
|
|
|
|
Add 'contentInsetAdjustmentBehavior' (new in iOS 11) to ScrollView
Summary:
In iOS11, Apple added a new layout feature called "Safe Areas" (this blog post talks a bit about it: https://www.bignerdranch.com/blog/wwdc-2017-large-titles-and-safe-area-layout-guides/).
UIScrollView is one component that is affected by this change in Apple's API. When the `contentInsetAdjustmentBehavior` is set to `automatic`, for example, it will adjust the insets (and override any manually set insets) automatically based on whether or not there's a UINavigationBar, a UITabBar, a visible status bar, etc on the screen. Frustratingly, Apple decided to default to `Automatic` for this behavior, which will cause any apps that set contentInset/contentContainerStyle padding to have their values offset by, at the very least, the size of the status bar, when they compile their app for iOS 11. Here's more information about this behavior: https://developer.apple.com/documentation/uikit/uiscrollview/2902261-contentinsetadjustmentbehavior?language=objc
Mostly, this is a really straightforward change -- it simply adds a new iOS-only prop to ScrollView that allows setting `contentInsetAdjustmentBehavior`. But I did decide to default the behavior to `never`, so that it mimics the behavior we've seen in iOS < 11. I think it's good to keep something as crucial as scrollview content insets non-magical, and also keep it behaving similarly between platforms.
Closes https://github.com/facebook/react-native/pull/15023
Reviewed By: javache
Differential Revision: D5517552
Pulled By: hramos
fbshipit-source-id: c9ce4bf331b3d243228268d826fdd4dcee99981d
2017-07-31 19:10:11 +00:00
|
|
|
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */
|
|
|
|
RCT_ENUM_CONVERTER(UIScrollViewContentInsetAdjustmentBehavior, (@{
|
|
|
|
@"automatic": @(UIScrollViewContentInsetAdjustmentAutomatic),
|
|
|
|
@"scrollableAxes": @(UIScrollViewContentInsetAdjustmentScrollableAxes),
|
|
|
|
@"never": @(UIScrollViewContentInsetAdjustmentNever),
|
|
|
|
@"always": @(UIScrollViewContentInsetAdjustmentAlways),
|
|
|
|
}), UIScrollViewContentInsetAdjustmentNever, integerValue)
|
|
|
|
#endif
|
|
|
|
|
2015-06-05 15:46:17 +00:00
|
|
|
@end
|
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
@implementation RCTScrollViewManager
|
|
|
|
|
2015-04-08 12:42:43 +00:00
|
|
|
RCT_EXPORT_MODULE()
|
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
- (UIView *)view
|
|
|
|
{
|
2015-03-06 00:36:41 +00:00
|
|
|
return [[RCTScrollView alloc] initWithEventDispatcher:self.bridge.eventDispatcher];
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
|
2015-03-26 04:29:28 +00:00
|
|
|
RCT_EXPORT_VIEW_PROPERTY(alwaysBounceHorizontal, BOOL)
|
|
|
|
RCT_EXPORT_VIEW_PROPERTY(alwaysBounceVertical, BOOL)
|
|
|
|
RCT_EXPORT_VIEW_PROPERTY(bounces, BOOL)
|
|
|
|
RCT_EXPORT_VIEW_PROPERTY(bouncesZoom, BOOL)
|
|
|
|
RCT_EXPORT_VIEW_PROPERTY(canCancelContentTouches, BOOL)
|
|
|
|
RCT_EXPORT_VIEW_PROPERTY(centerContent, BOOL)
|
2018-01-18 21:51:43 +00:00
|
|
|
RCT_EXPORT_VIEW_PROPERTY(maintainVisibleContentPosition, NSDictionary)
|
2015-03-26 04:29:28 +00:00
|
|
|
RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustContentInsets, BOOL)
|
|
|
|
RCT_EXPORT_VIEW_PROPERTY(decelerationRate, CGFloat)
|
|
|
|
RCT_EXPORT_VIEW_PROPERTY(directionalLockEnabled, BOOL)
|
2016-01-27 18:16:10 +00:00
|
|
|
RCT_EXPORT_VIEW_PROPERTY(indicatorStyle, UIScrollViewIndicatorStyle)
|
2015-03-26 04:29:28 +00:00
|
|
|
RCT_EXPORT_VIEW_PROPERTY(keyboardDismissMode, UIScrollViewKeyboardDismissMode)
|
|
|
|
RCT_EXPORT_VIEW_PROPERTY(maximumZoomScale, CGFloat)
|
|
|
|
RCT_EXPORT_VIEW_PROPERTY(minimumZoomScale, CGFloat)
|
|
|
|
RCT_EXPORT_VIEW_PROPERTY(scrollEnabled, BOOL)
|
2016-10-05 14:15:25 +00:00
|
|
|
#if !TARGET_OS_TV
|
|
|
|
RCT_EXPORT_VIEW_PROPERTY(pagingEnabled, BOOL)
|
2017-08-07 06:44:37 +00:00
|
|
|
RCT_REMAP_VIEW_PROPERTY(pinchGestureEnabled, scrollView.pinchGestureEnabled, BOOL)
|
2015-03-26 04:29:28 +00:00
|
|
|
RCT_EXPORT_VIEW_PROPERTY(scrollsToTop, BOOL)
|
2016-10-05 14:15:25 +00:00
|
|
|
#endif
|
2015-03-26 04:29:28 +00:00
|
|
|
RCT_EXPORT_VIEW_PROPERTY(showsHorizontalScrollIndicator, BOOL)
|
|
|
|
RCT_EXPORT_VIEW_PROPERTY(showsVerticalScrollIndicator, BOOL)
|
2015-03-30 11:58:02 +00:00
|
|
|
RCT_EXPORT_VIEW_PROPERTY(scrollEventThrottle, NSTimeInterval)
|
|
|
|
RCT_EXPORT_VIEW_PROPERTY(zoomScale, CGFloat)
|
|
|
|
RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets)
|
|
|
|
RCT_EXPORT_VIEW_PROPERTY(scrollIndicatorInsets, UIEdgeInsets)
|
2015-09-23 18:43:57 +00:00
|
|
|
RCT_EXPORT_VIEW_PROPERTY(snapToInterval, int)
|
|
|
|
RCT_EXPORT_VIEW_PROPERTY(snapToAlignment, NSString)
|
2015-03-30 11:58:02 +00:00
|
|
|
RCT_REMAP_VIEW_PROPERTY(contentOffset, scrollView.contentOffset, CGPoint)
|
2016-04-28 14:43:24 +00:00
|
|
|
RCT_EXPORT_VIEW_PROPERTY(onScrollBeginDrag, RCTDirectEventBlock)
|
|
|
|
RCT_EXPORT_VIEW_PROPERTY(onScroll, RCTDirectEventBlock)
|
|
|
|
RCT_EXPORT_VIEW_PROPERTY(onScrollEndDrag, RCTDirectEventBlock)
|
|
|
|
RCT_EXPORT_VIEW_PROPERTY(onMomentumScrollBegin, RCTDirectEventBlock)
|
|
|
|
RCT_EXPORT_VIEW_PROPERTY(onMomentumScrollEnd, RCTDirectEventBlock)
|
2017-06-08 18:49:39 +00:00
|
|
|
RCT_EXPORT_VIEW_PROPERTY(DEPRECATED_sendUpdatedChildFrames, BOOL)
|
Add 'contentInsetAdjustmentBehavior' (new in iOS 11) to ScrollView
Summary:
In iOS11, Apple added a new layout feature called "Safe Areas" (this blog post talks a bit about it: https://www.bignerdranch.com/blog/wwdc-2017-large-titles-and-safe-area-layout-guides/).
UIScrollView is one component that is affected by this change in Apple's API. When the `contentInsetAdjustmentBehavior` is set to `automatic`, for example, it will adjust the insets (and override any manually set insets) automatically based on whether or not there's a UINavigationBar, a UITabBar, a visible status bar, etc on the screen. Frustratingly, Apple decided to default to `Automatic` for this behavior, which will cause any apps that set contentInset/contentContainerStyle padding to have their values offset by, at the very least, the size of the status bar, when they compile their app for iOS 11. Here's more information about this behavior: https://developer.apple.com/documentation/uikit/uiscrollview/2902261-contentinsetadjustmentbehavior?language=objc
Mostly, this is a really straightforward change -- it simply adds a new iOS-only prop to ScrollView that allows setting `contentInsetAdjustmentBehavior`. But I did decide to default the behavior to `never`, so that it mimics the behavior we've seen in iOS < 11. I think it's good to keep something as crucial as scrollview content insets non-magical, and also keep it behaving similarly between platforms.
Closes https://github.com/facebook/react-native/pull/15023
Reviewed By: javache
Differential Revision: D5517552
Pulled By: hramos
fbshipit-source-id: c9ce4bf331b3d243228268d826fdd4dcee99981d
2017-07-31 19:10:11 +00:00
|
|
|
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */
|
|
|
|
RCT_EXPORT_VIEW_PROPERTY(contentInsetAdjustmentBehavior, UIScrollViewContentInsetAdjustmentBehavior)
|
|
|
|
#endif
|
2015-03-30 11:58:02 +00:00
|
|
|
|
2016-11-22 16:55:11 +00:00
|
|
|
// overflow is used both in css-layout as well as by react-native. In css-layout
|
2016-09-26 13:11:49 +00:00
|
|
|
// we always want to treat overflow as scroll but depending on what the overflow
|
|
|
|
// is set to from js we want to clip drawing or not. This piece of code ensures
|
|
|
|
// that css-layout is always treating the contents of a scroll container as
|
|
|
|
// overflow: 'scroll'.
|
2016-12-02 13:47:43 +00:00
|
|
|
RCT_CUSTOM_SHADOW_PROPERTY(overflow, YGOverflow, RCTShadowView) {
|
2017-01-10 03:23:44 +00:00
|
|
|
#pragma unused (json)
|
2016-12-02 13:47:43 +00:00
|
|
|
view.overflow = YGOverflowScroll;
|
2016-09-26 13:11:49 +00:00
|
|
|
}
|
|
|
|
|
2015-07-31 13:55:47 +00:00
|
|
|
RCT_EXPORT_METHOD(getContentSize:(nonnull NSNumber *)reactTag
|
2015-04-08 15:52:48 +00:00
|
|
|
callback:(RCTResponseSenderBlock)callback)
|
2015-03-11 02:03:59 +00:00
|
|
|
{
|
2016-01-14 15:41:24 +00:00
|
|
|
[self.bridge.uiManager addUIBlock:
|
|
|
|
^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTScrollView *> *viewRegistry) {
|
2015-03-11 02:03:59 +00:00
|
|
|
|
2015-11-03 22:45:46 +00:00
|
|
|
RCTScrollView *view = viewRegistry[reactTag];
|
|
|
|
if (!view || ![view isKindOfClass:[RCTScrollView class]]) {
|
|
|
|
RCTLogError(@"Cannot find RCTScrollView with tag #%@", reactTag);
|
2015-03-11 02:03:59 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-11-03 22:45:46 +00:00
|
|
|
CGSize size = view.scrollView.contentSize;
|
2015-03-11 02:03:59 +00:00
|
|
|
callback(@[@{
|
|
|
|
@"width" : @(size.width),
|
|
|
|
@"height" : @(size.height)
|
|
|
|
}]);
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
2015-07-31 13:55:47 +00:00
|
|
|
RCT_EXPORT_METHOD(calculateChildFrames:(nonnull NSNumber *)reactTag
|
2016-01-14 15:41:24 +00:00
|
|
|
callback:(RCTResponseSenderBlock)callback)
|
2015-07-07 14:38:24 +00:00
|
|
|
{
|
2016-01-14 15:41:24 +00:00
|
|
|
[self.bridge.uiManager addUIBlock:
|
|
|
|
^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTScrollView *> *viewRegistry) {
|
2015-07-07 14:38:24 +00:00
|
|
|
|
2015-11-03 22:45:46 +00:00
|
|
|
RCTScrollView *view = viewRegistry[reactTag];
|
|
|
|
if (!view || ![view isKindOfClass:[RCTScrollView class]]) {
|
|
|
|
RCTLogError(@"Cannot find RCTScrollView with tag #%@", reactTag);
|
2015-07-07 14:38:24 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-11-03 22:45:46 +00:00
|
|
|
NSArray<NSDictionary *> *childFrames = [view calculateChildFramesData];
|
2015-07-07 14:38:24 +00:00
|
|
|
if (childFrames) {
|
|
|
|
callback(@[childFrames]);
|
|
|
|
}
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
2016-01-14 15:41:24 +00:00
|
|
|
RCT_EXPORT_METHOD(scrollTo:(nonnull NSNumber *)reactTag
|
2016-02-01 19:04:43 +00:00
|
|
|
offsetX:(CGFloat)x
|
|
|
|
offsetY:(CGFloat)y
|
2016-01-14 15:41:24 +00:00
|
|
|
animated:(BOOL)animated)
|
2015-11-25 11:07:06 +00:00
|
|
|
{
|
2016-01-14 15:41:24 +00:00
|
|
|
[self.bridge.uiManager addUIBlock:
|
|
|
|
^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry){
|
2015-11-25 11:07:06 +00:00
|
|
|
UIView *view = viewRegistry[reactTag];
|
|
|
|
if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) {
|
2016-02-01 19:04:43 +00:00
|
|
|
[(id<RCTScrollableProtocol>)view scrollToOffset:(CGPoint){x, y} animated:animated];
|
2015-11-25 11:07:06 +00:00
|
|
|
} else {
|
2016-01-14 15:41:24 +00:00
|
|
|
RCTLogError(@"tried to scrollTo: on non-RCTScrollableProtocol view %@ "
|
|
|
|
"with tag #%@", view, reactTag);
|
2015-11-25 11:07:06 +00:00
|
|
|
}
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
2017-01-27 18:09:20 +00:00
|
|
|
RCT_EXPORT_METHOD(scrollToEnd:(nonnull NSNumber *)reactTag
|
|
|
|
animated:(BOOL)animated)
|
|
|
|
{
|
|
|
|
[self.bridge.uiManager addUIBlock:
|
|
|
|
^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry){
|
|
|
|
UIView *view = viewRegistry[reactTag];
|
|
|
|
if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) {
|
|
|
|
[(id<RCTScrollableProtocol>)view scrollToEnd:animated];
|
|
|
|
} else {
|
|
|
|
RCTLogError(@"tried to scrollTo: on non-RCTScrollableProtocol view %@ "
|
|
|
|
"with tag #%@", view, reactTag);
|
|
|
|
}
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
2016-01-14 15:41:24 +00:00
|
|
|
RCT_EXPORT_METHOD(zoomToRect:(nonnull NSNumber *)reactTag
|
|
|
|
withRect:(CGRect)rect
|
|
|
|
animated:(BOOL)animated)
|
2015-11-25 11:07:06 +00:00
|
|
|
{
|
2016-01-14 15:41:24 +00:00
|
|
|
[self.bridge.uiManager addUIBlock:
|
|
|
|
^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry){
|
2015-11-25 11:07:06 +00:00
|
|
|
UIView *view = viewRegistry[reactTag];
|
|
|
|
if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) {
|
2016-01-13 10:54:19 +00:00
|
|
|
[(id<RCTScrollableProtocol>)view zoomToRect:rect animated:animated];
|
2015-11-25 11:07:06 +00:00
|
|
|
} else {
|
2016-01-14 15:41:24 +00:00
|
|
|
RCTLogError(@"tried to zoomToRect: on non-RCTScrollableProtocol view %@ "
|
|
|
|
"with tag #%@", view, reactTag);
|
2015-11-25 11:07:06 +00:00
|
|
|
}
|
|
|
|
}];
|
|
|
|
}
|
2015-11-19 19:13:42 +00:00
|
|
|
|
2017-06-06 20:00:30 +00:00
|
|
|
RCT_EXPORT_METHOD(flashScrollIndicators:(nonnull NSNumber *)reactTag)
|
|
|
|
{
|
|
|
|
[self.bridge.uiManager addUIBlock:
|
|
|
|
^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTScrollView *> *viewRegistry){
|
|
|
|
|
|
|
|
RCTScrollView *view = viewRegistry[reactTag];
|
|
|
|
if (!view || ![view isKindOfClass:[RCTScrollView class]]) {
|
|
|
|
RCTLogError(@"Cannot find RCTScrollView with tag #%@", reactTag);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
[view.scrollView flashScrollIndicators];
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
@end
|