Window Management in Your Multitasking App

Session 246 WWDC 2019

Dive into the details of window management in your Multitasking app, including how to properly handle creating, refreshing, and closing windows. Hear about best practices for when to refresh the content in your window and learn how to ensure your app’s visual state is up-to-date in the switcher.

[ Music ]

Hi, everybody.

I'm Giovanni Tarducci, from the System UI SpringBoard team.

And today, I'm happy to tell you everything about managing your windows on iPadOS.

We are introducing three new API's, to do just that.

To respectively activate, refresh, or destroy any of your scene sessions.

But let's jump right into a demo of these API's at work, in an app we've been prototyping.

Okay. We call it Clown Town.

And it's a great new way to get a clown for your party.

It opens to a full-screen map, showing all the clowns in my network.

I can tap on any of their markers, to see more about them.

The implementation is straight forward, as well.

The detail view controller just knows how to show a clown with a given ID.

While the map view controller, delegates everything, presentation, animation and gestures to UIKit.

Through the presentviewcontroller animated API.

Well, with the enhancement to multitasking, we can push this delegation model to the next level.

And gain powerful new features for free.

Taxes may apply at checkout.

So, we did start by adding an "open in new window" button in the data view.

So, that all our users are going to be able to discover this great new feature.

When tapped, we call new request scene session activation API, to open this clown in an auxiliary dedicated window.

Let's do that.

Already, I can keep browsing my map and checking out and comparing other clowns.

But not only that, we can now have the whole system working for us.

We can resize the windows, move them around.

Keep opening new windows.

And the one I had on my side, is now in its own space in the switcher.

Now, I can make this one a slide over, move it around, stash it.

And maybe opening another couple details, Crusty and a last one, Mr. Happy here.

Okay, so, with the swipe app, I can check out my whole stack of slide overs.

And I can even quickly swipe through them all, just like that.

I can even go back from being a slide over, to being next to the map again.

Now, let's pause for a second.

We did achieve all of these, again, by delegating, presentation, animation and more, to the system.

So far, through the single request scene session activation API.

Now, I only now noticed that Mr.

Happy here has got a one-star rating.

Probably not a good clown.

So, let's get rid of him and his window, through new request scene session destruction API, which I'm calling for the upper right down button here.

Let's do that.

And he's gone for good.

Now, I remembered the one I had in my switcher, had a five-star rating.

So, let me open her again.

Notice how she wasn't duplicated.

This is Clown Town policy.

And it's easily enforced, just by asking the request session activation API to activate and existing session instead of a new one.

I can even track her availability.

She was checked and found available, as I can tell from the green navigation bar.

Also, an instant book button did appear on the left side.

If her availability were to change, I will get a notification.

I will update this UI to have the navigation bar now in red.

And I will call the new request scene session refresh API, to update, beside other things, the snapshot of this UI, for it to be represented in the switchers.

But I did not decide yet.

So, let us instead start tracking them all, just like this.

Say now that I do background the app, to do something else.

Maybe recommend this app to my friends.

And show the current collection I have right now.

But I'm getting notifications already.

A bunch of them are no longer available.

I cannot recall their names.

So, let me instead use app expose, to glance at all my windows at once.

And there we go.

Their snapshots were updated, which is great.

Because I can manage my window right here and there.

A couple of swipe ups and I'm done.

Looks like these clowns are running out fast.

So, let me go ahead and book the great BuBu LuBu, so we can go back to our party.

Oops, see you later Clown Town.

Okay, so...

[ Applause ]

Thank you.

[ Applause ]

So, let's check out the API's, activating a session.

First of all, you activate a session only in response to direct and local user interaction.

The user has to touch the screen for it to happen.

And you do so by calling the new request scene session activation API on UI application.

To either activate an existing session or a new one.

Now, in Clown Town, when we first launched, UIApplication had just one open session.

The one with our map configuration.

All in just window, displaying our whole view hierarchy.

When the open a new window button was pressed, through delegation, the detail view controller gets to the map view controller, which is presenting it.

And the map view controller calls this method that we added on our application delegate.

Here, since we have the no duplicate policy, we check for any existing session for this given clown.

Now, on first launch, we won't find any.

So, we're going to end up passing nil here, at runtime.

Pass nil here, to this parameter, requests a brand-new scene session to be created.

We then create a userActivity for this clown.

And the userActivity is going to be given back to us by UIKit, later on along the delegate chain, as we'll see.

The options objects, let's me specify a requesting scene, which is meant to be the one where this request was user initiated.

The system uses this information to avoid replacing the requestingScene with the activated one.

And for other navigation purposes.

We can now call the API.

The new window is shown.

And alongside, a new session hierarchy has been created.

As I mentioned, there are two key delicate methods you definitely want to implement, in order for your app to have a say in what actually gets created.

