iOS OC 用 runloop 监听用户屏幕滑动开始与停止

4,305 阅读2分钟

使用 RunLoop 监听屏幕滑动开始和停止的状态是一种更底层的实现方式。RunLoop 可以监听各种事件,包括触摸事件,从而可以检测到滑动的开始和停止。以下是一个示例代码,展示如何使用 RunLoop 来监听屏幕滑动开始和停止的事件,并使用 block 回调来通知外部:

#import <UIKit/UIKit.h>

typedef void (^ScrollStateBlock)(BOOL isStop);

@interface ScrollManager : NSObject

+ (instancetype)sharedManager;
- (void)startObservingWithView:(UIView *)view scrollStateBlock:(ScrollStateBlock)scrollStateBlock;

@end

@implementation ScrollManager {
    CFRunLoopObserverRef _observer;
    BOOL _isScrolling;
    ScrollStateBlock _scrollStateBlock;
}

+ (instancetype)sharedManager {
    static ScrollManager *sharedManager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedManager = [[self alloc] init];
    });
    return sharedManager;
}

- (void)startObservingWithView:(UIView *)view scrollStateBlock:(ScrollStateBlock)scrollStateBlock {
    _scrollStateBlock = [scrollStateBlock copy];
    
    CFRunLoopObserverContext context = {0, (__bridge void *)(self), NULL, NULL, NULL};
    _observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, &runLoopObserverCallback, &context);
    CFRunLoopAddObserver(CFRunLoopGetMain(), _observer, kCFRunLoopCommonModes);
}

void runLoopObserverCallback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
    ScrollManager *manager = (__bridge ScrollManager *)info;
    switch (activity) {
        case kCFRunLoopBeforeWaiting:
            if (manager->_isScrolling) {
                manager->_isScrolling = NO;
                if (manager->_scrollStateBlock) {
                    manager->_scrollStateBlock(YES);
                }
            }
            break;
        case kCFRunLoopAfterWaiting:
            if (!manager->_isScrolling) {
                manager->_isScrolling = YES;
                if (manager->_scrollStateBlock) {
                    manager->_scrollStateBlock(NO);
                }
            }
            break;
        default:
            break;
    }
}

- (void)dealloc {
    if (_observer) {
        CFRunLoopRemoveObserver(CFRunLoopGetMain(), _observer, kCFRunLoopCommonModes);
        CFRelease(_observer);
        _observer = NULL;
    }
}

@end

使用示例

在你的视图控制器中,你可以这样使用 ScrollManager 类:

#import "ScrollManager.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 开始监听并设置回调 block
    [[ScrollManager sharedManager] startObservingWithView:self.tableView scrollStateBlock:^(BOOL isStop) {
        if (isStop) {
            NSLog(@"Scroll stopped");
            // 在这里处理滑动停止的事件,比喻在给Cell传显示数据时,停止再去刷新数据,减少不必要的Cell刷新
        } else {
            NSLog(@"Scroll started");
            // 在这里处理滑动开始的事件
        }
    }];
}

@end

解释

  1. ScrollManager 类

    • 使用 CFRunLoopObserverCreate 创建一个 RunLoop 观察者,监听 kCFRunLoopAllActivities 活动。
    • 在 runLoopObserverCallback 回调函数中,根据 RunLoop 的活动状态(kCFRunLoopBeforeWaiting 和 kCFRunLoopAfterWaiting)来判断滑动开始和停止的状态。
    • 使用 +sharedManager 方法实现单例模式,确保只有一个实例。
    • 提供 startObservingWithView:scrollStateBlock: 方法来开始监听 RunLoop 活动,并通过 block 回调滑动状态。
  2. ViewController 类

    • 使用 [ScrollManager sharedManager] 获取单例实例。
    • 调用 startObservingWithView:scrollStateBlock: 方法开始监听,并设置回调 block 来处理滑动开始和停止的事件。

通过这种方式,你可以更简洁地监听和处理滑动状态,并且通过 block 回调一个 BOOL 值来表示滑动是否停止。