The code samples from Apple’s WWDC sessions and the sample code for supporting multiple windows, are a bit inconsistent which can make it a bit bumpy to get started. Here is the setup for supporting multiple windows on iPad that worked will in my experimentations so far:

1) Have an AppDelegate as usual, and keep it marked as @ApplicationMain.

2) Create a new SceneDelegate class that implements UIWindowSceneDelegate.

3) Tick the box, then add Info.plist configuration as follows:

Application Scene Manifest
Application Scene Manifest

4) As the SceneDelegate, if you’re using storyboards the following is sufficient:

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
  var window: UIWindow?
}

Contrary to some of the session videos, do not instantiate that window manually in scene(_:willConnectTo:options:) if you’re using storyboards and specified the storyboard in the scene manifest above. Only if that’s not the case, do instantiate it manually and then also set the root view controller.

And with that you’re good to go.

Your next step is likely that you’ll want to do is switch your state restoring to use NSUserActivity rather than manually restoring the view hierarchy. You’ll want to migrate UI life-cycle code over from your app delegate to the scene delegate.

And then you want to test the 💩 out of your app, to make sure that it works with many of your view controllers being running in parallel and your individual scenes disconnecting and re-connecting. Be wary of global state and singletons1 such as the standard user default; UISceneSession has a useful userInfo property for this.

Supporting window-based drop targets

A nice interaction with multi-window support is dragging an object in an app, and dragging it to the side of the screen to create a new window for that object. Getting this to work was a bit tricky in the first iOS 13 betas, but this has since been resolved. There are different ways to do this:

One note first: UIKit won’t create any new windows when dragging out of a UITableView or UICollectionView if you implemented dragSessionIsRestrictedToDraggingApplication delegate method and it returns true, so don’t implement it or make sure it returns false for cases where you do want to enable the window-based drop targets.

In the following code-samples, myObject is an object that you might already be using for drag items to support regular Drag & Drop.

1. User activity

NSUserActivity is playing an more and more important role, so this is probably the recommended approach. It is also the one taken in Apple’s sample app “Gallery”. There are two ways to achieve this:

let provider = NSItemProvider(object: myObject)
let activity = NSUserActivity(activityType: myActivityType)
provider.registerObject(activity, visibility: .all)
return [UIDragItem(itemProvider: provider)]

Make sure the visibility there is .all, as .team or .ownProcess will not work.

Alternatively, you can also pass the activity directly to the NSItemProvider:

let activity = NSUserActivity(activityType: myActivityType)
let provider = NSItemProvider(object: activity)
provider.registerObject(myObject, visibility: .all)
return [UIDragItem(itemProvider: provider)]

2. URL schemes

The good news is that I got this working with the kind help from @lostformars. First make sure you have a URL scheme defined, then pass that to the init(contentsOf:) constructor of NSItemProvider. This is rather unexpected as the documentation says that the URL you pass to that method should point to an existing file. Alas, it works:

let provider = NSItemProvider(contentsOf: URL(string: "myScheme://..."!))!
provider.registerObject(myObject, visibility: .all)
return [UIDragItem(itemProvider: provider)]

3. Universal links

I haven’t tested this myself, but universal links should work out of the box, similar to the URL scheme method above.

4. File URLs(?)

While this is mentioned in the 212 session as being support, I couldn’t yet get this working (up to iOS 13 beta 3). I tried using the fileURL from an UIDocument, but I am not getting the drop targets.

Further Stumbling blocks

Q: Why am I only getting a blank black screen?

The sample code in the WWDC slides isn’t all that consistent about this. Check that you follow the steps from above.


Q: I want to configure my window with the user activity from the session, but at the time the session videos showed there’s no root view controller set. Help!

I had this issue after following the code from session 258. Make sure to not overwrite your scene delegate’s var window if its already set. It will already be set properly with the root view controller, if you’re using Storyboards and specified your application scene manifest in the Info.plist.


Q: How do I determine which scene to activate when the app is opened through a notification, app shortcut or user activities?

This is the topic of session 242. You don’t determine this directly yourself, but rather iOS determines it. It does so by looking at the target content identifier that you have attached with the appropriate object. If you support Universal Links use those as those identifiers, otherwise use a URL scheme that identifiers the content in your app. Then attach it as follows:

  • For notifications, add it to your UNNotificationContent as a target-content-id APNS key.
  • For app shortcuts, UIApplicationShortcutItem has a new targetContentIdentifier field.
  • For user activities, NSUserActivity also has a new targetContentIdentifier field.

Then add those to your scene’s activation conditions. For document-based apps where a scene can show multiple tabs or documents it could look like this:

let conditions = scene.activationConditions

// This scene can display any content, ...
conditions.canActivateForTargetContentIdentifierPredicate = 
  NSPredicate(value: true)

// ..., but we prefer the current open tabs
let subpredicates = openTabs.map { 
  NSPredicate(format: "self == %@", $0.targetContentIdentifier) 
}
conditions.prefersToActivateForTargetContentIdentifierPredicate =
  NSCompoundPredicate(orPredicateWithSubpredicates: subpredicates)

Session videos

Have fun!


Changelog:

  • 14 June: Fixed first paragraph, linking to sample code and videos. Adding note the drag to open new window also isn’t working in sample code.
  • 16 June: Added working answer for how to get the window-based Drag & Drop targets in beta 1+2, with thanks to @lostformars. Added information on determining what scene to activate.
  • 18 June: Added another stumbling block that can prevent window-based Drag & Drop targets from working. Thanks to @simonbs for pointing this out.
  • 3 July: Updated for iOS 13 beta 3, where window-based Drag & Drop works with NSUserActivity, too.
  1. All the people who have warned that these are anti-patterns better to be avoided can have a smug smile of told-you-so. 

  2. Now is a great time to get familiar with reactive programming