17

In my app, I have a page view controller that allows the user to swipe between different "sections" of the app, and at the top in the nav bar I change the title text to the new section the user has swiped to via pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted:. It is currently instantly changing the title text when the animation has completed. I would like to improve this with some animation, a subtle fading in and out effect.

I first tried to implement [UIView animationWithDuration:...] to animate changing the title text, but it does not animation and simply still updates instantly.

I then wondered if it'd be possible to update the alpha of the nav bar title as the user scrolls horizontally based on how far they've scrolled, reaching 0 alpha when the next section is about to come on screen, then I can instantly change the text while it's at 0 and then quickly fade in to 1 alpha. But I don't see a method on UIPageViewControllerDelegate that is called when the scroll position has updated.

If possible, instead of just fading in and out, I could fade as well as move the title text position in the nav bar like the default animation that occurs when navigating back from a push segue via a swipe gesture. I would slide the old section title over as the user scrolls and provide the next section title on the other side, so that when the transition completes the previous section title is off screen and the new one is perfectly centered so the replacement completes. But again this requires knowing exactly how much the user has scrolled the page view controller.

Is it possible to implement any of the desired animations?

Jordan H
  • 52,571
  • 37
  • 201
  • 351
  • Have you tried using a custom view for your navigation title? self.navigationItem.titleView = customNavTitleLabel; – Zhang Jul 03 '14 at 01:55
  • @Zhang I haven't, I'll investigate that. Is there a way to get the `UILabel` from the existing title so that I can grab the default one (it changes with Bold Text enabled), change it, and set it to the tweaked one? – Jordan H Jul 03 '14 at 02:02
  • I think I have seen people use some crazy recursive loop to find the nav title subview :P you can try that if you want. – Zhang Jul 03 '14 at 02:10
  • Thanks @Zhang. I did ultimately decide to create a custom title and animate it using the page view controller's hidden scrollView's delegate method. It's working beautifully. – Jordan H Jul 03 '14 at 06:18

5 Answers5

48

If you want to animate between different title strings, use the following:

CATransition *fadeTextAnimation = [CATransition animation];
fadeTextAnimation.duration = 0.5;
fadeTextAnimation.type = kCATransitionFade;

[self.navigationController.navigationBar.layer addAnimation: fadeTextAnimation forKey: @"fadeText"];
self.navigationItem.title = "My new title";

You can adjust the duration and set a timing function to suit, of course.

There are also other types of animation that might work in different circumstances (thanks @inorganik):

kCATransitionFade
kCATransitionMoveIn
kCATransitionPush
kCATransitionReveal
Ashley Mills
  • 50,474
  • 16
  • 129
  • 160
  • 1
    And just a note for the uninitiated, if my experience is definitive, you'll need to add the QuartzCore framework via [project]>[target]>Build Phases>Link Binaries>QuartzCore.framework. – Jack Bellis Mar 29 '16 at 18:25
48

Swift 5

let fadeTextAnimation = CATransition()
fadeTextAnimation.duration = 0.5
fadeTextAnimation.type = .fade
    
navigationController?.navigationBar.layer.add(fadeTextAnimation, forKey: "fadeText")
navigationItem.title = "test 123"

For convenience, Ashley Mills solution in Swift:

11/16/2016 Updated for Swift 3 (Thanks to n13)

let fadeTextAnimation = CATransition()
fadeTextAnimation.duration = 0.5
fadeTextAnimation.type = kCATransitionFade

navigationController?.navigationBar.layer.add(fadeTextAnimat‌​ion, forKey: "fadeText")
navigationItem.title = "test 123"

Swift 2.x

let fadeTextAnimation = CATransition()
fadeTextAnimation.duration = 0.5
fadeTextAnimation.type = kCATransitionFade

navigationController?.navigationBar.layer.addAnimation(fadeTextAnimation, forKey: "fadeText")
navigationItem.title = "test 123"

I tip my hat to Ashley!

Community
  • 1
  • 1
