常见的一些UIScrollViewDelegate代理回调可以参考https://www.cnblogs.com/cchHers/p/12423838.html,下面列出的主要是一些特殊情况。
1. scrollViewWillBeginDecelerating和scrollViewDidEndDecelerating并不是必须的
你可以尝试使用后附的代码在模拟器或手机上进行测试:当你手指接触屏幕并拖动一段距离,稍作停顿再松手,你会发现只执行到scrollViewWillEndDragging
和scrollViewDidEndDragging
就停止了。
![](https://blog.onespirit.fyi/wp-content/uploads/2024/09/Screen-Shot-2024-09-06-at-23.32.55-542x1024.png)
因此,除非你确定你的滑动列表一定会有减速动画(比如,启用了Paging且保证用户不会精准停留至无需结束弹性动画的位置),你可以尝试以scrollViewWillBeginDecelerating
或scrollViewDidEndDecelerating
作为滑动结束的标志,否则请不要将这两个回调作为唯一结束标志。
2. 区别手指滑动和命令滑动
如果你通过命令控制UIScrollView的contentOffset,比如
[self.collectionView setContentOffset:CGPointZero animated:YES];
你需要注意,通过setContentOffset
方式进行滚动时会有下面几种情况
Animated: YES | Animated: NO | |
ContentOffset与上次相同 | 无 | 无 |
ContentOffset与上次不同 | [scrollViewDidScroll] 多次 [scrollViewDidEndScrollingAnimation] 一次 | [scrollViewDidScroll] 一次 |
你应该注意到涉及到手指滑动的一些回调一律不会被调用,只会涉及到scrollViewDidScroll
和scrollViewDidEndScrollingAnimation
这两种。如果你的App中存在通过命令滚动的情况则需要特别关心。
3. 轻点Status Bar至顶
点击Status Bar是一种比较tricky的操作,用户可以通过这种方式快速滚动至页面的顶端,它只会调用scrollViewDidScroll
和scrollViewDidScrollToTop
这两种,并且scrollViewDidScrollToTop
的触发时机是在滚动结束后,类似于scrollViewDidEndDecelerating
。在一些需要对用户滑动监控的场景下(比如回到顶部时刷新信息流)同样需要进行特别关注,因为在这种场景下监听scrollViewDidEndDecelerating
是无效的。
![](https://blog.onespirit.fyi/wp-content/uploads/2024/09/Screen-Shot-2024-09-06-at-23.58.11-542x1024.png)
附录
可以使用下面的代码自行测试
#import "UIKit/UIkit.h" #import "ViewController.h" @interface ViewController () <UICollectionViewDelegate, UICollectionViewDataSource, UIScrollViewDelegate> @property (nonatomic, strong) UITextView *logTextView; @property (nonatomic, strong) UICollectionView *collectionView; @property (nonatomic, strong) NSMutableArray *logMessages; @property (nonatomic, strong) UIButton *backToTopAnimatedButton; @property (nonatomic, strong) UIButton *backToTopNoAnimationButton; @property (nonatomic, strong) UIButton *scrollToRandomOffsetAnimatedButton; @property (nonatomic, strong) UIButton *scrollToRandomOffsetNoAnimationButton; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Setup logTextView self.logTextView = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 300)]; self.logTextView.editable = NO; [self.view addSubview:self.logTextView]; // Initialize logMessages array self.logMessages = [NSMutableArray array]; // Setup UICollectionView UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; layout.itemSize = CGSizeMake(100, 100); // Set the size of each cell self.collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 300, self.view.frame.size.width, self.view.frame.size.height - 300) collectionViewLayout:layout]; self.collectionView.delegate = self; self.collectionView.dataSource = self; [self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"cell"]; // [self.collectionView setPagingEnabled:YES]; [self.view addSubview:self.collectionView]; // Setup buttons [self setupButtons]; } // UICollectionView DataSource - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { return 100; } - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath]; UIImageView *imageView = [[UIImageView alloc] initWithFrame:cell.contentView.bounds]; imageView.image = [UIImage imageNamed:@"example_image"]; // Use your image name here [cell.contentView addSubview:imageView]; return cell; } // UIScrollViewDelegate Methods - (void)scrollViewDidScroll:(UIScrollView *)scrollView { [self logMessage:@"[scrollViewDidScroll]"]; } - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { [self logMessage:@"[scrollViewWillBeginDragging]"]; } - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset { [self logMessage:@"[scrollViewWillEndDragging]"]; } - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { [self logMessage:@"[scrollViewDidEndDragging]"]; } - (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView { [self logMessage:@"[scrollViewShouldScrollToTop]"]; return YES; } - (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView { [self logMessage:@"[scrollViewDidScrollToTop]"]; } - (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView { [self logMessage:@"[scrollViewWillBeginDecelerating]"]; } - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { [self logMessage:@"[scrollViewDidEndDecelerating]"]; } - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView { [self logMessage:@"[scrollViewDidEndScrollingAnimation]"]; } // Helper method to log messages - (void)logMessage:(NSString *)message { [self.logMessages addObject:message]; if (self.logMessages.count > 20) { [self.logMessages removeObjectAtIndex:0]; } NSString *logText = [self.logMessages componentsJoinedByString:@"\n"]; self.logTextView.text = logText; } // Setup buttons - (void)setupButtons { CGFloat buttonWidth = self.view.frame.size.width / 2; CGFloat buttonHeight = 50; self.backToTopAnimatedButton = [self createButtonWithTitle:@"Back to Top (Animated)" action:@selector(backToTopAnimated)]; self.backToTopAnimatedButton.frame = CGRectMake(0, self.view.frame.size.height - 100, buttonWidth, buttonHeight); [self.view addSubview:self.backToTopAnimatedButton]; self.backToTopNoAnimationButton = [self createButtonWithTitle:@"Back to Top (No Animation)" action:@selector(backToTopNoAnimation)]; self.backToTopNoAnimationButton.frame = CGRectMake(buttonWidth, self.view.frame.size.height - 100, buttonWidth, buttonHeight); [self.view addSubview:self.backToTopNoAnimationButton]; self.scrollToRandomOffsetAnimatedButton = [self createButtonWithTitle:@"Random Offset (Animated)" action:@selector(scrollToRandomOffsetAnimated)]; self.scrollToRandomOffsetAnimatedButton.frame = CGRectMake(0, self.view.frame.size.height - 50, buttonWidth, buttonHeight); [self.view addSubview:self.scrollToRandomOffsetAnimatedButton]; self.scrollToRandomOffsetNoAnimationButton = [self createButtonWithTitle:@"Random Offset (No Animation)" action:@selector(scrollToRandomOffsetNoAnimation)]; self.scrollToRandomOffsetNoAnimationButton.frame = CGRectMake(buttonWidth, self.view.frame.size.height - 50, buttonWidth, buttonHeight); [self.view addSubview:self.scrollToRandomOffsetNoAnimationButton]; } // Create a button with title and action - (UIButton *)createButtonWithTitle:(NSString *)title action:(SEL)action { UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem]; [button setTitle:title forState:UIControlStateNormal]; [button addTarget:self action:action forControlEvents:UIControlEventTouchUpInside]; button.backgroundColor = [UIColor lightGrayColor]; [button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; return button; } // Button Actions - (void)backToTopAnimated { [self.collectionView setContentOffset:CGPointZero animated:YES]; [self logMessage:@"Back to top with animation"]; } - (void)backToTopNoAnimation { [self.collectionView setContentOffset:CGPointZero animated:NO]; [self logMessage:@"Back to top without animation"]; } - (void)scrollToRandomOffsetAnimated { CGFloat maxOffsetY = self.collectionView.contentSize.height - self.collectionView.frame.size.height; CGFloat randomOffsetY = arc4random_uniform((unsigned int)maxOffsetY); [self.collectionView setContentOffset:CGPointMake(0, randomOffsetY) animated:YES]; [self logMessage:[NSString stringWithFormat:@"Scrolled to random offset (%.2f) with animation", randomOffsetY]]; } - (void)scrollToRandomOffsetNoAnimation { CGFloat maxOffsetY = self.collectionView.contentSize.height - self.collectionView.frame.size.height; CGFloat randomOffsetY = arc4random_uniform((unsigned int)maxOffsetY); [self.collectionView setContentOffset:CGPointMake(0, randomOffsetY) animated:NO]; [self logMessage:[NSString stringWithFormat:@"Scrolled to random offset (%.2f) without animation", randomOffsetY]]; } @end