mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Summary:
This pull request aims to remove iOS 11 availability check which is no longer needed.
The minimum iOS deployment target for React Native is iOS 11 but we still have iOS 11 version check like below.
```
if (available(iOS 11.0, *)) {
```
This is a continuation pull request of https://github.com/facebook/react-native/pull/32151
## Changelog
<!-- Help reviewers and the release process by writing your own changelog entry. For an example, see:
https://github.com/facebook/react-native/wiki/Changelog
-->
[iOS] [Changed] - Remove iOS 11 availability check
Pull Request resolved: https://github.com/facebook/react-native/pull/32488
Reviewed By: yungsters
Differential Revision: D32006312
Pulled By: ryancat
fbshipit-source-id: 0ee6579e433a15d3d220a52d2ccd6931b0513971
268 lines
9.2 KiB
Plaintext
268 lines
9.2 KiB
Plaintext
/*
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
#import "RCTEnhancedScrollView.h"
|
|
#import <React/RCTUtils.h>
|
|
|
|
@interface RCTEnhancedScrollView () <UIScrollViewDelegate>
|
|
@end
|
|
|
|
@implementation RCTEnhancedScrollView {
|
|
__weak id<UIScrollViewDelegate> _publicDelegate;
|
|
BOOL _isSetContentOffsetDisabled;
|
|
}
|
|
|
|
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
|
|
{
|
|
if ([key isEqualToString:@"delegate"]) {
|
|
// For `delegate` property, we issue KVO notifications manually.
|
|
// We need that to block notifications caused by setting the original `UIScrollView`s property.
|
|
return NO;
|
|
}
|
|
|
|
return [super automaticallyNotifiesObserversForKey:key];
|
|
}
|
|
|
|
- (instancetype)initWithFrame:(CGRect)frame
|
|
{
|
|
if (self = [super initWithFrame:frame]) {
|
|
// We set the default behavior to "never" so that iOS
|
|
// doesn't do weird things to UIScrollView insets automatically
|
|
// and keeps it as an opt-in behavior.
|
|
self.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
|
|
|
|
// We intentionally force `UIScrollView`s `semanticContentAttribute` to `LTR` here
|
|
// because this attribute affects a position of vertical scrollbar; we don't want this
|
|
// scrollbar flip because we also flip it with whole `UIScrollView` flip.
|
|
self.semanticContentAttribute = UISemanticContentAttributeForceLeftToRight;
|
|
|
|
__weak __typeof(self) weakSelf = self;
|
|
_delegateSplitter = [[RCTGenericDelegateSplitter alloc] initWithDelegateUpdateBlock:^(id delegate) {
|
|
[weakSelf setPrivateDelegate:delegate];
|
|
}];
|
|
[_delegateSplitter addDelegate:self];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)preserveContentOffsetWithBlock:(void (^)())block
|
|
{
|
|
if (!block) {
|
|
return;
|
|
}
|
|
|
|
_isSetContentOffsetDisabled = YES;
|
|
block();
|
|
_isSetContentOffsetDisabled = NO;
|
|
}
|
|
|
|
/*
|
|
* Automatically centers the content such that if the content is smaller than the
|
|
* ScrollView, we force it to be centered, but when you zoom or the content otherwise
|
|
* becomes larger than the ScrollView, there is no padding around the content but it
|
|
* can still fill the whole view.
|
|
*/
|
|
- (void)setContentOffset:(CGPoint)contentOffset
|
|
{
|
|
if (_isSetContentOffsetDisabled) {
|
|
return;
|
|
}
|
|
|
|
if (_centerContent && !CGSizeEqualToSize(self.contentSize, CGSizeZero)) {
|
|
CGSize scrollViewSize = self.bounds.size;
|
|
if (self.contentSize.width <= scrollViewSize.width) {
|
|
contentOffset.x = -(scrollViewSize.width - self.contentSize.width) / 2.0;
|
|
}
|
|
if (self.contentSize.height <= scrollViewSize.height) {
|
|
contentOffset.y = -(scrollViewSize.height - self.contentSize.height) / 2.0;
|
|
}
|
|
}
|
|
|
|
super.contentOffset = CGPointMake(
|
|
RCTSanitizeNaNValue(contentOffset.x, @"scrollView.contentOffset.x"),
|
|
RCTSanitizeNaNValue(contentOffset.y, @"scrollView.contentOffset.y"));
|
|
}
|
|
|
|
- (BOOL)touchesShouldCancelInContentView:(UIView *)view
|
|
{
|
|
if ([_overridingDelegate respondsToSelector:@selector(touchesShouldCancelInContentView:)]) {
|
|
return [_overridingDelegate touchesShouldCancelInContentView:view];
|
|
}
|
|
|
|
return [super touchesShouldCancelInContentView:view];
|
|
}
|
|
|
|
#pragma mark - RCTGenericDelegateSplitter
|
|
|
|
- (void)setPrivateDelegate:(id<UIScrollViewDelegate>)delegate
|
|
{
|
|
[super setDelegate:delegate];
|
|
}
|
|
|
|
- (id<UIScrollViewDelegate>)delegate
|
|
{
|
|
return _publicDelegate;
|
|
}
|
|
|
|
- (void)setDelegate:(id<UIScrollViewDelegate>)delegate
|
|
{
|
|
if (_publicDelegate == delegate) {
|
|
return;
|
|
}
|
|
|
|
if (_publicDelegate) {
|
|
[_delegateSplitter removeDelegate:_publicDelegate];
|
|
}
|
|
|
|
[self willChangeValueForKey:@"delegate"];
|
|
_publicDelegate = delegate;
|
|
[self didChangeValueForKey:@"delegate"];
|
|
|
|
if (_publicDelegate) {
|
|
[_delegateSplitter addDelegate:_publicDelegate];
|
|
}
|
|
}
|
|
|
|
#pragma mark - UIScrollViewDelegate
|
|
|
|
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView
|
|
withVelocity:(CGPoint)velocity
|
|
targetContentOffset:(inout CGPoint *)targetContentOffset
|
|
{
|
|
if (self.snapToOffsets && self.snapToOffsets.count > 0) {
|
|
// An alternative to enablePaging and snapToInterval which allows setting custom
|
|
// stopping points that don't have to be the same distance apart. Often seen in
|
|
// apps which feature horizonally scrolling items. snapToInterval does not enforce
|
|
// scrolling one interval at a time but guarantees that the scroll will stop at
|
|
// a snap offset point.
|
|
|
|
// Find which axis to snap
|
|
BOOL isHorizontal = [self isHorizontal:scrollView];
|
|
CGFloat velocityAlongAxis = isHorizontal ? velocity.x : velocity.y;
|
|
CGFloat offsetAlongAxis = isHorizontal ? scrollView.contentOffset.x : scrollView.contentOffset.y;
|
|
|
|
// Calculate maximum content offset
|
|
CGSize viewportSize = self.bounds.size;
|
|
CGFloat maximumOffset = isHorizontal ? MAX(0, scrollView.contentSize.width - viewportSize.width)
|
|
: MAX(0, scrollView.contentSize.height - viewportSize.height);
|
|
|
|
// Calculate the snap offsets adjacent to the initial offset target
|
|
CGFloat targetOffset = isHorizontal ? targetContentOffset->x : targetContentOffset->y;
|
|
CGFloat smallerOffset = 0.0;
|
|
CGFloat largerOffset = maximumOffset;
|
|
|
|
for (unsigned long i = 0; i < self.snapToOffsets.count; i++) {
|
|
CGFloat offset = [[self.snapToOffsets objectAtIndex:i] floatValue];
|
|
|
|
if (offset <= targetOffset) {
|
|
if (targetOffset - offset < targetOffset - smallerOffset) {
|
|
smallerOffset = offset;
|
|
}
|
|
}
|
|
|
|
if (offset >= targetOffset) {
|
|
if (offset - targetOffset < largerOffset - targetOffset) {
|
|
largerOffset = offset;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Calculate the nearest offset
|
|
CGFloat nearestOffset = targetOffset - smallerOffset < largerOffset - targetOffset ? smallerOffset : largerOffset;
|
|
|
|
CGFloat firstOffset = [[self.snapToOffsets firstObject] floatValue];
|
|
CGFloat lastOffset = [[self.snapToOffsets lastObject] floatValue];
|
|
|
|
// if scrolling after the last snap offset and snapping to the
|
|
// end of the list is disabled, then we allow free scrolling
|
|
if (!self.snapToEnd && targetOffset >= lastOffset) {
|
|
if (offsetAlongAxis >= lastOffset) {
|
|
// free scrolling
|
|
} else {
|
|
// snap to end
|
|
targetOffset = lastOffset;
|
|
}
|
|
} else if (!self.snapToStart && targetOffset <= firstOffset) {
|
|
if (offsetAlongAxis <= firstOffset) {
|
|
// free scrolling
|
|
} else {
|
|
// snap to beginning
|
|
targetOffset = firstOffset;
|
|
}
|
|
} else if (velocityAlongAxis > 0.0) {
|
|
targetOffset = largerOffset;
|
|
} else if (velocityAlongAxis < 0.0) {
|
|
targetOffset = smallerOffset;
|
|
} else {
|
|
targetOffset = nearestOffset;
|
|
}
|
|
|
|
// Make sure the new offset isn't out of bounds
|
|
targetOffset = MIN(MAX(0, targetOffset), maximumOffset);
|
|
|
|
// Set new targetContentOffset
|
|
if (isHorizontal) {
|
|
targetContentOffset->x = targetOffset;
|
|
} else {
|
|
targetContentOffset->y = targetOffset;
|
|
}
|
|
} else if (self.snapToInterval) {
|
|
// An alternative to enablePaging which allows setting custom stopping intervals,
|
|
// smaller than a full page size. Often seen in apps which feature horizonally
|
|
// scrolling items. snapToInterval does not enforce scrolling one interval at a time
|
|
// but guarantees that the scroll will stop at an interval point.
|
|
CGFloat snapToIntervalF = (CGFloat)self.snapToInterval;
|
|
|
|
// Find which axis to snap
|
|
BOOL isHorizontal = [self isHorizontal:scrollView];
|
|
|
|
// What is the current offset?
|
|
CGFloat velocityAlongAxis = isHorizontal ? velocity.x : velocity.y;
|
|
CGFloat targetContentOffsetAlongAxis = targetContentOffset->y;
|
|
if (isHorizontal) {
|
|
// Use current scroll offset to determine the next index to snap to when momentum disabled
|
|
targetContentOffsetAlongAxis = self.disableIntervalMomentum ? scrollView.contentOffset.x : targetContentOffset->x;
|
|
} else {
|
|
targetContentOffsetAlongAxis = self.disableIntervalMomentum ? scrollView.contentOffset.y : targetContentOffset->y;
|
|
}
|
|
|
|
// Offset based on desired alignment
|
|
CGFloat frameLength = isHorizontal ? self.frame.size.width : self.frame.size.height;
|
|
CGFloat alignmentOffset = 0.0f;
|
|
if ([self.snapToAlignment isEqualToString:@"center"]) {
|
|
alignmentOffset = (frameLength * 0.5f) + (snapToIntervalF * 0.5f);
|
|
} else if ([self.snapToAlignment isEqualToString:@"end"]) {
|
|
alignmentOffset = frameLength;
|
|
}
|
|
|
|
// Pick snap point based on direction and proximity
|
|
CGFloat fractionalIndex = (targetContentOffsetAlongAxis + alignmentOffset) / snapToIntervalF;
|
|
|
|
NSInteger snapIndex = velocityAlongAxis > 0.0 ? ceil(fractionalIndex)
|
|
: velocityAlongAxis < 0.0 ? floor(fractionalIndex)
|
|
: round(fractionalIndex);
|
|
CGFloat newTargetContentOffset = (snapIndex * snapToIntervalF) - alignmentOffset;
|
|
|
|
// Set new targetContentOffset
|
|
if (isHorizontal) {
|
|
targetContentOffset->x = newTargetContentOffset;
|
|
} else {
|
|
targetContentOffset->y = newTargetContentOffset;
|
|
}
|
|
}
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
- (BOOL)isHorizontal:(UIScrollView *)scrollView
|
|
{
|
|
return scrollView.contentSize.width > self.frame.size.width;
|
|
}
|
|
|
|
@end
|