• Dynamic iOS Multitasking

    Tuesday, Jan 14, 2014

    Intro

    iOS 4 brought us the wonderful feature to switch between apps quickly, and iOS 7 made this even more interactive through use of live previews of apps.

    I noticed an article written by @vpdn shared on twitter about making this switcher a little more lively through use of curated app content for multitasking. In other words, re-purposing the iOS 7 switcher as a sort of dashboard / widget interface for your applications.

    I wanted to try and further extend this.

    ALL the Apps

    The iOS 7 switcher is, as I see it, Exposé (for the Mac) brought to iOS. It gives you an overview of everything that's running (and that has been running) sorted in order of usage. This is displayed through previews of applications, as well as their respective icons and titles. However, it stops there, and doesn't really provide any insight to anything else.

    Extensions

    A neat example of extending this functionality is re-purposing them as widgets, for example, Facebook or Instagram. Imagine a user switching from the twitter app and noticing a widget like Facebook displaying some photos they were just tagged in. Another usage could be to show coverart for the currently playing as another form of heads up display. These give the switcher a more personalized and unique experience (than simply images which are basically just the app's Default.png).

    The Fun Part

    In order to conserve battery life, and free up CPU time, iOS throttles down applications that have been dismissed; videos stop playing, NSTimers get paused, applications don't get any updates. When the app enters background state a current snapshot of the application's window is performed and saved to disk to be shown as the preview:

    // Depending on orientation it'll append -Portrait, -LandscapeLeft, etc. to the image saved 
    [[UIApplication sharedApplication] _saveSnapshotWithName:@"UIApplicationAutomaticSnapshotDefault"];
    

    This is just a static image, and the rendering context for your application is paused when the application enters background state. So, currently, there's no way to alter that without breaking your app (or making Apple's App Review very angry face with you), or so I thought...

    See with iOS 7 they introduced some awesome backgrounding features, one of which being Background App Refresh! This allows developers to update their applications in the background (like Facebook, Twitter, etc.) to save users a few "pull to refreshes". When the user returns to an app that's been updated in the background, it'll always feature the latest content.

    When a backgrounded app is refreshed, the OS does something like this:

    1. Notification for background fetch comes in for application
    2. Wake application using -[UIApplication _shouldFakeForegroundTransitionForBackgroundFetch]
    3. Application handles network request for update
    4. Application refreshes UI to match said request
    5. Snapshot the whole window! (-[UIApplication _saveSnapshotWithName:] shown above)
    6. Send application back to background
    7. Show snapshot in Multitasking.

    During this it eventually calls (with proper parameters obviously):

    [[UIApplication sharedApplication] _replyToBackgroundFetchRequestWithResult:0
                                                        remoteNotificationToken:@"token"
                                                                 sequenceNumber:@(someNumber)
                                                      updateApplicationSnapshot:(YES || NO)]; // ohai
    

    So here's the fun part, this can be horribly abused :D!

    Let's re-purpose the app switcher preview!

    Whenever we refresh the app in the background, why not just throw a new UIView on top of everything to display ...and hook everything up to a CADisplayLink? >:D

    /* UIApplicationDelegateSubclass.m */
    - (void)applicationDidEnterBackground:(UIApplication *)application 
    {
      _backgroundID = [application beginBackgroundTaskWithExpirationHandler:^{}];
    }
    
    - (void)applicationDidBecomeActive:(UIApplication *)application 
    {
      [application endBackgroundTask:_backgroundID];
    }
    
    
    /* UIViewControllerSubclass.m */
    - (void)viewDidLoad 
    {
      [super viewDidLoad];
      [CADisplayLink displayLinkWithTarget:self selector:@selector(updateALLTheThings)];
    }
    
    - (void)updateALLTheThings 
    {
      [[UIApplication sharedApplication] _replyToBackgroundFetchRequestWithResult:0
                                                  remoteNotificationToken:@"no u"
                                                           sequenceNumber:@(1337)
                                                updateApplicationSnapshot:YES];
    }
    

    Tada! Dynamic application previews in Multitasking!

    1. Don't /ever/ do this. :P
    2. If you try this on backgrounded GL applications, it'll explode and crash.
    3. This is not App Store-safe and will be an insta-deny.

    The Sane Approach (aka App Store-safe-ish)

    Since all of this is governed by the background app refreshes, and your UI will automatically update anyways... why not put that to good use?

    Take Instagram for example, you're always going to see a timeline of photos in the app, and seeing a tableview as an application preview doesn't really make sense. A more appropriate preview could be a photo slideshow for your timeline (kinda like Facebook Home). To prove my point, I made a demo of it here.

    Basically when my demo goes into the background, a background task is created for an indefinite amount of time (10mins most likely, until watchdog or another application kills it), and when it resumes said task is cancelled. By doing this I can keep the application running (swapping photos in the slideshow) and just apply them as an overlay.

    However, one could do this without using private API (and no, it's not witchcraft):

    - (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler 
    {
      // Update my preview and do cool stuff here
      // Maybe add a UIImageView on top of everything?
      completionHandler(UIBackgroundFetchResultNewData);
    }
    

    Obviously you'll need to setup iOS 7 backgrounding by altering your Info.plist and such (you'll find more of that in the iOS 7 documentation).

    In addition, you could always try providing a different overlay to summarize information last displayed in your application through use of:

    - (void)applicationWillResignActive:(UIApplication *)application
    

    Give it a go! Might be neat to see a more personal iOS 7 App Switcher :)