Taking iPad Apps for Mac to the Next Level

Session 235 WWDC 2019

macOS Catalina provides an easy way to bring your iPad app to the Mac while maintaining your single code-base. Hear about ways in which you can take your app beyond the default behaviors to optimize its interface for the Mac. Get an overview of APIs you can use and macOS design guidelines that need to be considered. Learn how the iPad app lifecycle comes across on the Mac, and get distribution details for your application.

[ Music ]

Good afternoon.

[ Applause ]

So, my name is Jamie Montgomerie.

I work in the UIKit team.

I'm going to be joined by colleagues Glen, Nils and Chris.

Together, we're going to tell you about taking iPad apps on the Mac to the next level.

In our first talk Introducing iPad Apps for the Mac, we covered the basics.

We showed you how to get your app building and discuss some of the key API differences that you should be aware of.

In this talk, we're going to show how to go further and make your app better for Mac, cover multiplatform differences, talk a little about some design considerations, talk about how the application life cycle differs for Mac and discuss how you can distribute your app.

So let's talk about making your UIKit app a better Mac app.

The first thing to remember is that better iPad apps are better Mac apps.

So we'll talk about some of the things you can do to make your app better on iPad and on Mac.

On iPad, your app runs in everything, from Slide Over in iPad mini to our beautiful 12.9-inch iPad Pros.

But now running on Mac, that's not all.

Your app could be running full screen on a 27-inch iMac, and that's with a 77% skill factor.

In fact, the 27-inch display is more like a 35-inch iPad.

So you can fit a lot in that window.

So use best practices, support dynamic type and use Auto Layout, and lastly, remember that on the Mac app resize fast.

Can your layout code run at 60 frames per second?

If not, you should check it out using our Instruments performance tool.

Next, implement great keyboard support.

All Macs have a keyboard and Mac users' hands are always near them.

It's increasingly common for iPads to have keyboards too.

You can use our UIKeyCommand class to respond to keyboard shortcuts.

You can respond to standard responder actions in your view controllers, things like the cut, copy and paste methods.

And look after your responder ping.

Set first responder so that key commands go to the right places.

And this will help at iPad too.

If your app is a game, consider supporting game controllers that will make the experience better for your iOS and your Mac users.

So a couple of years ago, we introduced some great drag and drop APIs in UIKit.

IPad users now expect drag and drop, and Mac users have expected it for a long time.

Making great use of UI drag interaction and UI drop interaction will make your Mac and your iPad users happy.

Now this one is always good advice.

Use the latest APIs.

If you haven't done it for a while, now is the time to examine your app and look at your use of deprecated APIs.

I said it's always good advice but it's even better advice this year as we discussed in our introductory talk many of the deprecated iOS APIs are simply not available on Mac.

So use WKWebView not UIWebView, use Metal not OpenGL ES.

We've been working hard on these modern alternatives and using them enables you to take advantage of all that work in iPad and on Mac.

Lastly, support our new iOS 13 features.

For example, if you support multiple windows in your iPad app, every UI window scene will be a window on Mac.

Support Dark Mode on iPad and you'll support Dark Mode on Mac too.

I could call it some specific talks here but there are a lot this year.

So go and have a browse and watch the ones that you're interested in, remembering better iPad apps make better Mac apps.

So, better iPad apps or better Mac apps but better Mac apps also contain refinements just for Mac.

So we'll take a look about what you can do to make your UIKit app feel even more at home on Mac.

There are a whole range of things here.

They spend the gamut from making more use of existing UIKit APIs to enable unique Mac features to some APIs that are completely new on Mac, or at least completely new to UIKit developers.

We're going to take a quick look at the breadth of APIs available to you now.

So what's the first thing that you think of that's unique to Mac?

For me, it's the Menu Bar.

Global Menu Bars are unique to Mac.

They store all the possible actions your user can take in one place and neatly tucked away at the top of the screen.

They're global to the app and the items in them are enabled or disabled based on what the user is doing.

So for your iPad app on Mac, you can just accept the default Menu Bar.

No extra work is required.

You can also compose a Menu Bar in Interface Builder as we saw in our first session.

Or for field control, you can customize in code.

So I spoke about UIKeyCommand earlier.

If you're a sharp-eyed API dev reader, you'll have noticed that this year has a new UI command super class.

And this along with UIMenu and UIMenuBuilder enable you to take full control over the Mac Menu Bar.

We're going to see a demo of this in a moment.

