How to control the Preview Screenshot in the iOS Multitasking Switcher

screenshot

Since iOS 7, when you double-tap the home button, little preview screens are shown above the app icon. Swipe them up and the app is closed. Those preview screens are not live though, they’re simple screenshots that iOS takes before sending an app into the background.

Sometimes it’s not desirable to display confidential information on those preview screens. There is no real way to prevent iOS from taking those screenshots, but it’s easy to detect when an app is sent to the background and quickly change our display before this happens. That way the screenshot is taken of something that we can control.

Method 1: crude yet simple

In its simplest form we could just hide the main window when this happens, and bring it back when the app enters the foreground. We’ll do this in our AppDelegate.m, in applicationWillResignActive and applicationDidBecomeActive respectively:

- (void)applicationWillResignActive:(UIApplication *)application {
    
    // hide main window
    self.window.hidden = YES;
}

- (void)applicationDidBecomeActive:(UIApplication *)application {
    
    // bring main window back
    self.window.hidden = NO;
}

This will result in our app showing a black screen instead of our “real” content.

Method 2: elegant and subtle

The problem with the above approach is that the transition between the black screen and our app’s content is a plain cut, and because everything else in iOS transitions nicely with fades and slides, the cut looks a little out of place.

One solution is to create our own UIView and overlay our interface with it. UIViews can be faded in and out thanks to their alpha values and a UIView singleton. As an added benefit we get to choose which colour we’d like our screen to be – just in case black isn’t desirable. Let’s use white instead:

- (void)applicationWillResignActive:(UIApplication *)application {

    // fill screen with our own colour
    UIView *colourView = [[UIView alloc]initWithFrame:self.window.frame];
    colourView.backgroundColor = [UIColor whiteColor];
    colourView.tag = 1234;
    colourView.alpha = 0;
    [self.window addSubview:colourView];
    [self.window bringSubviewToFront:colourView];
    
    // fade in the view
    [UIView animateWithDuration:0.5 animations:^{
        colourView.alpha = 1;
    }];
}

- (void)applicationDidBecomeActive:(UIApplication *)application {

    // grab a reference to our coloured view
    UIView *colourView = [self.window viewWithTag:1234];
    
    // fade away colour view from main view
    [UIView animateWithDuration:0.5 animations:^{
        colourView.alpha = 0;
    } completion:^(BOOL finished) {
        // remove when finished fading
        [colourView removeFromSuperview];
    }];
}

This looks more complicated than it really is: in the first method we’ll create a standard UIView called colourView and give it the colour of our choice. We’ll also set the alpha value to 0 so it’s transparent. Since we won’t have a reference to this view we’ll give it a tag so we can identify and fade it out again later. 1234 is completely arbitrary – pick your favourite integer here.

As we bring up the view and add it to the main window’s view it’s invisible. animateWithDuration will fade it in over the course of 0.5 seconds, resulting in a subtle fade in. Now we’ll see white and iOS takes a screenshot – no cuts, no popping.

When we get back from the background we’ll first grab a reference to our view – thanks to the arbitrary tag this is really easy. Next we’ll use the same animateWithDuration method and fade the alpha value back to 0 over the course of 0.5 seconds. Longer durations work fine here too, but anything over 2 seconds gives the impression of lag and slowness.

The animateWithDuration comes in two flavours, one of which gives us a completion block so we can execute code when the fade is done. We’ll take this opportunity to remove our view rather than let it linger there in its transparent appearance.

Method 3: bring your own picture

If a solid colour is not snazzy enough we can replace the UIView with a UIImageView and initialise it with a snazzy picture:

- (void)applicationWillResignActive:(UIApplication *)application {

    // let's use a picture instead
    UIImageView *colourView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"snazzy-picture.png"]];
    
    [self.window addSubview:colourView];
    [self.window bringSubviewToFront:colourView];
    
    // fade in the view
    [UIView animateWithDuration:0.5 animations:^{
        colourView.alpha = 1;
    }];
}

The fade out method is the same. You may need to check device orientation and size here to provide a picture that covers the entire view, or use one that’s 2048×2048 to be on the safe side.

Many developers (including PayPal) choose to grab a screenshot of your app manually at this point and apply a blur effect as if the screenshot is presented through frosted glass.

Demo Project

I’ve added a sample project to GitHub so you can see the above in action:

8 thoughts on “How to control the Preview Screenshot in the iOS Multitasking Switcher

    1. Hi Jack, the third method is essentially the same as the second method. What I did not show with the third method is that on applicationDidBecomeActive, you must also remove your image view again – otherwise it will stay in place when the app comes back into the foreground.

      Here are the two methods you need:

      - (void)applicationWillResignActive:(UIApplication *)application {
        
          // fill screen with your picture
          UIImageView *colourView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"snazzy-picture"]];
          colourView.tag = 1234;
          colourView.alpha = 0;
          
          [self.window addSubview:colourView];
          [self.window bringSubviewToFront:colourView];
          
          // fade in the view
          [UIView animateWithDuration:0.5 animations:^{
              colourView.alpha = 1;
          }];
      }
      
      - (void)applicationDidBecomeActive:(UIApplication *)application {
         
          // grab a reference to your image view
          UIView *colourView = [self.window viewWithTag:1234];
          
          // fade away colour view from main view
          [UIView animateWithDuration:0.5 animations:^{
              colourView.alpha = 0;
          } completion:^(BOOL finished) {
              // remove when finished fading
              [colourView removeFromSuperview];
          }];
      }
      

      That should do it 😉

      1. This is not a complete solution. The problem with just fading the view in, is that it can still be fading – i.e. the underlying screen can still be seen – when the screen shot is taken. This can be seen if you app-switch with 4-finger swipe left or right.

        A full solution is the following:

        1. applicationWillResignActive
        fade view in. When bringing up the multi-tasking menu only this method will be called and we have a smooth transition.
        2. applicationDidEnterBackground
        set view.alpha = 1.0 to complete the animation if it is still in-flight. This is required as the screen shot is about to be taken. This will additionally be called when pressing the home button once or app switching with 4 fingers left/right.
        3. applicationDidBecomeActive
        fade the view out.

        1. Thanks for the suggestion, Rohinton – good call! I must admit I’ve never had an issue with the screenshot being taken before the animation is finished, but then I keep my animations to 0.5 seconds or less. So I always get the screenshot on black. But for anyone who runs into issues, your solution is perfect!

  1. The second solution works great! Let’s suppose the app is in background and the user switches the orientation of the device. Is there a way how I can react to that event, even though the app is in the background, and adjust the appearance of the UIView displayed?

    1. Hi Michael, very good question indeed. Ordinarily you can handle device rotation using size classes, using the viewWillTransitionToSize method. Iv’e explained how to do that here: http://pinkstone.co.uk/how-to-handle-device-rotation-since-ios-8/

      However, I believe this method is only called when the app is in the foreground. I’m pretty sure it won’t be called when the app is in the background, but do try it out and let me know if it works. An alternative approach should be to check what size the current interface is when the app is about to come back into the foreground and react to the faux-rotation there. So in appplicationDidBecomeActive, you can check the bounds of your custom view and compare it to the window.

      Good luck 😉

  2. Is this working on iOS 10.1.1 ? I downloaded the git project and tried to run on iPhone 6plus with iOS 10.1.1. I start the app and do the app switching and I see the content.

Leave a Reply