How to know when a UIScrollView is scrolling (moving).

I don’t know about you, but it’s happened to me where I’ve needed to know when a scroll view is currently scrolling, and ONLY when it is scrolling. This means, when it is visually moving. You see, there are delegate methods such as scrollViewDidScroll: but these get called if the contentOffset changes. This doesn’t mean you have observed a visually moving scrollView.

I guess the most obvious use case is when you are hacking a scrollView to allow for “infinite scrolling”. There are many approaches to take when doing such work, but inevitably you’re most likely going to be trying to shift your subviews to different locations and then programmatically reset your contentOffset to contentOffset + contentWidth or some multiple of a constant that’s related to your content, or something like that.

With me so far? All I want is a scrollView that has the following

// KVO Observable
@property (nonatomic, readonly, getter = isScrolling) BOOL scrolling;  

(If you are unfamiliar with KVO, I would say Apple has the fundamental documentation, but if you’re a bit of a blacksmith like me, such documents, although incredibly exact, also put me to sleep. So, I recommend you just google “KVO Objective-C Tutorial” and see what you find.)

So here it is. The problem is, you need to use the scrollView delegate to make it work, but you don’t want to steal the delegate callbacks away from someone who really needs them, so in order to do this, you have to sort of ‘re-route’ the delegate callbacks. (If you’re implementing an infinite scroller, chances are you need to intercept / re-route the delegate anyway because I imagine you’ll create your own dataSource and delegate that are something like (note the conformance to the UIScrollViewDelegate):

@protocol HSCircularScrollViewDelegate<UIScrollViewDelegate>

@optional
- (void)circularScrollView:(HSCircularScrollView*)csv didMoveToItemAtIndex:(NSUInteger)itemIndex;
- (void)circularScrollView:(HSCircularScrollView*)csv didTapOnItem:(UIView*)item atIndex:(NSUInteger)itemIndex;

@end

@protocol HSCircularScrollViewDataSource 

@required
- (NSUInteger)numberOfItemsInCircularView:(HSCircularScrollView*)csv;
- (UIView*)circularScrollView:(HSCircularScrollView*)csv viewForItemAtIndex:(NSUInteger)itemIndex;
@end

But I digress…

Step 1 : Intercept the delegate

In your .m file’s Private Interface (Encapsulate!):

@interface HSCircularScrollView()<UIScrollViewDelegate>  // conform to the delegate
{
    __weak id<UIScrollViewDelegate> _myDelegate;  // the delegate that other calling classes will set.

    BOOL _isSettingContentOffset;  // We will need this later!
}

/* 
   here we make scrolling have a public getter and a private setter.  
   The accessors are automatically synthesized, which is GOOD, because 
   these auto-synthesized methods generate KVO notifications whenever 
   we use the dot notation. i.e. self.scrolling = YES; 
   to change them.  Free functionality!  
*/
@property (nonatomic, assign, getter = isScrolling) BOOL scrolling;  
@end

I also hope to teach a bit of best practices, so please get in the habit of making your views be able to use Interface Builder by supporting the NSCoding protocol:

@implementation HSCircularScrollView 

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self setupDefaults];
    }
    return self;
}

- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder: aDecoder];
    if (self) {
        [self setupDefaults];
    }
    return self;
}
- (void)setupDefaults
{
    _scrolling = NO; 
    [super setDelegate: self];  // this is where we hijack the delegate.  see the overridden method setDelegate:
}

- (id<UIScrollViewDelegate>)delegate
{
    return _myDelegate;
}

- (void)setDelegate:(id<UIScrollViewDelegate>)aDelegate
{
    //  We are the delegate.  We will always be the delegate.  
    //  We use our delegate callbacks to pass the message along 
    //  to the argument provided to this method
    [super setDelegate:self];  
    
    if (aDelegate != _myDelegate)
    {
        _myDelegate = aDelegate;
    }
}

// ... continued in next section

I have to stop midway here to make a few comments and then talk about the next bit. We’ve now just set up the infrastructure to be able to be the scrollView’s delegate so we can do some further required work of our own, but then can also pass on the delegate’s messages to any external class that needs this scrollView’s delegate functionality. Now that we have this, we can actually proceed to adding the task of knowing when the scroll view is scrolling.

Step 2 : Dealing with contentOffset

A UIScrollView has a contentOffset property that can be set directly, or can be animated. The issue is however, if you set the contentOffset and don’t animate it, there are still messages sent to the delegate’s scrollViewDidScroll: callback. This is semantics. It basically means “scroll view did change its contentOffset”. However, for the purposes of knowing when a scrollView is visually scrolling (i.e. you see content moving across the screen), this method does not provide us with the info we need. We need to distinguish whether the contentOffset has changed because it animated or because it was ‘hard set’.