One neat thing about this API is that UIKeyCommands that you use in Menus will be available in iPad too.

They'll be shown in the discovery builder you get when you hold on the Command key.

So if you use this API to make your app a better Mac app, you'll be making it a better iPad app too.

Now, there's another kind of menu in Mac, the Context Menu.

It's shown in the Control click or right click.

And unlike the Main Menu, its contents are dynamic.

They're based on what's under the mouse pointer.

We have a great new UIKit API for this available on iOS 2, UIContextMenuInteraction.

It uses UICommands and its sibling, UIAction.

UIAction is block base, so maybe more suitable for this kind of menu.

And this cross platform API works uniquely on Mac showing the context menu that we're all used to.

To learn more about this API, you should check out our Modernizing Your UI for iOS 13 session.

So we've talked about some new UIKit APIs.

Here's an old UIKit API, UISplitViewController.

So the familiar sidebars on Mac are very similar to Split Views in iOS.

They're often used for master detail kinds of UI.

On Mac, UISplitViewController manage views are automatically drag resizable from the minimum to maximum column width.

And you can use our new primaryBackgroundStyle property to have the left-hand column take on a Mac sidebar look.

When you're using the sidebar background style, embedded table views using either of our group styles will take on a source list like appearance and look right at home on Mac.

We're going to see this in a second in the demo too.

Hover, it doesn't exist on iPad but it's an instantly familiar idea to Mac users.

We've added a new easy to use gesture recognizer, UIHoverGestureRecognizer.

It allows you to use Hover in you UIKit apps.

On screen here, you can see our stocks app making great use of this to display prices as the user moves the most plunger over the price chart.

Now for some things that differ completely on Mac, we already have some great Mac APIs.

One example of this is the Toolbar.

Mac apps often have Toolbars at the top of the window.

Because this is a Mac only feature, we've exposed the regular Mac only API, NSToolbar.

You can get to it via your UIWindowScene's titlebar property.

There's a demo of this coming up soon too.

Another kind of bar unique to Mac is the Touch Bar.

Touch Bars are on Mac harder feature.

On MacBook Pros, they sit just above the keyboard, unlike context menus, what's in them changes based on the what the user is doing.

To allow you to support Touch Bar, we've exposed the existing NSTouchBar class to UIKit apps.

And it's available via new UIResponder and UIViewController APIs.

So there are of course the whole host of other Mac features you could adopt.

You can have some control over window sizing, you could use the iOS printing APIs to implement print support on Mac, you can author a Help Book which will give your users help accessible directly from the Help Menu, and you can customize your Assets And Strings to make how your app looks or what your app says unique to Mac.

Now, last but not the least, I'd like to give a mention to the app icon.

If you do nothing, your icon for the iPad app will be used for your Mac app.

But Mac style differs from iPad style so you can also create an icon for your app that's just for Mac.

This may seem pretty trivial but it's the first thing that your users see.

So I encourage you to put some time and effort into it.

Now, I'd like to introduce my colleague Glen.

He'll show you firsthand how to make an iPad app into a better Mac app.

[Applause]

Thanks, Jaime.

As my colleague had said, better iPad apps make for better Mac apps.

Let's take an example of this.

So here, we have an app called ChocolateChip.

It's an iPad app that shows yummy recipes.

In previous demos, all we did was OK, let's and wait for it to load.

In previous demos, we just checked the Mac checkbox here, fix any build issues and just built and ran the app like so.

OK. Just taking a while.

As you can see, we have a brand-new Mac app with all the niceties you expect for a Mac app.

For example, all your content appears in a window that you can drag around.

And if you have supported varying screen sizes and optimize drawing like Jamie suggested, you will also be able to get resizing windows fast and smooth, like so.

Now, if you will implement it, the existing move callback in the table view controller, you can also reorder rows in the table view controller like so without going through a separate editing mode.

So this is a Mac feature.

Next, we've used the new UIContextMenuInteraction API which we introduced this year, configure it with the right UIActions and UICommands, then your context menus carry over to the Mac seamlessly.

So here I've got Add to Favorites.

I can make Donuts my favorites or I can take that away as well.

Now talking about menus.

You also get a default menu bar that start with all the default items that you're used to.

For example, cut, copy, past, undo and redo.

And these items, menu items are enabled or disabled depending on what's on the responder chain.

Finally, if you use the new Dark Mode API in iOS and specify your colors using these system colors, then Dark Mode works really well as well.

