From faf24838c83c69c1cbe89633cdf5c425be683375 Mon Sep 17 00:00:00 2001 From: wwwcg Date: Mon, 29 Jul 2024 17:05:14 +0800 Subject: [PATCH] feat(ios): refreshWrapper support viewPager --- .../refreshview/HippyRefreshWrapper.h | 19 +++ .../refreshview/HippyRefreshWrapper.m | 114 ++++++++++++------ .../refreshview/HippyRefreshWrapperItemView.m | 8 +- .../HippyRefreshWrapperViewManager.m | 6 +- ios/sdk/component/viewPager/HippyViewPager.h | 2 +- ios/sdk/component/viewPager/HippyViewPager.m | 11 +- 6 files changed, 113 insertions(+), 47 deletions(-) diff --git a/ios/sdk/component/refreshview/HippyRefreshWrapper.h b/ios/sdk/component/refreshview/HippyRefreshWrapper.h index 16ee607e5d1..85d745756e0 100644 --- a/ios/sdk/component/refreshview/HippyRefreshWrapper.h +++ b/ios/sdk/component/refreshview/HippyRefreshWrapper.h @@ -21,12 +21,31 @@ */ #import +#import "HippyComponent.h" #import "HippyInvalidating.h" + NS_ASSUME_NONNULL_BEGIN + @class HippyBridge; + +/// RefreshWrapper add refresh capability to scrollable components such as ListView @interface HippyRefreshWrapper : UIView + +/// Direction of Refresh +@property (nonatomic, assign, getter=isHorizontal) BOOL horizontal; + +/// Bounce time of refresh start/end animation +@property (nonatomic, assign) CGFloat bounceTime; + +/// The onRefresh block that JS side binding. +@property (nonatomic, copy) HippyDirectEventBlock onRefresh; + +/// Call to indicate refresh completion. - (void)refreshCompleted; + +/// Call to start the refresh process. - (void)startRefresh; + @end NS_ASSUME_NONNULL_END diff --git a/ios/sdk/component/refreshview/HippyRefreshWrapper.m b/ios/sdk/component/refreshview/HippyRefreshWrapper.m index 7898cb28d4e..dcbee8378f9 100644 --- a/ios/sdk/component/refreshview/HippyRefreshWrapper.m +++ b/ios/sdk/component/refreshview/HippyRefreshWrapper.m @@ -24,14 +24,21 @@ #import "UIView+Hippy.h" #import "HippyRefreshWrapperItemView.h" #import "HippyScrollableProtocol.h" + + +static NSTimeInterval const kHippyDefaultRefreshBounceTime = 400.0; + @interface HippyRefreshWrapper () + +/// The child view of RefreshWrapper @property (nonatomic, weak) HippyRefreshWrapperItemView *wrapperItemView; +/// Scrollable target @property (nonatomic, weak) id scrollableView; -@property (nonatomic, copy) HippyDirectEventBlock onRefresh; -@property (nonatomic, assign) CGFloat bounceTime; -@property (nonatomic, weak) HippyBridge *bridge; + @end + @implementation HippyRefreshWrapper + - (void)addSubview:(UIView *)view { if (view != _wrapperItemView) { [super addSubview:view]; @@ -39,60 +46,95 @@ - (void)addSubview:(UIView *)view { [self refactorViews]; } +- (void)insertHippySubview:(UIView *)view atIndex:(NSInteger)index { + if ([view isKindOfClass:[HippyRefreshWrapperItemView class]]) { + _wrapperItemView = (HippyRefreshWrapperItemView *)view; + } else if ([view conformsToProtocol:@protocol(HippyScrollableProtocol)]) { + _scrollableView = (id)view; + [_scrollableView addScrollListener:self]; + } + [super insertHippySubview:view atIndex:index]; +} + +- (void)invalidate { + [_scrollableView removeScrollListener:self]; +} + + +#pragma mark - Public & Private Methods + - (void)refactorViews { if (_wrapperItemView && _scrollableView) { CGSize size = _wrapperItemView.frame.size; - _wrapperItemView.frame = CGRectMake(0, -size.height, size.width, size.height); + if (self.isHorizontal) { + _wrapperItemView.frame = CGRectMake(-size.width, 0, size.width, size.height); + } else { + _wrapperItemView.frame = CGRectMake(0, -size.height, size.width, size.height); + } [_scrollableView.realScrollView addSubview:_wrapperItemView]; } } - (void)refreshCompleted { - CGFloat duration = _bounceTime != 0 ? _bounceTime : 400; - UIEdgeInsets contentInset = self->_scrollableView.realScrollView.contentInset; - contentInset.top = 0; - [UIView animateWithDuration:duration / 1000.f animations:^{ - [self->_scrollableView.realScrollView setContentInset:contentInset]; + CGFloat duration = _bounceTime != 0 ? _bounceTime : kHippyDefaultRefreshBounceTime; + UIEdgeInsets contentInset = self.scrollableView.realScrollView.contentInset; + if (self.isHorizontal) { + contentInset.left = 0; + } else { + contentInset.top = 0; + } + [UIView animateWithDuration:duration / 1000.0 animations:^{ + [self.scrollableView.realScrollView setContentInset:contentInset]; }]; } - (void)startRefresh { - CGFloat wrapperItemViewHeight = _wrapperItemView.frame.size.height; UIEdgeInsets insets = _scrollableView.realScrollView.contentInset; - insets.top = wrapperItemViewHeight; - CGFloat duration = _bounceTime != 0 ? _bounceTime : 400; - [UIView animateWithDuration:duration / 1000.f animations:^{ - [self->_scrollableView.realScrollView setContentInset:insets]; - [self->_scrollableView.realScrollView setContentOffset:CGPointMake(0, -insets.top)]; + CGPoint targetContentOffset; + if (self.isHorizontal) { + CGFloat wrapperItemViewWidth = CGRectGetWidth(_wrapperItemView.frame); + insets.left = wrapperItemViewWidth; + targetContentOffset = CGPointMake(-wrapperItemViewWidth, 0); + } else { + CGFloat wrapperItemViewHeight = CGRectGetHeight(_wrapperItemView.frame); + insets.top = wrapperItemViewHeight; + targetContentOffset = CGPointMake(0, -wrapperItemViewHeight); + } + + CGFloat duration = _bounceTime > DBL_EPSILON ? _bounceTime : kHippyDefaultRefreshBounceTime; + [UIView animateWithDuration:duration / 1000.0 animations:^{ + [self.scrollableView.realScrollView setContentInset:insets]; + [self.scrollableView.realScrollView setContentOffset:targetContentOffset]; }]; if (_onRefresh) { _onRefresh(@{}); } } -- (void)insertHippySubview:(UIView *)view atIndex:(NSInteger)index { - if ([view isKindOfClass:[HippyRefreshWrapperItemView class]]) { - _wrapperItemView = (HippyRefreshWrapperItemView *)view; - } else if ([view conformsToProtocol:@protocol(HippyScrollableProtocol)]) { - _scrollableView = (id)view; - [_scrollableView addScrollListener:self]; - } - [super insertHippySubview:view atIndex:index]; -} - -- (void)invalidate { - [_scrollableView removeScrollListener:self]; -} +#pragma mark - ScrollListener, UIScrollViewDelegate -- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { - CGFloat wrapperItemViewHeight = _wrapperItemView.frame.size.height; +- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView + withVelocity:(CGPoint)velocity + targetContentOffset:(inout CGPoint *)targetContentOffset { UIEdgeInsets insets = scrollView.contentInset; - CGFloat contentOffsetY = scrollView.contentOffset.y; - if (contentOffsetY <= -wrapperItemViewHeight && insets.top != wrapperItemViewHeight) { - insets.top = wrapperItemViewHeight; - scrollView.contentInset = insets; - if (_onRefresh) { - _onRefresh(@{}); + if (self.isHorizontal) { + CGFloat wrapperItemViewWidth = CGRectGetWidth(_wrapperItemView.frame); + CGFloat contentOffsetX = scrollView.contentOffset.x; + if (contentOffsetX <= -wrapperItemViewWidth && insets.left != wrapperItemViewWidth) { + // Update the end sliding state of scrollview + targetContentOffset->x = -wrapperItemViewWidth; + // start refresh and call js + [self startRefresh]; + } + } else { + CGFloat wrapperItemViewHeight = CGRectGetHeight(_wrapperItemView.frame); + CGFloat contentOffsetY = scrollView.contentOffset.y; + if (contentOffsetY <= -wrapperItemViewHeight && insets.top != wrapperItemViewHeight) { + insets.top = wrapperItemViewHeight; + scrollView.contentInset = insets; + if (_onRefresh) { + _onRefresh(@{}); + } } } } diff --git a/ios/sdk/component/refreshview/HippyRefreshWrapperItemView.m b/ios/sdk/component/refreshview/HippyRefreshWrapperItemView.m index 6cd43a5bd3c..03be58b1de5 100644 --- a/ios/sdk/component/refreshview/HippyRefreshWrapperItemView.m +++ b/ios/sdk/component/refreshview/HippyRefreshWrapperItemView.m @@ -21,13 +21,7 @@ */ #import "HippyRefreshWrapperItemView.h" #import "UIView+Hippy.h" -@implementation HippyRefreshWrapperItemView -- (void)setFrame:(CGRect)frame { - if ([self.superview isKindOfClass:[UIScrollView class]]) { - frame.origin.y = -frame.size.height; - } - [super setFrame:frame]; -} +@implementation HippyRefreshWrapperItemView @end diff --git a/ios/sdk/component/refreshview/HippyRefreshWrapperViewManager.m b/ios/sdk/component/refreshview/HippyRefreshWrapperViewManager.m index ac793e9efac..42deb5c3d92 100644 --- a/ios/sdk/component/refreshview/HippyRefreshWrapperViewManager.m +++ b/ios/sdk/component/refreshview/HippyRefreshWrapperViewManager.m @@ -27,11 +27,13 @@ @implementation HippyRefreshWrapperViewManager HIPPY_EXPORT_MODULE(RefreshWrapper) +HIPPY_EXPORT_VIEW_PROPERTY(horizontal, BOOL) +HIPPY_EXPORT_VIEW_PROPERTY(bounceTime, CGFloat) HIPPY_EXPORT_VIEW_PROPERTY(onRefresh, HippyDirectEventBlock) -HIPPY_EXPORT_VIEW_PROPERTY(bounceTime, CGFloat) - (UIView *)view { - return [HippyRefreshWrapper new]; + HippyRefreshWrapper *refreshWrapper = [HippyRefreshWrapper new]; + return refreshWrapper; } HIPPY_EXPORT_METHOD(refreshComplected:(NSNumber *__nonnull)hippyTag args:(id)arg) { diff --git a/ios/sdk/component/viewPager/HippyViewPager.h b/ios/sdk/component/viewPager/HippyViewPager.h index f4f934c1210..cbd253ea637 100644 --- a/ios/sdk/component/viewPager/HippyViewPager.h +++ b/ios/sdk/component/viewPager/HippyViewPager.h @@ -31,7 +31,7 @@ */ typedef void (^ViewPagerItemsCountChanged)(NSUInteger count); -@interface HippyViewPager : UIScrollView +@interface HippyViewPager : UIScrollView @property (nonatomic, strong) HippyDirectEventBlock onPageSelected; @property (nonatomic, strong) HippyDirectEventBlock onPageScroll; @property (nonatomic, strong) HippyDirectEventBlock onPageScrollStateChanged; diff --git a/ios/sdk/component/viewPager/HippyViewPager.m b/ios/sdk/component/viewPager/HippyViewPager.m index 644412ecd11..f7ca0660416 100644 --- a/ios/sdk/component/viewPager/HippyViewPager.m +++ b/ios/sdk/component/viewPager/HippyViewPager.m @@ -325,7 +325,16 @@ - (void)scrollViewDidEndScrolling { self.previousStopOffset = [self contentOffset].x; } -#pragma mark scrollview listener methods +#pragma mark - scrollview listener methods + +- (UIScrollView *)realScrollView { + return self; +} + +- (NSHashTable *)scrollListeners { + return _scrollViewListener; +} + - (void)addScrollListener:(id)scrollListener { [_scrollViewListener addObject:scrollListener]; }