Huvi: Initial Logo Animation

Huvi’s splash screen displays the logo over a white background. Once the app launches, the user is taken to either the login/signup screen, or to the app’s “home” screen. In either case, I think the default “fade into a new screen” transition was a bit janky. The logo was right there, and then it just faded into a new location.

Good apps should feel “alive”, rather than like a collection of static views. Just because it’s easy to lay out an app’s flow in a storyboard, that’s no excuse for ignoring how those screens relate to one another, and creating pleasing transitions between them. The way a scene interfaces with other scenes is an important detail, and paying attention to those transitions can really help an app feel polished. There isn’t much that can’t benefit from a subtle bit of animation.

So, back to Huvi. Here’s what I accomplished for the transition from the splash logo to the initial screens:

huvi-splash-logo-transition

Getting this done was more of a pain than it should have been, but still reasonably straightforward. When iOS launches an app, it loads the launch screen NIB and displays a view from that NIB. Trouble is, iOS owns that view, and your program doesn’t get any access to it. I used autolayout for the launch screen and the initial screen, so there’s no way to know exactly where the logo is at compile time, but worse yet, without access to the launch screen, I don’t even really know where it is at runtime, either.

The first part of the solution was to make the initial view controller load the launch screen NIB and display it above all the other content. This, in effect, means that when iOS launches our app, it’s fading from the launch screen to… the launch screen, with the important caveat that we own the new view, and can do stuff with it.

So the app has launched, and we’re still displaying the launch screen, but it’s OUR launch screen, not the system’s. The next problem is that we have two logos – one from the launch screen and one from the login view. We also have two different coordinate systems to deal with when animating the logo, so some coordinate conversion is a must as well. Here’s how the code looks to accomplish that:

#pragma mark - Huvi Logo Animation
-(void)animateLogoFromLaunchScreenToView:(UIView*)targetView inView:(UIView*)view {
    // Setup initial animation from launch screen
    UIView* launchView = [[UINib nibWithNibName:@"LaunchScreen" bundle:nil] instantiateWithOwner:nil options:nil][0];
    UIView* logoView = [launchView viewWithTag:1];

    [view addSubview:launchView];
    [launchView pinToSuperview];
    [view layoutIfNeeded];

    targetView.hidden = YES;

    self.didAppearBlock = ^{
        [UIView promiseWithDuration:0.35 delay:0.35 options:UIViewAnimationOptionCurveEaseInOut animations:^{
            logoView.frame = [targetView.superview convertRect:targetView.frame toView:view];
            launchView.backgroundColor = [UIColor clearColor];
        }].then(^(){
            [launchView removeFromSuperview];
            [logoView removeFromSuperview];
            targetView.hidden = NO;
        });
    };
}

pinToSuperview is an extension method I wrote to set explicit autolayout constraints equal to the frame of a view’s superview.

self.didAppearBlock is an optional block of code that this view controller will execute when viewDidAppear gets called. There was a good reason for setting up the view lifecycle to use blocks this way, but I can’t remember what it was off the top of my head.

In all, the code required here wasn’t tricky, and PromiseKit made it even a bit easier, but it’s a good example of how treating views as fluid things, which have to interact with each other as well as the user, can add some sheen to an otherwise unremarkable section of UI.