As you can see, everything goes dark and all your colors are set and your highlights are set correctly as well.

And all of this without a single line of code.

[ Applause ]

Yeah. Thank you.

But what if you want it to catalyze your app to the next level?

If you want it to make it look and feel like it really belongs in a Mac, what can we do?

Well, let's fix three things today, sidebars, toolbars and menu bars.

So it looks like I'll be your friendly bartender for today.

Yeah, thanks [laughter].

OK. First order of business is you look at this master view here.

This is a master view of the regular Split View controller.

But it really should look like a Mac toolbar.

So let's set Let's figure out how to set the toolbar style.

First, we go to the RecipeSplitViewController and we need to hook on to the viewDidLoad delegate call.

And here, our call has to be conditionalized for UIKit for Mac so that it only applies for Mac builds.

And we only need to insert a primaryBackgroundStyle and a new, you know, enumeration of sidebar.

So with this single line of code, we now have a and we have a quick rebuild.

We now have the sidebar style.

So just to show you how it looks like and to convince you it really does look like a sidebar.

You can see it has the translucency of a sidebar as I move it around.

The other things about sidebars are you can also set in the general preferences the sidebar icon size.

So here I'm setting it to small.

And as you can see, it dynamically updates.

You can also set it to large.

I will leave it at medium.

And as before, reordering what's just as what I did before.

OK. Sorry.

Back to the app.

Next, we should really put in a toolbar.

We have various widgets integrated into the title bar chrome so that users can click on their commonly used commands.

Now, this is different from iOS where toolbars are typically found at the bottom of the app.

We'll have a widget to filter the recipes.

We'll also have a widget to add a new recipe.

So let's see how to do that.

We'll go over to the scene delegate.

And here in the scene delegate, we're hooking on to the scene willConnect callback.

And again, we also have to conditionalize this for UIKitForMac so that it only applies in the Mac.

We're digging into the object model of the scene windowScene here.

And we grab this object called the titlebar.

The titlebar allows us to set toolbars as well as change title bar visibility.

So let's create a new toolbar.

So, notice now that I'm using NS prefix.

This is actually AppKit object.

And I need to give it identifier which I have one previously set.

And all I need to do now is a title bar.

I want to set the toolbar to the toolbar I just created.

So with these two lines of code, I've now created a toolbar that's attached to the title bar.

But this code is not particularly interesting, or the result is not particularly interesting because we actually need to configure the toolbar of the actual items.

So let's see how to do that.

I'll leave you to assign this code.

First, we're grabbing we need to grab the rootViewController, and then we pass it to an initializer of the ToolbarDelegate which is a class we've created.

And we set up the delegate.

We want to allow user customization of the toolbar.

We also want to center the navigationItem which is the filter that I talked about.

And finally, I want to set the titleVisibility to hidden so that the title doesn't overlap the navigation item that I've just created.

So, with a quick recompile and build, we now have a toolbar.

And you can see the toolbar has filters for various meals.

So, you know, for breakfast I think I'm having donuts.

For lunch, I'm having pizzas.

For dinner, I'm having spaghetti.

Hopefully for desert I'm not having spaghetti again.

Wow, it's chocolate chip cookies.

Yes. And we also have a plus widget or the add item widget which allows us to add new recipes.

OK. Finally, we should actually also customize the menu bar so that users can find all the commands they want in a convenient place.

Now, menu bars are great places to put all the commands you want and the system will enable or disable them accordingly, in contrast to toolbars where you might only put commonly used commands.

In the introduction session, we showed you how to do this by dragging out a menu bar in Xcode Interface Builder.

But sometimes you want to better control your menus at runtime.

So I'll show you how to do this in code.

What are we going to do?

We are going to remove the Format menu because having a Format menu doesn't make a lot of sense because this app has no editable text.

So not a lot of sense to have a font menu or even text alignment.

We also want to add two items to the File menu.

So here, I want to add a new recipe command which is similar to the add recipe widget here.

And we also want to add a command to add or remove favorite status which is similar to the context menu here.

All right.

Let's see how to do that.

We'll go over to the app delegate.

And here in the app delegate, we need to override a single method.

And this method is buildCommands with builder, and it gets passed a UICommandBuilder.

In subsequent seeds, this will be called buildMenu and buildMenu with builder and that will be passed a UIMenuBuilder.

So be aware of that if you are working with the current seed.

First thing you need to do is to make sure that we're actually building for the Main menu.

