1

I have a UIButton subclass that does some custom drawing and animations. That is all working fine and dandy.

However, most of my buttons dismiss the current view via their superview calling [self dismissViewControllerAnimated] once it is confirmed with the model that whatever the button push was supposed to accomplish was actually done, and I want there to be a delay to allow the animation to complete before dismissing the view.

I am able to easily enough animate the UIButton subclass on touchesEnded and then call [super touchesEnded], which works fine except that it doesn't let my animations finish before dismissing the view. Like this:

-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    CABasicAnimation *myAnimation = [CABasicAnimation animationWithKeyPath:@"transform.foo"];
    //set up myAnimation's properties

    [self.layer addAnimation:shakeAnimation forKey:nil];
    [super touchesEnded:touches withEvent:event]; //works! but no delay
}

My first attempt at creating a delay was by using CATransaction, as follows:

-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{   
    CABasicAnimation *myAnimation = [CABasicAnimation animationWithKeyPath:@"transform.foo"];
    //set up myAnimation's properties

    [CATransaction begin];
    [CATransaction setCompletionBlock:^{
        [super touchesEnded:touches withEvent:event]; //doesn't seem to do anything :-/
    }];
    [self.layer addAnimation:shakeAnimation forKey:nil];
    [CATransaction commit];
}

Which, as far as I can tell, is executing CATransaction's completionBlock, but it just isn't doing anything.

I've also tried assigning the touches and event arguments from touchesEnded to both properties and global variables, and then executing [super touchesEnded] in another method called by an NSTimer. The same thing seems to be occurring where the code is executing, but my call to [super touchesEnded] isn't doing anything.

I've dug around online for hours. Added stubs of the other touches methods from UIResponder which just contain [super touches...]. Tried setting up my global variables for the method called by NSTimer differently (I very well may be missing something in regards to global variables...). This button is being created by the Storyboard, but I've set the class to my custom class, so I don't think UIButton's +(UIButton *)buttonWithType method is affecting this.

What am I missing? Is there some small thing I'm forgetting about or is there just no way to delay the call to [super touchesEnded] from a UIButton subclass?

paulmrest
  • 414
  • 4
  • 14

1 Answers1

1

I was not able to solve this, only find a way around it.

My last stab at solving this was to figure out whether the [super touchesEnded...] inside the completion block was being executed in a thread that was different from the thread it was executed in when it was outside the completion block... and no, they both appear to be the main thread (Apple's documentation on CATransaction does state that its completionBlock is always run in the main thread).

So in case someone else is banging their head against this, here's my less-than-elegant solution:

1.) In my subclass of UIButton I created a weak property called containingVC.

2.) In every single (ugh) VC that uses the custom button class I have to do this:

@implemenation VCThatUsesCustomButtonsOneOfWayTooMany

-(void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];
    self.firstCustomButton.containingVC = self;
    self.secondCustomButton.containingVC = self;
    self.thirdCustomButton.containingVC = self;
    ....
    self.lastCustomButton.containingVC = self;
    //you're probably better off using an IBOutletColletion and NSArray's makeObjectPerformSelector:withObject...
}

@end

3.) Then in my custom UIButton class I have something like this:

-(void)animateForPushDismissCurrView
{
    CAAnimation *buttonAnimation = ...make animation

    [CATransaction begin];
    [CATransaction setCompletionBlock:^{

        [self.containingVC performSegueWithIdentifier:@"segueID" sender:self.containingVC];

    }];
    [self.layer addAnimation:buttonAnimation forKey:nil];
    [CATransaction commit];
}

4.) Then in whatever VC that the user is currently interacting with, after making sure the button has done whatever it was supposed to do (in my case it checks with the model to confirm that the relevant change was made), each button has to call [someCustomButton animateForPushDismissCurrView] which then animates the button press and then fires the UIStoryboardSegue that actually dismisses the view.

Obviously this would work for going deeper, not just unwinding, but you'd need additional logic in the custom button's -(void)animateForPush method or a separate method entirely.

Again, if I'm missing something here, I'd love to know what it is. This seems like an absurd number of hoops to jump through to accomplish what seems like a simple task.

Lastly, and most importantly, if it just won't work with the [super touchesEnded...] method in CATransaction's completionBlock, I'd like to know WHY. I suspect that it has something to do with threads or the weirdness that is Objective-C's super keyword.

paulmrest
  • 414
  • 4
  • 14