2

TL;DR Update: Basically what I need is to delay my code until iOS finishes its "app startup" animation.

I would like to animate content of a navigation bar when my app becomes active. In my controller, I'm subscribed to UIApplicationDidBecomeActiveNotification and use setRightBarButtonItem:animated: to perform the change.

The problem is that the change is not animated.

I did some experimentation and discovered that if I wait a little ([NSThread sleepForTimeInterval:.3]), it's animating without any issues.

Here is a simple view controller demonstrating the problem:

@interface TESTViewController ()

@property (strong, nonatomic) IBOutlet UINavigationBar *navigationBar;

@end

@implementation TESTViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    UINavigationItem *item = [UINavigationItem new];
    UIBarButtonItem *oldItem = [[UIBarButtonItem alloc] initWithTitle:@"Old" style:UIBarButtonItemStyleBordered target:nil action:NULL];
    [item setRightBarButtonItem:oldItem];
    [[self navigationBar] setItems:@[item] animated:NO];
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidBecomeActiveNotificationAction:) name:UIApplicationDidBecomeActiveNotification object:nil];
}

- (void)applicationDidBecomeActiveNotificationAction:(NSNotification *)notification
{
    // [NSThread sleepForTimeInterval:.3];
    UIBarButtonItem *newItem = [[UIBarButtonItem alloc] initWithTitle:@"New" style:UIBarButtonItemStyleBordered target:nil action:NULL];
    [[[self navigationBar] items][0] setRightBarButtonItem:newItem animated:YES];
}

@end

I'd like find a better solution than blocking the thread or performing the change after a fixed delay.

Rudolf Adamkovič
  • 31,030
  • 13
  • 103
  • 118
  • Application becomes 'active' before view appears - why don't you simply move the animation code from `applicationDidBecomeActiveNotificationAction` to `viewDidAppear`. – Rok Jarc Mar 06 '13 at 17:08
  • 2
    @rokjarc I'd love to. The thing is that `viewDidAppear:` is not being called when I switch back to the app. BTW, `viewDidAppear:` name is a bit misleading (see http://stackoverflow.com/questions/5417318/viewdidappear-not-firing-ever-again-after-app-enters-foreground). – Rudolf Adamkovič Mar 06 '13 at 17:16
  • Ah, yes. i know i saw a workaround for that some time ago. I belive it had something to do with `UINavigationController` (even if it is not used in the view hierarchy). Will try to find it. – Rok Jarc Mar 06 '13 at 17:22
  • I've had similar issues in the past solved by using dispatch_async (onto the main queue) and performing the animation in that block. It adds no visible delay and doesn't block any threads. Might not work for your specific case, though. – jrturton Mar 09 '13 at 18:33

5 Answers5

4

Simply use dispatch_after on the main queue instead of calling
+[NSThread sleepForTimeInterval:] class method.

Pass your animation as a block and it should work perfectly.

Tricertops
  • 8,492
  • 1
  • 39
  • 41
Vincent Zgueb
  • 1,491
  • 11
  • 11
  • 1
    Yes, this should work, since the changes will be performed _later_ – that means in some next update cycle. This is just a workaround, but since this has something to do with internal implementation, there is no _correct way_ of doing that. – Tricertops Mar 09 '13 at 21:55
  • it's not actually a workaround; it is indeed much better than using `[NSThread sleepForTimeInterval:]` because it will not block the UI thread. there are lots of times in terms of transitions that it is necessary to delay a small bit to allow in-process animation to complete so that your animation can properly start (and in the proper location). and `dispatch_after()` (or `[obj performSelector:withObject:afterDelay:]`) is the way to do this without blocking. – john.k.doe Mar 10 '13 at 03:27
0

When posting notifications, I don't use your methods.

I use:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(changeButton) name:@"changeButton" object:nil]; on the receiving end.

And on the posting end, I call [[NSNotificationCenter defaultCenter] postNotificationName:@"changeButton" object:nil];

Try moving your code to the method:

-(void)changeButton{

UIBarButtonItem *newItem = [[UIBarButtonItem alloc] initWithTitle:@"New" style:UIBarButtonItemStyleBordered target:nil action:NULL];

[[[self navigationBar] items][0] setRightBarButtonItem:newItem animated:YES];

}

Hope this helps!

waylonion
  • 6,866
  • 8
  • 51
  • 92
0

Have you tried to performSelectorAfterDelay?

Try something like this:

- (void)applicationDidBecomeActiveNotificationAction:(NSNotification *)notification{
     [self performSelector:@selector(yourAnimationAction) withObject:nil afterDelay:1];
}

- (void)yourAnimationAction{
    //Set your NavAnimation here
    [self.navigationItem setRightBarButtonItem:theItem animated:TRUE];
}

Just change the Delay to fit your apps wakeup delay... maybe this will take the effect?

0

Try by moving [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidBecomeActiveNotificationAction:) name:UIApplicationDidBecomeActiveNotification object:nil]; to viewDidLoad: method. I think object is not registered for "UIApplicationDidBecomeActiveNotification" while it is fired.

Ab'initio
  • 5,368
  • 4
  • 28
  • 40
-1

Just call viewDidAppear: manually from within your applicationDidBecomeActiveNotificationAction: method. Place your animation code in the viewDidAppear: method. Hope this helps.