So here we're guarding against checking to see that the system is the main system.

Sometimes you'll get a .context if you're building for the a context menu.

With that, we need to talk to the builder.

So builder, I want to remove a particular menu.

So here, this menu that I wanted to remove is a Format menu.

So as you can see, code completion helps you out a lot and suggests what kinds of menus you can actually remove.

So quick build and run.

You can see we've removed the Format menu from between the Edit and View.

So, let's go back to this.

So the builder is actually pretty flexible.

You can do anything from replacing an entire menu structure to making selective edits like what I'm doing.

OK. So next thing I want to do is I actually want to insert the content.

So let's do a talk to the builder and say insert a child at the start here of the File menu.

So, what about this content?

I have a little commands here that will be added.

The first one is newRecipeCommand.

And this is a key command which is hooked on to the createRecipe method.

And it's a key command so it will take a input of f and a command and option.

So if you press Command option f, you'll also be able to invoke this UIKeyCommand.

The nice thing about having a UIKeyCommand here is this is perfectly cross platform.

This will also appear, you know, discoverability view or discoverability had that you get on the iPad.

So without conditionalizing any code.

This works on both Mac as well as iOS.

Next, we can create a makeFavoriteCommand.

And this is just a regular command without any key equivalence.

And this goes to toggleSelected RecipeFavoriteState.

And finally, we make a menu which is a construct that groups both the new command newRecipeCommand and the makeFavoriteCommand.

One thing to highlight here is we're passing the options of displayInline so that the menu will be displayed the contents in the menu will be displayed together with the parent.

If you omitted these options, what will happen is the menu will be displayed as a hierarchical menu which is what you might be used to on the Mac.

OK. Let's see what this looks like here.

So we insert this into the at the beginning of File menu.

OK. We have a quick rebuild and run.

As you can see, it now has a New Recipe command which does the same thing as that plus widget.

It also has a Make Favorite which does it gives a love heart on the chocolate chips.

And we can remove favorites as well.

In summary, what I've shown you is how much you get for free just by rebuilding your iPad app on the Mac.

I've also shown you sidebars, toolbars and menu bars to take you all to catalyze your app to the next level.

So it's over to you, Jamie, for some design considerations.

[ Applause ]

Thanks, Glen.

So, now we've talked about some of the technical details of adapting your iPad app for Mac, let's take a short dip into some design considerations.

First, Navigation.

Think about how your users find their way around your app.

If you're not already think about using a sidebar on Mac and maybe a Split View on iPad too, and reconsider your use of tab bars.

On the Mac, you should consider using a segmented control in the toolbar instead.

Next, layout.

We've talked about screen sizes already.

It should be flexible in your layout and take advantage of the big window.

You can reflow and redesign your user interface and you can sue custom assets to reword hardware related content or just to take advantage of the space.

iPad design is larger than Mac design.

It's optimized for touch.

So baseline font sizes are not the same.

On iOS, most text is 17 points and on Mac it's 13 points.

So we scale UIKit content for you by 77% compared to how it would appear in an AppKit app.

This means that UIKit points are smaller than AppKit points on screen.

Even bitmap graphics in UIKit apps will be shown at 77% of the size that they would be in an AppKit app.

Now because the scaling is global to the app, in most cases, you don't need to worry about it.

If you do want some more direct control especially over font scaling, you should look at the Font Management and Text Scaling session to learn all about it.

And menu bars.

We talked about the how already, but what should you put in it?

So I mentioned that the menu contains all possible actions in one place that is global to the app and that items are enabled or disabled based on what the user is doing.

This means that what's in the menus shouldn't change.

You should build it only once at launch time.

And you really should think about all the functions your app has and make sure they're all available via the menu bar.

And we just talked about how the iPad is optimized for touch.

One thing that's unique to iPad is direct multitouch.

The Mac doesn't have this but it does always have a keyboard and the mouse or trackpad.

You should think about how you can map the gestures used in your app to these input devices, reimplementing them for mouse and trackpad on Mac.

And when you're doing this, remember to consider accessibility.

Now that really was just a short dip into design topics.

There's lots of useful information on device in the Mac Human Interface Guidelines.

You should look at it while you're designing your app.

And I encourage you to check out the Design for iPad Apps on Mac talk.

It contains lots of great advice on how to make your UIKit app a great Mac app.

So now we've talked about making your iPad app into a better Mac app, I'd like to introduce Nils who'll talk about another thing that's unique to UIKit apps on Mac, the application lifecycle.