Now you will see above in the class’ private interface why there is a BOOL _isSettingContentOffset; You need to do this any time your custom code calls:

_isSettingContentOffset = YES;
self.contentOffset = somePoint;

(No, you can’t override setContentOffset and add this YES clause, because the UIScrollView itself calls this method internally as well when it actually is scrolling due to motion.)

Now we have to start implementing our UIScrollViewDelegate methods:

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    if (_isSettingContentOffset) 
    {    
        self.scrolling = NO;
    }
    else
    {
        self.scrolling = YES;
    }
    
    if (_isSettingContentOffset) {
        _isSettingContentOffset = NO;
    }
    
    // check to see if I am my own delegate and then prevent infinite loop.
    if (_myDelegate != (id<UIScrollViewDelegate>)self && [_myDelegate respondsToSelector:@selector(scrollViewDidScroll:)])
    {
        [_myDelegate performSelector:@selector(scrollViewDidScroll:) withObject:self];
    }
}

We’ve now dealt with the unpleasantness associated with contentOffset. Remember that if in your implementation of this subclass you have to programmatically change the contentOffset to change the _isSettingContentOffset = YES;

Step 3 : Complete the Hijacking process and add the scrolling KVO notifications

Now we simply look at the UIScrollViewDelegate methods and add the rest of the scrolling KVO notifications AS WELL AS ensuring all the delegate methods will be re-routed to any ‘real’ delegate.

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
    if (!decelerate) {
        self.scrolling = NO;
    }
    
    // check to see if I am my own delegate and then prevent infinite loop.
    if (_myDelegate != (id<UIScrollViewDelegate>)self &&
        [_myDelegate respondsToSelector:@selector(scrollViewDidEndDragging:willDecelerate:)])
    {
        [_myDelegate scrollViewDidEndDragging:self willDecelerate:decelerate];
    }
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    self.scrolling = NO;
    
    // check to see if I am my own delegate and then prevent infinite loop.
    if (_myDelegate != (id<UIScrollViewDelegate>)self &&
        [_myDelegate respondsToSelector:@selector(scrollViewDidEndDecelerating:)])
    {
        [_myDelegate performSelector:@selector(scrollViewDidEndDecelerating:) withObject:self];
    }
}

- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
    self.scrolling = NO;
 
    // check to see if I am my own delegate and then prevent infinite loop.
    if (_myDelegate != (id<UIScrollViewDelegate>)self &&
        [_myDelegate respondsToSelector:@selector(scrollViewDidEndScrollingAnimation:)])
    {
        [_myDelegate performSelector:@selector(scrollViewDidEndScrollingAnimation:) withObject:self];
    }
}

// and now for completeness...

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
    // check to see if I am my own delegate and then prevent infinite loop.
    if (_myDelegate != (id<UIScrollViewDelegate>)self &&
        [_myDelegate respondsToSelector:@selector(scrollViewWillBeginDragging:)])
    {
        [_myDelegate performSelector:@selector(scrollViewWillBeginDragging:) withObject:self];
    }
}

- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView   // called on finger up as we are moving
{
    // check to see if I am my own delegate and then prevent infinite loop.
    if (_myDelegate != (id<UIScrollViewDelegate>)self &&
        [_myDelegate respondsToSelector:@selector(scrollViewWillBeginDecelerating:)])
    {
        [_myDelegate performSelector:@selector(scrollViewWillBeginDecelerating:) withObject:self];
    }
}

