Adopting Scenes in iPadOS

In this Adopting Scenes tutorial, you’ll learn how to support multiple windows by creating a new iPadOS app. You’ll also learn how to update existing iOS 12 apps to support multiple windows. By Mark Struzinski.

5 (10) · 1 Review

Download materials
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Keeping Scenes up to Date

Even though you’ve implemented multiple windows and scenes, you may notice there is a problem with the state of the UI when the data changes.

Open a note list window and an Add Note window side-by-side. When you add a new note, you can see that the changes don’t update to the list window.

If you stop and restart the app, you can see the list updates with the latest data, which means the notes are present in the Core Data store. You need a way of telling the UI to update its current state and re-fetch data. This is where UISceneSession comes into play. A scene session can be in one of the following states:

  • Foreground Active: The scene is running in the foreground and currently receiving events.
  • Foreground Inactive: The scene is running in the foreground but not currently receiving events.
  • Background: The scene is running in the background and is not on screen.
  • Unattached: The scene is not currently connected to your app.

Scenes can become disconnected at indeterminate times. The operating system may disconnect scenes at any point to free up resources.

You have to handle both foreground and background scenarios to keep your scenes up-to-date with their backing data.

Keeping Foreground Scenes up to Date

You’ll handle foreground sessions with a familiar tool: NotificationCenter.

You can refresh any foreground sessions by listening for the appropriate notification and requesting updates from the Core Data store. You’ll do so by adding a typed notification name for refresh scenarios.

Create a new group in Xcode and name it Extensions. Select File ▸ New ▸ File and pick the Swift File template. Add a new file and name it Notification+Name.

Add the following content to it:

extension Notification.Name {
  static let noteCreated = Notification.Name("com.raywenderlich.notecreated")
}

This creates a typed notification name for reuse.

You’ll post a notification for any new notes. Open AddNoteViewModel.swift. and find createNote(). Add the following line after the request to Core Data to create the new note:

NotificationCenter.default.post(name: Notification.Name.noteCreated, object: nil)

Now every time you create a note, you also post a notification.

You also need to handle the notification in the list view. Open NoteListViewModel.swift and add the following method right underneath the property declarations:

init() {
  NotificationCenter
    .default
    .addObserver(
      self,
      selector: #selector(handleNoteCreated),
      name: Notification.Name.noteCreated,
      object: nil
  )
}

Every list that is on screen has a backing view model. Now each view model will respond to the new note notification by performing a fetch. Since the list views are all bound to the notes array, they will update every time the data changes.

Build and run.

Put a new note view side-by-side with a note list view. Next, add several notes, and you can see the note list update in real-time.

Then, add a third list window in slideover mode. Continue adding notes. Now both list views are updating in real-time.

Multiple windows with slideover

Notice, however, that any scenes currently in the background are not updated:

Background Discrepancies

You have to use a different API to update the snapshot for backgrounded or disconnected.

Keeping Background Scenes up to Date

A scene that is in the multitasking switcher is backgrounded. It is still possible to update scene snapshots for scenes in this state of the lifecycle.

To find and update these scenes, you first have to attach some identifying information to them. In this way, you can query them later and call update on the ones that need it.

For this purpose, you’ll use the userInfo dictionary property on the scene session.

Update application(_:configurationForConnecting:options:) in AppDelegate.swift to attach a userInfo dictionary to the scene session. Right after you create the scene configuration, add the following code:

// 1
let userInfo = [
  "type": activity.rawValue
]

// 2
connectingSceneSession.userInfo = userInfo

With this code, you:

  1. Create a user info dictionary from the current activity.
  2. Set the userInfo property on the scene session.

Next, locate and request updates to scenes that are currently in the background. This API will only affect scenes in the background.

Open AddNoteViewModel.swift and add the following method after createNote():

func updateListViews() {
  // 1
  let scenes = UIApplication.shared.connectedScenes
  
  // 2
  let filteredScenes = scenes.filter { scene in
    guard 
      let userInfo = scene.session.userInfo,
      let activityType = userInfo["type"] as? String,
      activityType == ActivityIdentifier.list.rawValue 
      else {
        return false
    }

    return true
  }
  
  // 3
  filteredScenes.forEach { scene in
    UIApplication.shared.requestSceneSessionRefresh(scene.session)
  }
}

Here is what you do above:

  1. Request all connected scenes from UIApplication.
  2. Filter all scenes to look for the information you attached to the userInfo object earlier.
  3. Ask UIKit to refresh all scenes from the filtered list.

Finally, add the following to the end of createNote():

updateListViews()

To see this in action, you’ll need to delete the app from the simulator and build and run again. This is because the older scenes won’t have the correct userInfo dictionaries set, and they won’t refresh properly. Now build and run. You should be able to background a few sessions, create notes in the foreground and see the background snapshots update when you go into App Exposé.

Updating Your App From iOS 12

This section gives you the steps to update an older project, but it doesn’t include a sample project. There are not many steps to adopt basic multiple-window support when updating from iOS 12:

  • Update your Info.plist.
  • Add a scene delegate.
  • Update the scene delegate.

Update Info.plist

Find and open the Info.plist of your iOS 12 app target and perform the following steps:

  1. Click the plus button (+) to add a new entry.
  2. Select Application Scene Manifest.
  3. Open up the new list item by clicking the disclosure triangle ().
  4. Change the value for Enable Multiple Windows from NO to YES.
  5. Under Scene Configuration, click the plus button (+) to add a new scene configuration.
  6. Select Application Session Role.
  7. Click the disclosure triangle to open Item 0.
  8. Enter the following values under each entry:
    1. Storyboard Name: The name of your initial storyboard.
    2. Delegate Class Name: $(PRODUCT_MODULE_NAME).SceneDelegate.
    3. Configuration Name: Default Configuration.
  9. You can delete the Delegate Class Name entry.
  1. Storyboard Name: The name of your initial storyboard.
  2. Delegate Class Name: $(PRODUCT_MODULE_NAME).SceneDelegate.
  3. Configuration Name: Default Configuration.

After you finish, your application Scene Manifest should look like this:

iOS 12 Info Plist