IOS 类似抖音下拉刷新与自定义上拉加载
首先考虑在UICollection与拖动手势之间的问题。
解决UICollectionView上添加手势不能触发。
这里使用了子类继承UICollectionView
#import "INMyCollectionView.h"
@implementation INMyCollectionView
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[[self nextResponder] touchesBegan:touches withEvent:event];
[super touchesBegan:touches withEvent:event];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
[[self nextResponder] touchesMoved:touches withEvent:event];
[super touchesMoved:touches withEvent:event];
}
- (void)touchesEnded:(NSSet *)touches
withEvent:(UIEvent *)event {
[[self nextResponder] touchesEnded:touches withEvent:event];
[super touchesEnded:touches withEvent:event];
}
@end
在View上添加拖动手势
self.startPoint = CGPointZero;
self.isInLoading = NO;
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(panGestureHandle:)];
panGesture.minimumNumberOfTouches = 1;
panGesture.maximumNumberOfTouches = 1;
panGesture.delegate = self;
[self addGestureRecognizer:panGesture];
处理手势判断
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
// 给加的手势设置代理, 并实现此协议方法
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
UIPanGestureRecognizer *pan = (UIPanGestureRecognizer *)gestureRecognizer;
CGPoint pos = [pan velocityInView:pan.view];
if (pos.y > 0) {
return YES;
}
}
return NO;
}
在处理拖动手势
(void)panGestureHandle:(UIPanGestureRecognizer *)pan{
if (self.collectionView.contentOffset.y > 0) {
return;
}
DebugLog(@"panGestureHandle");
if (self.isInLoading) {
return;
}
if (pan.state == UIGestureRecognizerStateBegan) {
DebugLog(@"UIGestureRecognizerStateBegan");
self.startPoint = [pan translationInView:self];
self.collectionView.scrollEnabled = NO;
} if (pan.state == UIGestureRecognizerStateChanged) {
DebugLog(@"UIGestureRecognizerStateChanged");
CGPoint point = [pan translationInView:self];
CGFloat distance = point.y - self.startPoint.y;
if (distance > 0) {
self.refreshStateLabel.text = @"下拉刷新";
CGFloat scale = distance/KMaxScrollRefreshHeight;
if (scale > 1.0) {
scale = 1.0;
}
self.refreshStateLabel.alpha = scale;
self.navbarView.alpha = (1-scale);
self.refreshStateLabel.frame = CGRectMake(0.0, scale*(kStatusBarHeight + [BaseView baseSafeAreaEdgeInsets].top) + 5.0, CGRectGetWidth(self.bounds), 44.0);
if (distance > KMaxScrollRefreshHeight) {
DebugLog(@"可以下拉刷新");
}
} else {
DebugLog(@"上拉操作");
self.refreshStateLabel.alpha = 0.0;
}
} else if (pan.state == UIGestureRecognizerStateEnded || pan.state == UIGestureRecognizerStateCancelled || pan.state == UIGestureRecognizerStateFailed) {
DebugLog(@"UIGestureRecognizerStateEnded");
self.collectionView.scrollEnabled = YES;
CGPoint point = [pan translationInView:self];
CGFloat distance = point.y - self.startPoint.y;
self.refreshStateLabel.text = @"";
if (distance > 0) {
if (distance > KMaxScrollRefreshHeight) {
DebugLog(@"加载中...");
self.refreshStateLabel.text = @"加载中...";
self.refreshStateLabel.alpha = 1.0;
self.isInLoading = YES;
if (self.actionDelegate && [self.actionDelegate respondsToSelector:@selector(refreshLoadingData)]) {
[self.actionDelegate refreshLoadingData];
}
}
}
}
}
当然,类似抖音的下拉,在ScrollDidScoll处理了下
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView.contentOffset.y < 0) {
[scrollView setContentOffset:CGPointMake(0.0, 0.0)];
} else {
// 向上拖动,向上拖动的指定位置后加载更多数据
DebugLog(@"向上拖动,向上拖动的指定位置后加载更多数据");
}
}
自己处理上拉加载更多,这里代码如下
INUpwardRefreshView.h
@protocol INUpwardRefreshViewDelegate;
@interface INUpwardRefreshView : BaseView
@property (nonatomic, weak) id<INUpwardRefreshViewDelegate>delegate;
@property (nonatomic, strong) UIScrollView *scrollView;
- (instancetype)initWithTarget:(UIScrollView *)scrollView;
- (void)endRefreshing;
@end
@protocol INUpwardRefreshViewDelegate <NSObject>
- (void)pullUpwardRefreshDidFinish;
@end
INUpwardRefreshView.m
//
// INUpwardRefreshView.m
// Views
//
// Created by ABC on 2019/7/26.
//
#import "INUpwardRefreshView.h"
#import "Color.h"
#import "NSString+size.h"
NSString *const RefreshUpKeyPathContentOffset = @"contentOffset";
NSString *const RefreshUpKeyPathContentSize = @"contentSize";
CGFloat const FooterUpHeight = 80;
@interface INUpwardRefreshView ()
@property (nonatomic, strong) UIActivityIndicatorView *activityView;
@property (nonatomic, strong) UIImageView *arrowView;
@property (nonatomic, strong) UILabel *stateLable;
@property (nonatomic, assign) BOOL isRefresh;
@property (nonatomic, assign) CGFloat lastOffSet;
@end
@implementation INUpwardRefreshView
- (instancetype)initWithTarget:(UIScrollView *)scrollView {
self = [super init];
if (self) {
self.backgroundColor = [UIColor clearColor];
_scrollView = scrollView;
self.frame = CGRectMake(0, 0, 0, FooterUpHeight);
[_scrollView addSubview:self];
[self addObserver];
}
return self;
}
- (void)layoutSubviews{
[super layoutSubviews];
CGSize statelabelSize = [self.stateLable.text sizeWithFont:self.stateLable.font forMaxSize:CGSizeMake(MAXFLOAT, 40)];
self.stateLable.frame = CGRectMake((CGRectGetWidth(self.bounds) - statelabelSize.width)/2 + 10.0, 0.0, statelabelSize.width, 40);
self.arrowView.frame = CGRectMake(CGRectGetMinX(self.stateLable.frame) - CGRectGetWidth(self.arrowView.frame) - 10.0, 0.0, CGRectGetWidth(self.arrowView.frame), 40);
self.activityView.frame = CGRectMake(CGRectGetMinX(self.stateLable.frame) - CGRectGetWidth(self.arrowView.frame) - 10.0, (40.0 - 20.0)/2, 20, 20);
}
- (void)setCurrentFrame {
[self setFrame:CGRectMake(0, MAX(self.scrollView.contentSize.height, CGRectGetHeight(self.scrollView.frame)), self.scrollView.size.width, FooterUpHeight)];
[self setNeedsLayout];
}
- (void)setFrameHeight:(CGFloat)height {
[self setFrame:CGRectMake(0, MAX(self.scrollView.contentSize.height, CGRectGetHeight(self.scrollView.frame)), self.scrollView.size.width, height)];
DebugLog(@"frameHeight:%f",height);
}
#pragma mark setter
- (UIActivityIndicatorView *)activityView{
if (!_activityView) {
_activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
_activityView.hidesWhenStopped = YES;
[self addSubview:_activityView];
}
return _activityView;
}
- (UIImageView *)arrowView{
if (!_arrowView) {
_arrowView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 15, 40)];
_arrowView.image = [UIImage imageNamed:@"refresh_up_arrow"];
//[self addSubview:_arrowView];
}
return _arrowView;
}
- (UILabel *)stateLable{
if (!_stateLable) {
_stateLable = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 90, 40)];
_stateLable.backgroundColor = [UIColor clearColor];
_stateLable.font = [UIFont systemFontOfSize:12];
_stateLable.textAlignment = NSTextAlignmentLeft;
_stateLable.textColor = [UIColor whiteColor];
[self addSubview:_stateLable];
}
return _stateLable;
}
#pragma mark private
- (void)scrollViewContentOffsetDidChange:(NSDictionary *)change{
_stateLable.hidden = NO;
CGFloat yOffSet = _scrollView.contentOffset.y;
DebugLog(@"yOffSet:%f",yOffSet);
CGFloat contentSizeHeight = _scrollView.contentSize.height;
CGFloat frameHeight = CGRectGetHeight(_scrollView.frame);
if (contentSizeHeight > 0 && contentSizeHeight < frameHeight) {
CGFloat insetTop = frameHeight - contentSizeHeight;
if (yOffSet+insetTop > 0.0) {
//正在拖拽中
if (self.scrollView.isDragging) {
[UIView animateWithDuration:0.3 animations:^{
self.arrowView.hidden = NO;
if (yOffSet+insetTop > 100.0) {
self.arrowView.transform = CGAffineTransformMakeRotation(M_PI);
self.stateLable.text = @"松开夹子";
}else{
self.arrowView.transform = CGAffineTransformMakeRotation(M_PI*2);
self.stateLable.text = @"继续滑动";
}
}];
[self setFrameHeight:(yOffSet+insetTop)];
} else {
if (yOffSet+insetTop > 100.0) {
[self beginRefreshing];
}
}
}
} else {
//正在拖拽中
CGFloat aYBottom = yOffSet - (contentSizeHeight - frameHeight);
if (self.scrollView.isDragging) {
[UIView animateWithDuration:0.3 animations:^{
self.arrowView.hidden = NO;
if (aYBottom > 100.0) {
self.arrowView.transform = CGAffineTransformMakeRotation(M_PI);
self.stateLable.text = @"松开加载";
}else{
self.arrowView.transform = CGAffineTransformMakeRotation(M_PI*2);
self.stateLable.text = @"继续滑动";
}
}];
[self setFrameHeight:yOffSet];
} else {
if (contentSizeHeight > 0 && aYBottom > 100.0) {
[self beginRefreshing];
}
}
}
}
#pragma mark publick
- (void)beginRefreshing{
[self setFrameHeight:FooterUpHeight];
if (!_isRefresh) {
_isRefresh = YES;
CGFloat insetTop = _scrollView.contentInset.top;
//设置偏移量,衔接加载的更多数据
[UIView animateWithDuration:0.35 animations:^{
//[self.scrollView setContentInset:UIEdgeInsetsMake(insetTop, 0, FooterUpHeight, 0)];
[self.activityView startAnimating];
self.arrowView.hidden = YES;
self.stateLable.text = @"加载中";
} completion:^(BOOL finished) {
[self startBeginRefresh];
}];
}
}
- (void)startBeginRefresh {
// Refresh action!
if ([self.delegate respondsToSelector:@selector(pullUpwardRefreshDidFinish)]) {
[self.delegate performSelector:@selector(pullUpwardRefreshDidFinish) withObject:nil];
}
}
- (void)endRefreshing {
_isRefresh = NO;
[UIView animateWithDuration:0.3 animations:^{
[self.activityView stopAnimating];
self.arrowView.hidden = YES;
self.arrowView.transform = CGAffineTransformMakeRotation(M_PI*2);
}];
}
#pragma mark KVO
- (void)addObserver{
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[_scrollView addObserver:self forKeyPath:RefreshUpKeyPathContentOffset options:options context:nil];
[_scrollView addObserver:self forKeyPath:RefreshUpKeyPathContentSize options:options context:nil];
}
- (void)removeObserver{
[_scrollView removeObserver:self forKeyPath:RefreshUpKeyPathContentOffset];
[_scrollView removeObserver:self forKeyPath:RefreshUpKeyPathContentSize];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
if ([keyPath isEqualToString:RefreshUpKeyPathContentOffset]) {
[self scrollViewContentOffsetDidChange:change];
} else if ([keyPath isEqualToString:RefreshUpKeyPathContentSize]) {
[self setCurrentFrame];
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
- (void)dealloc{
[self removeObserver];
}
@end
使用上拉加载的shih
在collection创建的时候,设置下即可
_upwardRefreshView = [[INUpwardRefreshView alloc] initWithTarget:_collectionView];
_upwardRefreshView.delegate = self;
https://github.com/goodbruce/DouyinRefresh