- (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView
{
    // check to see if I am my own delegate and then prevent infinite loop.
    if (_myDelegate != (id<UIScrollViewDelegate>)self &&
        [_myDelegate respondsToSelector:@selector(scrollViewDidScrollToTop:)])
    {
        [_myDelegate performSelector:@selector(scrollViewDidScrollToTop:) withObject:self];
    }
}

Long blog post, but hopefully this will be helpful to you. Create a subclass and add this stuff to it. Try it out by putting one of these in a view controller and have the view controller observe the scrolling property. And have the observe callback set a label’s text to scrolling or not scrolling. Then you’ll see.

Advertisements

Cocoa Bindings – The Easy way for an NSView subclass to update itself

So, say you have a NSView subclass in your Interface that is purely data-driven (i.e. it’s not an interactive component, it merely provides a graphical representation of an underlying data model).  It would be nice to keep this view updated to the current state of your data model automatically without writing too much glue code.  Here comes Cocoa Bindings!

Now, as Cocoa Bindings and Core Data are both powerful technologies, it’s hard to write a tutorial that doesn’t start from the beginning.  So, this is not a tutorial (per se), nor am I starting from the beginning.  Here is the start situation:

– You are using Core Data in your app, and don’t have any issues like I have described in blog posts preceding these (i.e. pretty much every attribute is immutable (i.e. you use myAttribute = newAttribute to register any kind of change on my attribute),

– You are familiar with NSArrayControllers and know a bit about basic Cocoa Bindings.

So?  How to get things working?  The trick is to make use of the CoreData framework’s automatically generated NSNotifications, namely this one:

NSManagedObjectContextObjectsDidChangeNotification, which you can use to determine if any of these objects are of any interest to your NSView subclass, and if yes, tell your class to redraw itself.

How does this all begin?  It begins with the bindings.   In an app I’m working on, I have sort of objects that have a specific duration, and this duration is derived from the duration of child objects.  Imagine the parent object as a day, and the child objects as tasks, and those children have children of subtasks, who have a duration.  Get it ?  A sort of pseudo equation for the entire duration would be:

totalDuration = [day valueForKeyPath: @"tasks.@sum.subtasks.@sum.duration"];

(although in Cocoa this isn’t allowed, but perhaps you get the shorthand.  It sums all durations in the sub elements)

Or said another way, a Day has many Task object who each have many Subtask objects, and a subtask has a duration property that you are editing elsewhere in your GUI.

So, how do we get this working? Here would be a portion of your NSView subclass’ .m file. The .h is a subclass of NSView and has:

@property (nonatomic, strong) Day *day;

static int DayObservingContext; // see here for an explanation of KVO contexts http://stackoverflow.com/a/11917449/421797

+ (void)initialize
{
    [self exposeBinding:@"day"];
}

- (void)bind:(NSString *)binding toObject:(id)observable withKeyPath:(NSString *)keyPath options:(NSDictionary *)options
{
    if ([binding isEqualToString:@"day"]) {
        [observable addObserver:self forKeyPath:@"selection" options:0 context:&amp;DayObservingContext];
        _arrayController = (NSArrayController*)observable;
    }
    else{
        [super bind:binding toObject:observable withKeyPath:keyPath options:options];
    }
}

- (void)unbind:(NSString *)binding
{
    if ([binding isEqualToString:@"day"]) {

        [_arrayController removeObserver:self forKeyPath:@"selection"];
        _arrayController = nil;
        self.day = nil;
    }
    else{
        [super unbind: binding];
    }
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if (context == &amp;DayObservingContext) {

        id selection = _arrayController.arrangedObjects[_arrayController.selectionIndex];

        if (!selection &amp;&amp; [selection isKindOfClass:[Day class]] == NO) {
            [NSException raise:NSInternalInconsistencyException format:@"This object should be bound to Day Objects!"];
        }

        self.day = (Day*)selection;

        [self setNeedsDisplayInRect:[self visibleRect]];
    }
    else
    {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

- (Day*)day { return _day;}
- (void)setDay:(Day *)day
{
    if (day == _day) {
        return;
    }

    if (_day) {
        [[NSNotificationCenter defaultCenter] removeObserver: self
                                                        name: NSManagedObjectContextObjectsDidChangeNotification
                                                      object: _day.managedObjectContext];
    }

    _day = day;

    if (_day) {
        [[NSNotificationCenter defaultCenter] addObserver: self
                                                 selector: @selector(contextChanged:)
                                                     name: NSManagedObjectContextObjectsDidChangeNotification
                                                   object: _day.managedObjectContext];
    }

    [self setNeedsDisplayInRect:[self visibleRect]];
}

- (void)contextChanged:(NSNotification*)notification
{
    // Get a set containing ALL objects which have been changed
    NSSet* insertedObjects = (NSSet*)[[notification userInfo] objectForKey:NSInsertedObjectsKey];
    NSSet* updatedObjects = (NSSet*)[[notification userInfo] objectForKey:NSUpdatedObjectsKey];
    NSSet* deletedObjects = (NSSet*)[[notification userInfo] objectForKey:NSDeletedObjectsKey];

    NSSet *changedObjects = [NSSet set];

    if (insertedObjects) changedObjects = [changedObjects setByAddingObjectsFromSet:insertedObjects];
    if (updatedObjects) changedObjects = [changedObjects setByAddingObjectsFromSet:updatedObjects];
    if (deletedObjects) changedObjects = [changedObjects setByAddingObjectsFromSet: deletedObjects];

    // go through all objects, find type, and see if they have a relationship that is a part of this Day
    BOOL shouldRefresh = NO;

    for (NSManagedObject *object in changedObjects) {

        if ([object isKindOfClass:[Day class]]) {
            if (object == _day) {
                shouldRefresh = YES;
                break;
            }
        }
        if ([object isKindOfClass:[Task class]]) {
            if ([(SCRecipeStep*)object day] == _day) {
                shouldRefresh = YES;
                break;
            }
        }
        if ([object isKindOfClass:[Subtask class]]) {  // it is a subtask that has the .duration property which is edited elsewhere in your GUI
            if ([[(Subtask*)object task] day] == _day) {
                shouldRefresh = YES;
                break;
            }
        }
    }

    if (shouldRefresh) {
        [self setNeedsDisplayInRect:[self visibleRect]];
    }

}

- (void)drawRect:(NSRect)dirtyRect
{
   // your custom code here that knows how to draw a Day object
}

Now, all you have to do in Interface Builder is create an outlet to the NSArrayController that is managing your Day objects (i.e. _arrayController.arrangedObjects will be a collection of Day objects), and of course an outlet to your “DayView” subclass.

then, you just add the binding in your loadView method:

- (void)loadView
{
    [super loadView];

    [self.dayView bind: @"day"
              toObject: self.daysArrayController
           withKeyPath: @"selection" /* due to the way we overrode bind: we could specify nil here */
               options: nil];
}

And so any time your currently selected day changes, it will know how to change the currently drawn day, and because we are using NSNotificationCenter to be informed of any changes to the managedObjectContext, and have code that checks if any of these changes are relevant to our view, the DayView will update itself accordingly.

I spent ages trying to get the Day object’s children to notify the parent if any of its properties change (explained in the KVO documentation, “Registering Dependent Keys, to-many relationships), but found it to be an insane amount of code required and thought “there must be an easier way than this”, I read that one sentence in this section of the documentation that states:

“2. If you’re using Core Data, you can register the parent with the application’s notification center as an observer of its managed object context. The parentshould respond to relevant change notifications posted by the children in a manner similar to that for key-value observing.”

This is what this post attempts to clarify. I hope it helps. I can’t believe how much time I’ve spent trying to get something that’s meant to be simple, working.

PS – THIS POST was very helpful for getting the right approach

Core Data, KVO, Bindings… Tips and Tricks for the Layman

So, as my blog has indicated, I have been trying to tackle many advanced technologies all at once, and I’m sure I will look back on this blog one day and shake my head at the silliness of the issues I’m having currently, but well, you can’t make an omelette without breaking some eggs.

I really wish I had a mentor, some guru, someone where I could just ask “am I on the right track?  Is this the right approach?”  I don’t need much supervision or direction, but just a point in the right direction would be IMMENSELY helpful.   So, without that, taking the brute force approach, I thought I’d post a few things that you, as the beginner may want to keep in mind until you are more advanced yourself.

This list will be periodically updated.

* KVO and Bindings are deep and powerful technologies (as is Core Data).  Apple has tried to hide a lot of this from you so you can get up and running quickly.  That said, it’s still a steep learning curve and it’s difficult to just ‘jump into’ KVO, Bindings, and Core Data all at once.

* Errors and exceptions relating to KVO, CoreData, and Bindings are difficult to debug as the console output of such errors is seldom descriptive enough so to help you track down the problem.

* Core Data entities should have properties that are ‘one thing’, and not a compound property.  (else you have to write a bunch of custom code)  i.e., if you for example use a Value Transformer to restore a custom object, changes to those objects’ properties will not mark the Core Data entity as dirty, and thus any changes you make to that will not get saved.  (unless you are setting these properties after just having added that object to the array, because by definition that object will be dirty.)  In slightly more programmer speak, your custom properties (transformable attributes) should be IMMUTABLE, meaning, only changes are registered if you make a call that’s something like

myEntity.myCustomProperty = someValue

NOT,

myEntity.myCustomProperty.maxValue = someMaxValue

* All the tutorials and intros to Cocoa Bindings tout it as this magic thing that will result in you never having to write glue code again!  Wrong.  F•cking wrong wrong wrong.  (At least until you get your head around it!!)  I’m actually quite surprised at how terrible some of Apple’s documentation can be on the topic of KVO, Core Data, and Bindings.  iOS Developers (which I am, 2 weeks into Cocoa…) won’t have to worry as most of this stuff doesn’t work on iOS.  For example, the easiest way to make a NSView subclass that is backed by a Core Data model.  I spent AGES trying to figure this out the KVO way, whereas the solution’s approach was briefly mentioned in ONE sentence in a KVO programmer’s guide.   (will write a post about this soon)

False Assumptions I have made along the way and what WASN’T true:

– changes to an object in a to-many relationship will notify its parent.  (i.e. I change an Employee’s name, the Department will not receive a KVO notification about this.  Only if you add/remove employees, will the Department receive an update about this.  This isn’t such an issue, but what if your object was a TimeLine with many TimeSteps, who each have a duration property?  The Timeline won’t update.  Yes yes, this is all in the KVO Documentation, and implementing such updates are non-trivial)

KVO – Observing any change in Object state

I have a situation where it would be really handy to bind an entire object (and its encapsulated states) to a view.  As this view displays itself depending on the all the different values of its member variables, it could be very tedious to set all this up, property by property, when all a view object really needs to know is when its underlying data model has changed and it should therefore redraw itself.

So, I thought I would post my solution to the problem.  Basically the approach is to create a property that you can observe (call it observableSelf), and will send change notifications any time any other property of that object changes.  Other than the fact that you have to observe some extra property, I find the solution kind of elegant.

meh.  Here’s the code.  It speaks for itself.

//
//  KVOUpdatingObject.h
//
//  Created by Stephen O'Connor on 1/28/13.
//  Copyright (c) 2013 Stephen O'Connor. All rights reserved.

#import <Foundation/Foundation.h>

typedef NSUInteger ChangeMonitor;  // I put this here because it seemed better to
                                   //define an appropriate type than int, although it doesn't really matter.

@interface KVObservableObject : NSObject

@property (nonatomic, assign) ChangeMonitor observableSelf; // basically you observe this

@end

and now the .m

//  KVOUpdatingObject.m
//  CocoaBindingsTesting
//
//  Created by Stephen O'Connor on 1/28/13.
//  Copyright (c) 2013 Stephen O'Connor. All rights reserved.
//

#import "KVOUpdatingObject.h"
#import <objc/runtime.h> // very important to import this!

@implementation KVOUpdatingObject

+(NSSet*)keyPathsForValuesAffectingValueForKey:(NSString*)key {

    NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];

    if ([key isEqualToString:@"observableSelf"]) {
        NSMutableArray *keys = [NSMutableArray array];
        unsigned int count;
        objc_property_t *properties = class_copyPropertyList([self class], &count);  // see imports above!
        for (size_t i = 0; i < count; ++i) {
            NSString *property = [NSString stringWithCString:property_getName(properties[i])
                                                    encoding:NSASCIIStringEncoding];

            if ([property isEqualToString: @"observableSelf"] == NO) {
                [keys addObject: property];
            }
        }
        free(properties);

        keyPaths = [keyPaths setByAddingObjectsFromArray: keys];
    }

    return keyPaths;
}

@end

KVC is easy to understand. KVO starts to get a little more complicated, and Cocoa Bindings is the next step up. I’ve been having trouble with Bindings and CoreData only because I haven’t had very much time with it. It’s amazing what Apple hides from the Developer and yet still enables the Developer to do wonderful things. But slowly the Developer needs to know more and more and go deeper. I’m still on my way down that road.

This is easily tested to give you some safe feeling. I created a subclass of the above, gave it a NSNumber property and an NSArray property (someNumber, and someObjects respectively). Here’s the test case:

@implementation CocoaBindingsTestingTests

- (void)setUp
{
    [super setUp];

    // Set-up code here.
    _testObject = [KVOTesting new];   // the subclass of KVObservableObject

    _observationCount = 0;  // some instance variable on this test case

    [_testObject addObserver:self forKeyPath:@"observableSelf"
                     options:NSKeyValueObservingOptionNew
                     context:nil];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if ([keyPath isEqualToString:@"observableSelf"]) {

        NSLog(@"Object Changed!");
        _observationCount++;
    }
    else
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}

- (void)tearDown
{
    // Tear-down code here.
    [super tearDown];
}

- (void)testExample
{
    [_testObject setValue:@4 forKey:@"someNumber"];  
    _testObject.someNumber = @4; 
    _testObject.someObjects = @[@2, @3, @4]; 
    [_testObject setSomeNumber: @5];

    STAssertTrue(_observationCount == 4, @"Should have reported 4 changes");
}

@end

Now, the only remaining issue for such a use case is what if this object is a member of another object, and this object's properties change, but not the object itself.  How would that 'parent' know?