And how it gets configured.

So, let's step back and go back to when we called the API.

As soon as we do so, UIKit creates a brand-new scene session.

And let's you specify a configuration for it, by calling configuration for connecting scene session on your app delegate.

You definitely want to implement this one.

And here, you can inspect the user activity.

Which is given back to you now through the UI scene connecting options, to pick a session.

In my case, it's the detail configuration.

Now, if your configuration specifies a storyboard, as I do recommend.

At this point, UIKit is able to go ahead and create the whole view hierarchy.

And you just need to configure that.

You do so by implementing scenewillConnectToSession on your scene delegate.

In there again, you'll be able to find your user activity and the connecting options.

And you configure your window and view controller hierarchy for it.

Now, that was a new session.

What about an existing session?

If the session is still is existing, we go straight to the scene delegate.

And if the session had been disconnected in the meantime, we call sceneWillConnectToSession.

But if the scene is still connected, we'll just go to the continueUserActivity one.

You definitely want to implement those.

To recap, you activate a session, only in response to direct user request.

To either activate a new or existing session.

And you want to implement your app and scene delegates methods, to be able to configure both session and the window for the activity at hand.

Now, onto refreshing a session.

You refresh for user-relevant updates from your app.

A couple of examples.

It could be that you have multiple windows showing and working on the same assets.

And the user may have modified them from a window or even another device.

And now you want to keep them in sync.

You want to have their presentation being updated in these features and more.

Or you just fetched new data and it is available for the user to be seen.

Or you want to update some scene and session metadata, as we'll see.

You do so by calling at any time, the requestSceneSessionRefresh API on UIApplication.

By just passing in the session you wish to be refreshed.

Now, what is it actually that this API affords you to update?

You can update the state restoration user activity for the session.

You can update the scene activation conditions.

And your UI, which is eventually going to be captured in a snapshot, again.

As we saw in Clown Town, thanks to those updated snapshots, the app felt alive in the switcher.

And we were able to confidently act on the incoming data.

Without having to navigate to each and every session, just to find out, once connected and updated, that we didn't even want them anymore.

On an architectural note, you want to listen for rare model changes for which you want to call the API.

Both in the interested view controller, as well as in a long-lived object.

Because if the scene is still connected, either in the foreground or the background, it can listen to the notification itself.

And call the API, which is going to do the right thing internally.

If the scene has been disconnected though, the view controller won't be there anymore.

And so, the long-lived object can step in, figure that out and call to refresh API in its place.

The scene is going then to be background connected.

And the view controller will have a chance to update itself.

And the snapshot will be captured.

As a summary, you want to refresh for user relevant updates from your app.

Make your layout time fast, so that we can quickly capture the snapshot.

And do not rely on it being executed immediately.

The system reserves the ability to fulfill this request at a later point in time, if necessary.

Now, destroying a session, also known as going away with style.

You destroy a session for direct user request.

Or if you have a window, which is an auxiliary window dedicated to show a piece of data, an item, that the user already deleted from somewhere else.

Another window or even another device.

You do so by calling, at any time, the requestSceneSessionDestruction API, on UIApplication.

Which takes in the session you wish to be destroyed.

As well as an options object.

This option lets you specify a DismissalAnimation.

Which the system is going to take into account, if the scene happens to be foreground at that moment.

Now, let me be clear, the session is going to go away and won't come back.

But the animation lets you acknowledge the user's intent in getting rid of it.

So, which one to pick, then?

The main draft is a great paradigm.

When the main draft is cancelled, without saving, the user is explicitly asking for its content to be destroyed.

Which is the common case, when getting rid of a window, as we saw in Clown Town.

You pick the standard animation, in these cases, to acknowledge this intent.

When to draft the assent, the user is not asking for his content to be destroyed.

The session though, will be destroyed as a side effect of the user accepting the final purpose of the session.

Which, in the mail case, is sending the e-mail.

In the Clown Town case, was booking the clown, if you are in tracking mode.

You pick commit to acknowledge that the final purpose of the session has been approved by the user.

Now, when the draft is cancelled and saved, the user again, is not asking for his content to be destroyed.

The session though, will be destroyed as a side effect of the user declining from taking action right now.

You pick decline, in these cases, to acknowledge that the final purpose of the session has not been fulfilled yet.

To recap, you destroy a session in response to user request.

Either locally or remotely.

And you pick the animation style to best acknowledge the user's intent in getting rid of it.

To recap, we've seen how through these new API's, you can empower your user's actions.

And you can delegate complexity to the system, while gaining powerful new features, easily discoverable by all your users.

Thank you.

[ Applause ]

Apple, Inc. AAPL
1 Infinite Loop Cupertino CA 95014 US