KCCScrollViewContext

View the Project on GitHub 00StevenG/KCCScrollViewContext

Scrolling with context

UIScrollView is an indispensable class for iOS development. If you're not using UIScrollView, you're likely using one of it's subclasses (such as UITableView, UICollectionView or UITextView) and implementing its delegate protocol (UIScrollViewDelegate) or extended delegates (such as UITableViewDelegate or  UITextViewDelegate). I often implement methods to calculate UIScrollView state or position. The logical place is the view controller instance presenting the scroll view. It may look something like this:

@interface ViewControlller 

// scrolling state
@property (nonatomic, readwrtie, asssign) BOOL isScrolling;

@end 

Rather than do this again and again (see DRY) ), I’ve created KCCScrollViewContext.

KCCScrollViewContext provides additional UIScrollView state and expands upon UIScrollViewDelegate. It’s simple to add and works with existing messaging from UIScrollViewDelegate and its extended protocols. It features :

How to use:

- (void)viewDidLoad {
    [super viewDidLoad];    
    self.scrollContext = [[KCCScrollViewContext alloc]initWithScrollView:self.tableView andDelegate:self];
}

Init:

KCCScrollViewContext servers as the passed UIScrollView instance’s delegate. It also adds an additional handler to scrollView’s pan gesture to keep track of the most recent tracking point.

- (instancetype)initWithScrollView:(UIScrollView*)scrollView andDelegate:(id<KCCScrollViewContextDelegate>)delegate {  
    if ([super init]) {
        _scrollDelegate = delegate;
        _scrollView = scrollView;
        _scrollView.delegate = self;
        _lastContentOffset = _scrollView.contentOffset;
        [_scrollView.panGestureRecognizer addTarget:self action:@selector(scrollViewPanGestureHandler:)];
    }
    return self;
}

Messaging:

KCCScrollViewContext works with existing delegate instances by using Objective-C’s dyanmic messaging feature. It overrides two NSObject methods

When the context is reassigned as the delegate. UIScrollView subclasses such as UITableView check which methods the object responds to by calling respondsToSelector. So the context’s implementation of this method checks itself and it’s delegate.

- (BOOL)respondsToSelector:(SEL)aSelector {

    BOOL result = [super respondsToSelector:aSelector];
    if (!result) {
        result = [self.scrollDelegate respondsToSelector:aSelector];
    }
    return result;
}

When the context is sent a message for a method not implemented (such as in response to UITableView row selection), forwardingTargetForSelector is an opportunity to provide an object that can. In this case the context’s delegate.

- (id)forwardingTargetForSelector:(SEL)aSelector {

    if ([self.scrollDelegate respondsToSelector:aSelector]) {
        return self.scrollDelegate;
    }
    return [super forwardingTargetForSelector:aSelector];
}

For UIScrollViewDelegate methods implemented by the context (such as scrollViewDidScroll:). The context first updates it’s internal logic and state and then forwards the original invocation to the delegate as seen here.

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {

    self.isScrolling = YES;
    [self sendScroll:scrollView messageIfNeeded:_cmd];
}

- (void)sendScroll:(UIScrollView *)scroll messageIfNeeded:(SEL)message {

    if ([self.scrollDelegate respondsToSelector:message]) {
#       pragma clang diagnostic push
#       pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        [self.scrollDelegate performSelector:message withObject:scroll];
#       pragma clang diagnostic pop
    }
}

The remainder of the context code is simply calculations and state management by responding to scroll view events.