[ Applause ]

Thank you Jamie.

Good afternoon, everyone.

My name is Nils Beck.

I'm an engineer with the AppKit framework team and I'm excited to talk to you today about the application lifecycle of your iPad app for the Mac.

When we compare the app lifecycle on iOS to that on macOS, we find that there are differences.

In some iOS, specific behaviors are reflected in the UIKit API.

This presents a challenge when running iPad apps on the Mac.

How do we map macOS app states to the iOS API?

Let's start by first reviewing the app lifecycle states as they exist on iOS.

You can follow along in the chart that I'll be showing here on the right-hand side.

When your app is on screen and the person is normally interacting with your app on iPad, it is both in the foreground and active.

Your app is inactive but still in the foreground when something is occluding it and it is therefore not receiving events.

This is usually temporary, for example when control center is visible on top of your app.

Once your app is in the background, the user is not interacting with it.

For example, they may have used the task switcher and swiped sideways to a different app, but you may still perform background task completion and your app is suspended when no background tasks remain.

Once suspended, your app is frozen and gets no more CPU cycles.

At this point, it may be killed by the system at any moment without further notification.

And finally, your app is not running when it is no longer even in memory.

Putting all of this together, let's look at the possible state changes you will see whether running an iOS or a macOS.

I will walk you through these in a second but first I'd like to note that to keep simple things simple for you, iPad apps on the Mac will get the same delegate calls and notifications as an iOS.

You may be used to handling these state changes in app delegate methods, but they also existed and NSNotification shown here.

We recommend using these notifications as they work even when you are opted into the new multi-window API where some app delegate calls are omitted.

It's the same on iOS by the way.

Also, the overall app state in a multi-window app depends on the activation states of the individual scenes.

So you will likely want to handle state changes per scene.

But in this talk, we'll just focus on the overall app lifecycle.

In addition to the delegate calls and notifications, on macOS, the sequences of these state changes are also the same as an iPad.

As I promised a second ago, let's take a closer look at these sequences of state change notifications.

On the launch, you will see didFinishLaunching and didBecomeActive.

On the way out, you will see willResignActive and didEnterBackground.

You may also get willTerminate if we are certain that the app is exiting and your app is not already suspended.

And finally, on the way back in from the background state, you will see willEnterForeground and didBecomeActive.

While the notifications and sequences are the same on both platforms, we did alter when these transitions occur on macOS.

We'll get back to that in a moment.

Now, back in iOS, we recommend that your code should respond to these state changes in certain ways.

For example, when you learn about the deactivation of your app through willResignActiveNotification, you're expected to among other things, reduce the frame rate of on-screen content, stop various types of nonessential work, for example, timers, queues and queries, and there may also be other side effects.

For example, you may want to pause game play in your game.

Similarly, when your app enters the background state through didEnterBackgroundNotification, on iOS, your app is expected to cease all rendering, reduce your CPU usage to the bare minimum necessary, and free up as much memory as possible.

Again, there may also be other side effects.

For example, if the content shown on screen should be private, you may want to obscure it before the screenshot is taken.

Now, we don't want you to have to change all of the state change handling code when bringing your iPad app to macOS.

But your iPad app running on macOS is now a Mac app.

How does that change things?

Here are some Mac specific considerations.

It is common for many Mac apps to share the screen at the same time.

One of those applications is Frontmost which just means that it receives key events and its menu bar is shown.

Many things may affect the visibility of an app's contents.

For example, a window may be occluded by another window, or the window may be minimized to the dock, or it could even be in another space entirely.

It is also possible that the app isn't showing contents on screen for other reasons.

For example, the whole app could be hidden, or it might just not have any windows right now, or it's even possible that your app could be running in an off-screen log in session that only exists on a virtual screen which you might be accessing via VNC during one of those late night debugging sessions.

Regardless of their frontmost state and content feasibility, Mac apps are generally not expected to change their behavior as drastically as iOS apps.

All running apps are expected to be doing valuable work for the user.

Also, you want apps that are not frontmost to still receive scroll events, click-throughs and hover events.

And a Mac user would not necessarily expect game play to pause when they switch to a different app, or rendering to be disabled entirely while trying to use your app through one of those VNC sessions on a virtual screen.

I told you earlier that we had altered when these state changes occur compared to iOS.

And here it is.

Based on what I just told you, we have decided that on macOS, we will simultaneously keep all iPad apps foreground and active almost all the time.

