1

I did a bit of searching for this and I'm hoping I'm not creating a duplicate (it is 4am after all). I'm also hoping it's an easy one for the gurus to answer but I just can't find it at the moment.

I have a UITableView being used for application settings. As you select one of the rows, it adds the checkmark accessory. As it's a modal window, I've set it to close the window once you select it. However, my current code is closing the window immediately as you select it and you cannot see the select animation (and subsequent appearance of the checkmark). My code for the modal window is based on the AddMusic example project.

Is there a way to have the table row flash a few times before executing the close command so that it's obvious which row was selected?

For reference, my modal window is a UIViewController <UITableViewDelegate>

And my select row code is as follows:

- (void) tableView: (UITableView *) tableView didSelectRowAtIndexPath: (NSIndexPath *) indexPath {
    UITableViewCell *oldCell = [tableView cellForRowAtIndexPath:self.selectedIndexPath];
    oldCell.accessoryType = UITableViewCellAccessoryNone;

    UITableViewCell *newCell = [tableView cellForRowAtIndexPath:indexPath];
    newCell.accessoryType = UITableViewCellAccessoryCheckmark;

    [self setSelectedIndexPath:indexPath];
    [tableView deselectRowAtIndexPath: indexPath animated: YES];

    // close the window when an option is selected.
    [self.delegate movieCinemaViewControllerDidFinish: self];
}
Cyntech
  • 5,362
  • 6
  • 33
  • 47

3 Answers3

3

To delay closing, change

[self.delegate movieCinemaViewControllerDidFinish: self];

to:

[self.delegate performSelector:@selector(movieCinemaViewControllerDidFinish:) withObject:self afterDelay:5];

To flash you can queue up a number of de/selectRowAtIndexPath:s using a similar technique. It might be wise to disable user interaction during this process too. Personally, I think flashing the cell is unnecessary - just delaying the exit by a fraction of a second should be enough for the highlight?


Alternatively schedule an NSTimer to call a -(void)complete method after a delay, and have that method call the delegate's selector.

Benjie
  • 7,701
  • 5
  • 29
  • 44
  • Thanks for the answer. The delay is specifically what I'm after but I've seen apps that flash as well, so I thought they may have been intertwined. – Cyntech Nov 01 '11 at 17:28
  • Just tried it; no dice, I'm afraid. XCode informs me that instance method -performSelector:withObject:afterDelay is not found. – Cyntech Nov 01 '11 at 17:48
  • You need to define your delay as in Bengie's example. Being that, - (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay , is a method of NSObject it is highly likely you have a typo. – Jesse Black Nov 01 '11 at 18:12
  • Added an alternative solution – Benjie Nov 01 '11 at 19:19
2

Actually, if you have a delegate that is an id only, you can not count on performSelector:withObject:afterDelay method, as it is a method from NSObject's NSDelayedPerforming category. Given that, the problem is that not all id necessarily are NSObject classes (you id could point to an NSProxy object, which does not inherit from NSObject, for example)

So, what you can do is use some GCD to accomplish that delay

double delayInSeconds = 0.5;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
    [self.delegate movieCinemaViewControllerDidFinish: self];
});
Felipe Sabino
  • 17,825
  • 6
  • 78
  • 112
  • 1
    Have used this for several similar situations and it works well. – timthetoolman Nov 01 '11 at 19:08
  • No GCD on iOS. Simplest workaround in this situation is to perform selector on self after delay, and have self call delegate directly. I would up vote you for NSProxy (something I didn't know but I don't think it's relevant to the question) and downvote for GCD so I've left your answer neutral :) – Benjie Nov 01 '11 at 19:12
  • I didn't get why `no GCD on iOS`... the first line of the doc says "Grand Central Dispatch (GCD) comprises language features, runtime libraries, and system enhancements that provide systemic, comprehensive improvements to the support for concurrent code execution on multicore hardware in **iOS** and Mac OS X." http://developer.apple.com/library/ios/#documentation/Performance/Reference/GCD_libdispatch_Ref/Reference/reference.html – Felipe Sabino Nov 01 '11 at 21:38
  • Finally got around to finishing this bit off. This works nicely! Thanks :) – Cyntech Nov 13 '11 at 11:27
  • Whoops I missed my qualifier, I meant there's `No GCD in iOS < 4.0`. – Benjie Nov 14 '11 at 09:45
  • Make it simple @benjie, always assume the latest SDK. After all, we are on SDK 5 already, if the user really need sth for 3 or less it should be described in the question as a limitation. – Felipe Sabino Nov 14 '11 at 11:39
1

I would create a subclassed UITableViewCell with the animation methods in it to depict a cell selection. The cell would have a protocol which would notify the delegate when the animation is completed, so the delegate (in this case, your UIViewController) would dismiss the modal only when the cell tells it that the animation is complete.

Step 1 would be to create a delegate protocol in the header of your UITableViewCell with a method like subclassedCellSelected:(NSIndexPath*)myIndexPath

Step 2 would be to have an ivar declared in your cell which stores the index path of the cell. Declare this ivar as a property (and synthesize it, of course) so that when your UIViewController creates the cell in cellForRowAtIndexPath, you can set the indexPath ivar on the cell. This tells the cell it's indexpath position and will be helpful when it's notifying the delegate about it's selection.

Step 3 Would be to implement the actual animation methods:

- (void)startPulse
{
    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationBeginsFromCurrentState:YES];
    [UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
    [UIView setAnimationDuration:0.1];
    [UIView setAnimationDelegate:self];
    [UIView setAnimationDidStopSelector:@selector(endPulse)];

    CGAffineTransform transform = CGAffineTransformMakeScale(0.9, 0.9);
    self.transform = transform;
    self.alpha = 0.5;

    [UIView commitAnimations];
}

- (void)endPulse
{
    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationBeginsFromCurrentState:YES];
    [UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
    [UIView setAnimationDuration:0.1];
    [UIView setAnimationDelegate:self];
    [UIView setAnimationDidStopSelector:@selector(completeAction)];

    CGAffineTransform transform = CGAffineTransformMakeScale(1, 1);
    self.transform = transform;
    self.alpha = 1;

    [UIView commitAnimations];
}

- (void)completeAction
{
    if([self.delegate respondsToSelector:@selector(subclassedCellDelegate:)])
        [self.delegate subclassedCellSelected:self.indexPath];
}

In the didSelectRowAtIndexPath method in your view controller, you should now call the startPulse method on the cell. You do this as follows:

CustomTableViewCell *myCell = (CustomTableViewCell*)[tableView cellForRowAtIndexPath:indexPath]

[myCell startPulse];

The indexPath to be passed is the one you get from the didSelectRowAtIndexPath callback.

What's going to happen now is that the cell will kick off it's animation methods, and once the animations are complete, will post the delegate callback for subclassedCellSelected.

Your UIViewController should subscribe to the custom cell's protocol and implement the delegate callback method in your class file as follows:

- (void)subclassedCellSelected(NSIndexPath*)cellIndexPath
{
    //dismiss the modal here
}

If you need help in setting up a delegate protocol, check my answer here: dismissModalViewController AND pass data back

What this code will end up doing is showing a simple animation where our cell 'bounces' in and out before carrying out the desired action.

Community
  • 1
  • 1
Sid
  • 9,508
  • 5
  • 39
  • 60
  • Mind you that there might be a simpler way to implement this (one that I'm not aware of) but this is a method that's been working for me for ages :) – Sid Nov 01 '11 at 18:18