Search code examples
objective-ccocoaanimationcore-animationnsview

Animating sibling NSViews


Background & Goal

I have a window and three sepearate views in a nib file in IB. The three views are used for the three 'parts' of the program: the first were the user drops a file, the second where it chooses some options and the third where it is presented with a progress bar as the program accomplishes a task.

I want to animate between those views as they change, and I'm doing a fade-in/out animation. The goal is to have the old view (the one that is going to disappear) fade out as the new view fades in. The views have different sizes, so I animate the window frame as well at the same time (those views fill up the window). All three views have Core Animation enabled in IB, but nothing else (the window's content view for example) has.

Code

At the start of the program, I register the views' sizes so I can use them later, add the views to the window and set the 2nd and 3rd view hidden.

- (void)awakeFromNib
{
    // Register native views' size (when they get displayed again the window can be set to their bounds)
    view1NativeSize = [firstView bounds].size;
    view2NativeSize = [secondView bounds].size;
    view3NativeSize = [thirdView bounds].size;

    // Add views to the main window's content view
    NSRect viewFrame = [[mainWindow contentView] bounds];
    [firstView setFrame: viewFrame];
    [secondView setFrame: viewFrame];
    [thirdView setFrame: viewFrame];

    [[mainWindow contentView] addSubview: firstView];
    [[mainWindow contentView] addSubview: secondView];
    [[mainWindow contentView] addSubview: thirdView];

    // Set their attributes
    [secondView setHidden: TRUE];
    [thirdView setHidden: TRUE];

    currView = 1;
}

Then I have three methods for swapping between views, where I calculate the new window frame and call a method for the animation.

- (IBAction)goToFirstView: (id)sender
{        
    NSRect newFrame = [mainWindow frame];
    newFrame.size = view1NativeSize;
    newFrame.size.height += [self titleBarHeight]; // Method that returns the title bar height

    if (currView == 1) {
        return;
    }
    else if (currView == 2) {
        [self animateFromView: secondView toView: firstView andWindowFrame: newFrame];
        currView--;
    }
    else if (currView == 3) {
        [self animateFromView: thirdView toView: firstView andWindowFrame: newFrame];
        currView -= 2;
    }
}
- (IBAction)goToSecondView: (id)sender
{
    NSRect newFrame = [mainWindow frame];
    newFrame.size = view2NativeSize;
    newFrame.size.height += [self titleBarHeight];

    if (currView == 2) {
        return;
    }
    else if (currView == 1) {
        [self animateFromView: firstView toView: secondView andWindowFrame: newFrame];
        currView++;
    }
    else if (currView == 3) {        
        [self animateFromView: thirdView toView: secondView andWindowFrame: newFrame];
        currView--;
    }
}
- (IBAction)goToThirdView: (id)sender
{
    NSRect newFrame = [mainWindow frame];
    newFrame.size = view3NativeSize;
    newFrame.size.height += [self titleBarHeight];

    if (currView == 3) {
        return;
    }
    else if (currView == 1) {
        [self animateFromView: firstView toView: thirdView andWindowFrame: newFrame];
        currView += 2;
    }
    else if (currView == 2) {
        [self animateFromView: secondView toView: thirdView andWindowFrame: newFrame];
        currView++;
    }
}

The method for the animation is pretty straightforward. I animate the views.

- (void)animateFromView: (NSView*)oldView
                 toView: (NSView*)newView
         andWindowFrame: (NSRect)newWindowFrame
{
    [NSAnimationContext beginGrouping];
    [[NSAnimationContext currentContext] setDuration: 0.5];

    [[newView animator] setHidden: FALSE];
    [[oldView animator] setHidden: TRUE];
    //[[mainWindow animator] setFrame: newWindowFrame]; --Doesn't work??

    [NSAnimationContext endGrouping];

    [mainWindow setFrame: newWindowFrame display: YES animate: YES];
}

Questions

I have two problems with this (most important first):

  • On the third view there is a progress indicator and it never displays correctly. It doesn't update! I call -startAnimation: but it doesn't move at all. If i turn off CA on that third layer it won't fade in/out correctly but the progress bar starts to work (animate). No clue why...

  • In my numerous experiments trying to solve the first problem I tried turning on CA for the window's content view and turning off CA for the three subviews. The first problem wasn't solved, but I noticed something: that way the old view was fading out AS the new view faded in, while before that change (subviews with CA) I could only notice the new view fading in. I recorded it and watched it in sloooowww-moootion :) and found I was right: the old view magically disappears and all I see is the new view fading in. It's funny I only found that after watching the animation I really wanted. Am I supposed to turn CA on only for the content view or for all three subviews separately? (or is this problem from something else?)

I tried many things like turning on CA for the progress bar. I found also many questions here that are close to this one but don't specifically address the progress bar problem.

Good luck finding a solution :)


Solution

  • To handle your crossfade you could just use a CATransition as explained in this answer but for a crossfade you wouldn't need to set the transition type as it is the default.

    I'm afraid I don't have an answer for the progress bar problem though, it may be something to do with the unorthodox way that you're doing the crossfade.