We will only enter the foreground inactive and background states during app termination and background launches.

In other words, as long as the user perceives your app to be running, it is foreground active to avoid any unMac like side effects.

Let me repeat that.

The UIKit state change notifications are not going to be called for example when your app gains or loses the menu bar, or a window becomes occluded.

That means that your app won't reduce its resource consumption as eagerly as on iOS.

However, the AppNap heuristics are applied to all Mac apps including your iPad app.

AppNap is a feature of macOS where the system constantly observes various properties of the app for indications that it is not in use.

For example, whether it is visible, actively drawing, playing audio and so on.

AppNap then automatically applies throttling when it is deemed safe to do so.

As I mentioned earlier, we do enter the other app states during termination and background launches.

Let's start with app termination.

Something developers need to be aware of when moving their iPad apps to macOS is that switching between apps in iOS has no single equivalent on macOS.

For example, switching to another app in iOS while background audio is still playing is like changing a frontmost Mac app to a different frontmost Mac app, because your app continues to be perceived as running by the user.

But when the user doesn't return to your app on iOS and no audio was playing, this might be more similar to terminating a Mac on macOS because your app is out of sight and out of mind and exits eventually.

On macOS, user initiated app termination of your iPad app gets the same state transitions as switching to a different app in iOS, for example by using the task switcher.

The state change notifications will take you from foreground active through foreground inactive to the background.

To the user, however, the app will immediately appear as no longer running, the app's remaining windows are made invisible, it is no longer in control of the menu bar, there will be no indicator light in the dock, and your app is no longer available in the macOS command tab task switcher.

But behind the scenes, we give background tasks a chance to finish if they are created through the usual API on UIApplication that I've shown here.

Just like in iOS, end your background tasks quickly or risk task expiration.

There is one exception to this rule.

Background audio plist entries are ignored.

The process will terminate in spite of background audio requests.

After all, to the user, the app already looks terminated.

So your media frameworks will pause your our media frameworks will pause your playback for you upon entering the background state.

However, keep in mind that background audio is not needed on macOS.

Your app will remain foreground active even when the user switches to a different app.

Additionally, if the user tries to launch the app while it is still backgrounded, we simply return your app to the foreground active state.

This is similar to returning to your app in the iOS task switcher.

This sort of relaunch is only possible while your app is still processing background tasks, not once the synchronous handling of willTerminate has begun.

So try to minimize this phase where your app ignores user attempts to relaunch it.

Once the last background task completes or expires, the process exits without suspension.

As mentioned before, you will get the same delegate calls and notifications as on iOS.

In thid case, that means you will see willResignActive, didEnterBackground, and willTerminate because we will exit the process rather than just suspending it like on iOS.

So, that was iPad app termination on Mac.

As I mentioned earlier, the other way for your iPad app on Mac to enter the background state is when it is launched directly into the background.

iOS has quite a few APIs that allow you to opt into some form of background launch for your app.

To learn much more about this topic, check out the session Advances in App Background Execution, also from this year.

The following subset of these APIs is supported for your iPad apps for Mac.

URL sessions configured for background downloads, silent remote user notifications with the content available specified in the APS dictionary.

You could use this, for example, to update your app's contents when a new content arrives server-side, also when our user invokes a notification action that does not specify the foreground option.

For example, they might like a post thorugh a user notification and we could background launch your app to let the server know about this.

And finally, we also support the new BackgroundTasks framework including the new BGProcessingTask as well as BGAppRefreshTask for traditional background app refresh.

Note that for iPad apps for Mac, the app refresh feature is only supported through this new API.

The usual restrictions on background runtime and process priority apply same as on iOS.

So if your task takes too much time, your app risks being suspended or even killed.

If the user attempts to launch the app while it is already running in the background, we will simply transition it to foreground active similar to the previously discussed relaunch during termination.

For background launches as well, all state transitions will match those on iOS.

In this case, that means you will only see didFinishLaunching and whatever callback was specific to the background launch API that was used.

Only during the full launch by the user will you additionally see willEnterForeground and didBecomeActive.

A word on app suspension.

iPad apps are rarely suspended on macOS compared to iOS.

During regular termination, the app will exit exclusively without suspension.

And during app switching on macOS, this does not cause backgrounding or suspension.

But you will likely still see it occur if an app is launched directly into the background.

Now on iPad, your app may be terminated when resources are needed elsewhere especially if it's already suspended.

