Skip to content

Commit

Permalink
feat(ios): add footer support to refreshWrapper
Browse files Browse the repository at this point in the history
  • Loading branch information
wwwcg committed Jul 31, 2024
1 parent 7a07b5a commit 3c6b696
Show file tree
Hide file tree
Showing 10 changed files with 264 additions and 33 deletions.
30 changes: 28 additions & 2 deletions ios/sdk/component/listview/HippyBaseListView.m
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,16 @@

@interface HippyBaseListView () <HippyScrollProtocol, HippyRefreshDelegate>

/// Scrollable's scroll event delegates
@property (nonatomic, strong) NSHashTable<id<UIScrollViewDelegate>> *scrollListeners;
/// Scrollable's layout event delegates
@property (nonatomic, strong) NSHashTable<id<HippyScrollableLayoutDelegate>> *layoutDelegates;

@end

@implementation HippyBaseListView {
__weak HippyBridge *_bridge;
__weak HippyRootView *_rootView;
NSHashTable *_scrollListeners;
BOOL _isInitialListReady;
NSUInteger _preNumberOfRows;
BOOL _allowNextScrollNoMatterWhat;
Expand All @@ -55,7 +59,6 @@ @implementation HippyBaseListView {
- (instancetype)initWithBridge:(HippyBridge *)bridge {
if (self = [super initWithFrame:CGRectZero]) {
_bridge = bridge;
_scrollListeners = [NSHashTable weakObjectsHashTable];
_dataSource = [HippyBaseListViewDataSource new];
_isInitialListReady = NO;
_preNumberOfRows = 0;
Expand Down Expand Up @@ -197,6 +200,9 @@ - (void)zoomToRect:(__unused CGRect)rect animated:(__unused BOOL)animated {
}

- (void)addScrollListener:(NSObject<UIScrollViewDelegate> *)scrollListener {
if (!self.scrollListeners) {
self.scrollListeners = [NSHashTable weakObjectsHashTable];
}
[_scrollListeners addObject:scrollListener];
}

Expand Down Expand Up @@ -231,6 +237,19 @@ - (void)scrollToIndex:(NSInteger)index animated:(BOOL)animated {
}
}

- (void)addHippyScrollableLayoutDelegate:(id<HippyScrollableLayoutDelegate>)delegate {
HippyAssertMainThread();
if (!self.layoutDelegates) {
self.layoutDelegates = [NSHashTable weakObjectsHashTable];
}
[self.layoutDelegates addObject:delegate];
}

- (void)removeHippyScrollableLayoutDelegate:(id<HippyScrollableLayoutDelegate>)delegate {
HippyAssertMainThread();
[self.layoutDelegates removeObject:delegate];
}

#pragma mark - Delegate & Datasource

- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
Expand Down Expand Up @@ -368,6 +387,13 @@ - (void)tableViewDidLayoutSubviews:(HippyListTableView *)tableView {
}
}
_previousVisibleCells = visibleCells;

// Notify delegates of HippyScrollableLayoutDelegate
for (id<HippyScrollableLayoutDelegate> layoutDelegate in self.layoutDelegates) {
if ([layoutDelegate respondsToSelector:@selector(scrollableDidLayout:)]) {
[layoutDelegate scrollableDidLayout:self];
}
}
}

#pragma mark - Scroll
Expand Down
9 changes: 9 additions & 0 deletions ios/sdk/component/refreshview/HippyRefreshWrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,21 @@ NS_ASSUME_NONNULL_BEGIN
/// The onRefresh block that JS side binding.
@property (nonatomic, copy) HippyDirectEventBlock onRefresh;

/// The footer onRefresh block that JS side binding.
@property (nonatomic, copy) HippyDirectEventBlock onFooterRefresh;

/// Call to indicate refresh completion.
- (void)refreshCompleted;

/// Call to indicate refresh footer completion.
- (void)refreshFooterCompleted;

/// Call to start the refresh process.
- (void)startRefresh;

/// Call to start the footer refresh process.
- (void)startRefreshFooter;

@end

NS_ASSUME_NONNULL_END
140 changes: 116 additions & 24 deletions ios/sdk/component/refreshview/HippyRefreshWrapper.m
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@

static NSTimeInterval const kHippyDefaultRefreshBounceTime = 400.0;

@interface HippyRefreshWrapper () <UIScrollViewDelegate>
@interface HippyRefreshWrapper () <UIScrollViewDelegate, HippyScrollableLayoutDelegate>

/// The child view of RefreshWrapper
@property (nonatomic, weak) HippyRefreshWrapperItemView *wrapperItemView;
/// The child header view of RefreshWrapper
@property (nonatomic, weak) HippyRefreshWrapperItemView *headerItemView;
/// The child footer view of RefreshWrapper
@property (nonatomic, weak) HippyRefreshWrapperFooterItemView *footerItemView;
/// Scrollable target
@property (nonatomic, weak) id<HippyScrollableProtocol> scrollableView;

Expand All @@ -40,18 +42,21 @@ @interface HippyRefreshWrapper () <UIScrollViewDelegate>
@implementation HippyRefreshWrapper

- (void)addSubview:(UIView *)view {
if (view != _wrapperItemView) {
if (view != _headerItemView && view != _footerItemView) {
[super addSubview:view];
}
[self refactorViews];
}

- (void)insertHippySubview:(UIView *)view atIndex:(NSInteger)index {
if ([view isKindOfClass:[HippyRefreshWrapperItemView class]]) {
_wrapperItemView = (HippyRefreshWrapperItemView *)view;
_headerItemView = (HippyRefreshWrapperItemView *)view;
} else if ([view conformsToProtocol:@protocol(HippyScrollableProtocol)]) {
_scrollableView = (id<HippyScrollableProtocol>)view;
[_scrollableView addScrollListener:self];
} else if ([view isKindOfClass:[HippyRefreshWrapperFooterItemView class]]) {
_footerItemView = (HippyRefreshWrapperFooterItemView *)view;
[_scrollableView addHippyScrollableLayoutDelegate:self];
}
[super insertHippySubview:view atIndex:index];
}
Expand All @@ -64,14 +69,14 @@ - (void)invalidate {
#pragma mark - Public & Private Methods

- (void)refactorViews {
if (_wrapperItemView && _scrollableView) {
CGSize size = _wrapperItemView.frame.size;
if (_headerItemView && _scrollableView) {
CGSize size = _headerItemView.frame.size;
if (self.isHorizontal) {
_wrapperItemView.frame = CGRectMake(-size.width, 0, size.width, size.height);
_headerItemView.frame = CGRectMake(-size.width, 0, size.width, size.height);
} else {
_wrapperItemView.frame = CGRectMake(0, -size.height, size.width, size.height);
_headerItemView.frame = CGRectMake(0, -size.height, size.width, size.height);
}
[_scrollableView.realScrollView addSubview:_wrapperItemView];
[_scrollableView.realScrollView addSubview:_headerItemView];
}
}

Expand All @@ -88,15 +93,28 @@ - (void)refreshCompleted {
}];
}

- (void)refreshFooterCompleted {
CGFloat duration = _bounceTime != 0 ? _bounceTime : kHippyDefaultRefreshBounceTime;
UIEdgeInsets contentInset = self.scrollableView.realScrollView.contentInset;
if (self.isHorizontal) {
contentInset.right = 0;
} else {
contentInset.bottom = 0;
}
[UIView animateWithDuration:duration / 1000.0 animations:^{
[self.scrollableView.realScrollView setContentInset:contentInset];
}];
}

- (void)startRefresh {
UIEdgeInsets insets = _scrollableView.realScrollView.contentInset;
CGPoint targetContentOffset;
if (self.isHorizontal) {
CGFloat wrapperItemViewWidth = CGRectGetWidth(_wrapperItemView.frame);
CGFloat wrapperItemViewWidth = CGRectGetWidth(_headerItemView.frame);
insets.left = wrapperItemViewWidth;
targetContentOffset = CGPointMake(-wrapperItemViewWidth, 0);
} else {
CGFloat wrapperItemViewHeight = CGRectGetHeight(_wrapperItemView.frame);
CGFloat wrapperItemViewHeight = CGRectGetHeight(_headerItemView.frame);
insets.top = wrapperItemViewHeight;
targetContentOffset = CGPointMake(0, -wrapperItemViewHeight);
}
Expand All @@ -111,31 +129,105 @@ - (void)startRefresh {
}
}

- (void)startRefreshFooter {
UIScrollView *scrollView = _scrollableView.realScrollView;
UIEdgeInsets insets = scrollView.contentInset;
CGSize contentSize = _scrollableView.contentSize;
CGPoint targetContentOffset;
if (self.isHorizontal) {
CGFloat wrapperItemViewWidth = CGRectGetWidth(_footerItemView.frame);
CGFloat scrollViewWidth = CGRectGetWidth(scrollView.frame);
insets.right = wrapperItemViewWidth;
targetContentOffset = CGPointMake(contentSize.width - scrollViewWidth + wrapperItemViewWidth, 0);
} else {
CGFloat wrapperItemViewHeight = CGRectGetHeight(_footerItemView.frame);
CGFloat scrollViewHeight = CGRectGetHeight(scrollView.frame);
insets.bottom = wrapperItemViewHeight;
targetContentOffset = CGPointMake(0, contentSize.height - scrollViewHeight + 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 (_onFooterRefresh) {
_onFooterRefresh(@{});
}
}

#pragma mark - ScrollListener, UIScrollViewDelegate

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView
withVelocity:(CGPoint)velocity
targetContentOffset:(inout CGPoint *)targetContentOffset {
UIEdgeInsets insets = scrollView.contentInset;
if (self.isHorizontal) {
CGFloat wrapperItemViewWidth = CGRectGetWidth(_wrapperItemView.frame);
// horizontal, for example, wrapping a view pager
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];
if (_headerItemView) {
CGFloat wrapperItemViewWidth = CGRectGetWidth(_headerItemView.frame);
if (contentOffsetX <= -wrapperItemViewWidth && insets.left != wrapperItemViewWidth) {
// Update the end sliding state of scrollview
targetContentOffset->x = -wrapperItemViewWidth;
// start refresh and call js
[self startRefresh];
}
}

if (_footerItemView) {
CGSize contentSize = scrollView.contentSize;
CGFloat scrollViewWidth = CGRectGetWidth(scrollView.frame);
CGFloat footerItemWidth = CGRectGetWidth(_footerItemView.frame);
if (contentOffsetX >= contentSize.width - scrollViewWidth + footerItemWidth && insets.right != footerItemWidth) {
// Update the end sliding state of scrollview
targetContentOffset->x = contentSize.width - scrollViewWidth + footerItemWidth;
// start refresh and call js
[self startRefreshFooter];
}
}

} else {
CGFloat wrapperItemViewHeight = CGRectGetHeight(_wrapperItemView.frame);
// vertical refresh wrapper, for example, wrapping a listview
CGFloat contentOffsetY = scrollView.contentOffset.y;
if (contentOffsetY <= -wrapperItemViewHeight && insets.top != wrapperItemViewHeight) {
insets.top = wrapperItemViewHeight;
scrollView.contentInset = insets;
if (_onRefresh) {
_onRefresh(@{});
if (_headerItemView) {
CGFloat wrapperItemViewHeight = CGRectGetHeight(_headerItemView.frame);
if (contentOffsetY <= -wrapperItemViewHeight && insets.top != wrapperItemViewHeight) {
insets.top = wrapperItemViewHeight;
scrollView.contentInset = insets;
if (_onRefresh) {
_onRefresh(@{});
}
}
}

if (_footerItemView) {
CGFloat wrapperItemViewHeight = CGRectGetHeight(_footerItemView.frame);
CGFloat scrollViewHeight = CGRectGetHeight(scrollView.frame);
if (contentOffsetY >= wrapperItemViewHeight - scrollViewHeight && insets.bottom != wrapperItemViewHeight) {
insets.bottom = wrapperItemViewHeight;
scrollView.contentInset = insets;
if (_onFooterRefresh) {
_onFooterRefresh(@{});
}
}
}
}
}

#pragma mark - HippyScrollableLayoutDelegate

- (void)scrollableDidLayout:(id<HippyScrollableProtocol>)scrollableView {
if (_footerItemView && _scrollableView) {
CGSize size = _footerItemView.frame.size;
CGSize contentSize = _scrollableView.realScrollView.contentSize;

if (self.isHorizontal) {
_footerItemView.frame = CGRectMake(contentSize.width, 0, size.width, size.height);
} else {
_footerItemView.frame = CGRectMake(0, contentSize.height, size.width, size.height);
}
[_scrollableView.realScrollView addSubview:_footerItemView];
}
}

Expand Down
6 changes: 6 additions & 0 deletions ios/sdk/component/refreshview/HippyRefreshWrapperItemView.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@

#import <UIKit/UIKit.h>

/// HeaderItem of RefreshWrapper
@interface HippyRefreshWrapperItemView : UIView

@end

/// FooterItem of RefreshWrapper
@interface HippyRefreshWrapperFooterItemView : UIView

@end
6 changes: 5 additions & 1 deletion ios/sdk/component/refreshview/HippyRefreshWrapperItemView.m
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#import "HippyRefreshWrapperItemView.h"
#import "UIView+Hippy.h"

@implementation HippyRefreshWrapperItemView

@end

@implementation HippyRefreshWrapperFooterItemView

@end
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,15 @@

NS_ASSUME_NONNULL_BEGIN

/// HeaderItem's ViewManager of RefreshWrapper
@interface HippyRefreshWrapperItemViewManager : HippyViewManager

@end

/// FooterItem's ViewManager of RefreshWrapper
@interface HippyRefreshWrapperFooterItemViewManager : HippyViewManager

@end


NS_ASSUME_NONNULL_END
15 changes: 15 additions & 0 deletions ios/sdk/component/refreshview/HippyRefreshWrapperItemViewManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,24 @@

#import "HippyRefreshWrapperItemViewManager.h"
#import "HippyRefreshWrapperItemView.h"

@implementation HippyRefreshWrapperItemViewManager

HIPPY_EXPORT_MODULE(RefreshWrapperItemView)

- (UIView *)view {
return [HippyRefreshWrapperItemView new];
}

@end


@implementation HippyRefreshWrapperFooterItemViewManager

HIPPY_EXPORT_MODULE(RefreshWrapperFooterItemView)

- (UIView *)view {
return [HippyRefreshWrapperFooterItemView new];
}

@end
Loading

0 comments on commit 3c6b696

Please sign in to comment.