Frederik Winkelsdorf
  • 4,383
  • 1
  • 34
  • 42
  • 1
    Swift 3 navigationController?.navigationBar.layer.add(fadeTextAnimation, forKey: "fadeText") – n13 Nov 11 '16 at 17:31
  • 1
    Noticed the docs on the `type` property. "The name of the transition. Current legal transition types include fade, moveIn, push and reveal. Defaults to fade", so the `type` property if sat to fade is unnecessary. – Oscar Apeland Oct 09 '17 at 13:20
  • 1
    @OscarApeland IIRC it was not always `fade` when used with `UISplitViewControllers`. That might have changed with newer iOS Versions. I always opt for defensive programming, if I know exactly what I want and need it, explicitly request it. But thanks for making aware of a possibly unneeded declaration anyway! – Frederik Winkelsdorf Oct 11 '17 at 17:14
  • @dimpiax Thank's for adding the revision for Swift 5, but adding a comment that Swift 3 version still works should have been sufficient if you don't change anything, shouldn't it? – Frederik Winkelsdorf Dec 17 '19 at 17:30
  • @FrederikA.Winkelsdorf old stuff will be deprecated, and code should be stay in fresh state. – dimpiax Dec 18 '19 at 09:48
  • @dimpiax Don't know what the answer means in this context: Compare your Swift 5 code to the Swift 3 code. It's identical ;) – Frederik Winkelsdorf Dec 18 '19 at 10:16
  • @FrederikA.Winkelsdorf check `fadeTextAnimation.type`. – dimpiax Dec 18 '19 at 17:21
  • @dimpiax I see, missed that one. Sorry, my fault. Thanks for the follow up! – Frederik Winkelsdorf Dec 18 '19 at 17:38
2

A solution is to create a custom title and animate its position using the page view controller's hidden scrollView's delegate method. As Zhang stated, the custom title is simply self.navigationItem.titleView = customNavTitleLabel;

Jordan H
  • 52,571
  • 37
  • 201
  • 351
1

If you need to animate only title (not the whole nav bar)

Develop custom title control Link for example

Assign

  let titleView = UIAnimatedTitleView(frame: CGRect(x: 0, y: 0, width: 200, height: 40))
 titleView.text = "Hello"

Animate

var flag = true

@objc private func animateNavigationTitle() {
            guard let titleView = navigationItem.titleView as? UIAnimatedTitleView else { return }

        let fadeTextAnimation = CATransition()
        fadeTextAnimation.duration = 0.5
        fadeTextAnimation.type = kCATransitionPush
        fadeTextAnimation.subtype = kCATransitionFromTop

        titleView.layer.add(fadeTextAnimation, forKey: "pushText")
        titleView.text = flag ? "Hello" : "Good buy" 
        flag = !flag
    }
Svitlana
  • 2,938
  • 1
  • 29
  • 38
0

Found the best way is this category:

@implementation UIViewController (ControllerNavigationEffects)

-(void) setNavigationTitleWithAnimation:(NSString *) title {
    if ([self.navigationItem.title isEqualToString:title]) {
        return;
    }
    @weakify(self);
    float duration = 0.2;
    [UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
        @strongify(self);
        self.navigationItem.titleView.alpha = 0;
    } completion:^(BOOL finished) {}];

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        @strongify(self);
        self.navigationItem.titleView = nil;
        self.navigationItem.title = title;

        [UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
            self.navigationItem.titleView.alpha = 1;
        } completion:nil];
    });
}

-(void) setNavigationTitleViewWithAnimation:(UIView *) titleView {
    if ([self.navigationItem.titleView isKindOfClass:[titleView class]]) {
        return;
    }
    @weakify(self);
    float duration = 0.2;

    CATransition *fadeTextAnimation = [CATransition animation];
    fadeTextAnimation.duration = duration;
    fadeTextAnimation.type = kCATransitionFade;

    [self.navigationController.navigationBar.layer addAnimation: fadeTextAnimation forKey: @"fadeText"];
    self.navigationItem.title = @"";


    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        @strongify(self);
        self.navigationItem.title = @"";
        self.navigationItem.titleView = titleView;

        [UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
            self.navigationItem.titleView.alpha = 1;
        } completion:nil];

    });
}
MCMatan
  • 8,623
  • 6
  • 46
  • 85