But on macOS, if your iPad app is suspended, it is always terminated immediately even if no memory pressure exists.

Speaking of memory, the Mac memory model applies which means there are no enforced memory limits.

Your app won't risk being killed while doing heavy lifting.

But this is a double-edged sword.

Because your app runs longer, user may notice degraded system performance from leaks piling up.

This is specially the case if it causes VM swapping on hard disk base systems.

So please, use the allocations profiling template and instruments to find and fix your leaks.

This will also benefit your app on iPad.

Now, that was a lot of information, so let's recap.

On macOs, your iPad app will spend most of its time in the foreground active state.

App termination and background launches do allow your app to get into the foreground inactive and background states though.

Don't expect audio playback to continue while your app is backgrounded even if you requested it.

And finally, you can just continue to respond to these state changes the same way as you do on iOS.

And now, I'm going to hand it over to Chris who will talk to you about distributing your iPad app to the Mac.

Thank you.

[ Applause ]

Thank you, Nils.

You've been building iOS apps for a while now.

You know how to distribute on the App Store, how to use TestFlight, how to distribute yourself using development signing, ad hoc or enterprise.

But now, you're going to be building and selling a second app on the Mac all from the same code base.

How are you going to do that?

What will be different about the Mac?

How will you ensure that your customers have a seamless experience using both apps?

That's what we're going to cover.

Let's go back to the beginning.

You opened up the project editor in Xcode.

You enabled the Mac support.

You've been working very hard.

You've got your app perfectly tuned and you think to yourself, gosh, I haven't had to worry about code signing once.

Well, that's great.

But when you want to know more about how signing is working, you can head over to the Signing and Capabilities tab.

There you can see there is a lot going on under the hood to sign your iOS and Mac app.

Let's take a closer look.

First off, if you enable automatic signing, all of the maintenance of code signing is taken care of by Xcode.

It will make this transition super easy.

I highly recommend using automatic signing.

Now, using automatic signing, Xcode will create a necessary provisioning profile for the Mac and one for iOS.

And we've modernized things a bit.

Instead of using an iOs certificate and a Mac certificate, we're going to use one unified Apple developer certificate.

So, that's a nice improvement.

[ Applause ]

Now, when you add Mac support, Xcode will use automatic signing to register a reserved bundle identifier for your app, for your Mac app prefixed with UIKit for Mac.

Now, this is important.

If you have been making use of your bundle identifier in your code or in your entitlements, you need to think about how that may affect both apps.

The entitlements file which you have been using for your iOS app is shared when signing for the Mac.

So there's no issue there.

And when you enable Mac support, Xcode will add those capabilities that are required on the Mac, Hardened Runtime and App Sandbox.

So for instance, if you've provided a usage description for the camera in your info.plist, Xcode will update your entitlements file so that your app can make use of the camera on the Mac.

If you're using an iCloud default container, that is based on your bundle identifier.

So, Xcode will adjust your entitlements file so that your iOS app and your Mac app point explicitly to your existing container.

If you've been using the default container API, you may continue doing so.

Now, Xcode will migrate a lot for you.

But if you want to share functionality between your iOS app and your Mac app, there may be some manual steps that you need to take.

If you're using the Keychain in your app, you should add the Keychain Sharing Capability in Xcode.

iCloud Keychain is made available by default on iOS.

But on the Mac, you need to declare your intent to use it through this capability.

If you're using push notifications, continue sending notifications to your iOS app.

Those notifications will be received by your iOS and Mac app.

All right.

That's what you need to know to get your project set up properly for development.

Now, when you're ready to share your app with others, you begin in Xcode by building an archive.

And on iOS, you use that archive to export for the store as an IPA.

But things are a bit different on the Mac.

When Xcode uploads your app to the App Store, it sends it as a Mac package behind the scenes.

Now, uploading to the App Store is very similar to how it is for iOS.

Let me show you.

When you have your project open, you can use the Product menu to switch your Run Destination to My Mac.

then you Archive.

The organizer will open up and show you your brand-new Mac app archive.

From there, you can click the Distribute App button and follow that same upload workflow that you're used to.

Of course, you can do all of this from the command line including uploading from Xcode build.

For more on how to automate all of this, I recommend checking out the session from 2017, What's New in Signing for Xcode and Xcode Server.

OK. So you've built your app, but there are some steps to take before sending your app to the App Store.

First, this is a new Mac app, so you need to create a new app record in App Store Connect just like you did for iOS, then before and now once more.

Now when you do this, you're going to associate that App Record with the app identifier that Xcode synthesized for you, the one prefix with UIKit for Mac.

And whenever you upload to the App Store, you should always increment your build number.

This is a little different from iOS.

macOS uses the build number to determine the order of your releases.

And if you've done all that, you can upload from Xcode and release your app on the App Store.

Now, a cool thing about the Mac is that you have more control over how you install apps.

When a customer downloads your app from the App Store, it is perfectly possible to copy that app to another Mac and run it, which brings up a few things.

App Thinning isn't used on macOS.

Your app will always contain the complete set of resources necessary to run on any Mac.

And because your app can be dragged and dropped to any Mac, it is up to you to add receipt validation logic to your app.

So whenever your app launches, it should be checking that it was legitimately purchased and is allowed to run.

For more on how to add receipt validation to your app, I recommend checking out the Advanced StoreKit talk from 2017.

The good news is that once your app is on the App Store, you have access to the same great tools, like App Store Connect, App Analytics to track your sales and marketing efforts, and the Xcode Crashes Organizer to triage any crashes that may be affecting your customers.

Your app can use the same StoreKit and GameKit APIs.

Now, if you're using those API's, there is some additional set up required on App Store Connect.

If you're using In-App Purchases or subscriptions, you will need to recreate those in App Store Connect for your new Mac app.

And if you want to share the same purchase history between both apps, that will require that you maintain your own server-side synching solution.

If you have a game, you should begin using Game Center Groups to share your leaderboard and achievement data between both apps.

And if you have a multiplayer game, you should update the Multiplayer Compatibility section in App Store Connect that will ensure that your players are matched with the appropriate versions of your app across iOS and Mac.

All right.

So you can begin playing with all of this now, but a heads-up.

For the time being, the App Store will not be accepting uploads with the prefix UIKit for Mac.

You can still create an App record and experiment with those StoreKit and GameKit APIs in the meantime.

Over the summer, we will begin accepting uploads and this would be a good time to practice uploading your app.

And finally, when Xcode 11 is released, you can begin submitting for app review.

And that is distribution on the App Store.

[Applause]

Now, there are other options if you'd like to distribute outside of the App Store.

You can serve you app directly to your customers if you sign your app with a developer ID certificate and get it notarized by Apple.

Notarized apps are a great evolution and security for the Mac.

We can all take comfort that the apps that we download and run on our Mac have been signed by the developer that wrote it and that signature has been vetted by Apple.

There's no app review for these apps, but we enforce good policies like requiring that Hardened Runtime be turned on in your app.

And there's great command line tool support to sign and notarize your app.

And sharing a notarized app with your team is a great alternative to TestFlight which is not available on the Mac.

One caveat though, signing this way doesn't allow you to use those App Store based features like GameKit and StoreKit.

Now, signing a notarized app is not as scary as it sounds.

You can do all of this from Xcode.

The first step is uploading your app to Apple.

In this process, Xcode will sign your app with the developer ID certificate and Apple will scan it for malware.

After a few minutes, Xcode will receive a notification, notarize the app, at which point you can export it, you can zip it up or put it in a disk image and make it available on your server.

If you want to know more about the security model or how to use it in your app, I recommend checking out the session from this year, All About Notarization.

All right.

Those are your options for distribution to your customers.

There is a third distribution option worth mentioning and that is development signing.

Now, development signing is just the same as you're used to on iOS.

It is great for sharing a build with your team to test.

And remember, when you share with a new Mac, remember to register any new devices on the developer portal.

And development signing is what you should use to test those StoreKit and GameKit APIs.

You can When you use development signing, you can use Sandbox test accounts instead of spending your own money exercising receipt validation.

And those are your options for distribution on the Mac.

Now, to be sure, this has all been a lot to take in.

But consider if you will how far we've come.

Maybe you're like me.

You began as an iPhone developer.

You mastered table views and screen rotation, then you graduated to something a bit bigger.

You sought opportunity on iPad.

You challenged yourself with a new form factor adding multitasking and drag and drop.

Your next adventure will be the Mac.

You'll conquer window resizing and menu bars.

I am delighted to see what you bring to the Mac.

We all stand to share greatly in its growth.

Now, go check that checkbox.

Please, join us in the labs.

[Applause]

Please watch the related sessions.

Please, have a fantastic WWDC.

Thank you.

[ Applause ]

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