iPadOS 15 Tutorial: What’s New for Developers
See what’s new in iPadOS 15 and take your app to the next level with groundbreaking changes! By Saeed Taheri.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
iPadOS 15 Tutorial: What’s New for Developers
30 mins
Activation Action
Per Apple’s guidelines, you only need to open new windows for your app based on the user’s explicit interaction. As you can implement many of these interactions using UIAction
, Apple provided a code shortcut.
In NotesListViewController.swift, go to configureBarButtonItems()
. Then, create an action that calls openNewNote()
, and attach it to the bar button item.
Do this by replacing the current configureBarButtonItems()
with this:
private func configureBarButtonItems() {
// 1
let addAction = UIAction { _ in
let navigationController = UINavigationController(
rootViewController: NoteViewController.storyboardInstance)
self.present(navigationController, animated: true)
}
// 2
let newSceneAction = UIWindowScene.ActivationAction(
alternate: addAction
) { _ in
// 3
let userActivity = ActivityIdentifier.create.userActivity()
let options = UIWindowScene.ActivationRequestOptions()
options.preferredPresentationStyle = .prominent
// 4
return UIWindowScene.ActivationConfiguration(
userActivity: userActivity,
options: options)
}
// 5
navigationItem.rightBarButtonItem = UIBarButtonItem(
systemItem: .add,
primaryAction: newSceneAction,
menu: nil)
}
Here’s what this does:
- First, create a
UIAction
that presentsNoteViewController
modally. - Next, create an instance of
UIWindowsScene.ActivationAction
. As the name implies, you use it for activating a scene. Pass theaddAction
you created in step 1 as a parameter to this function.UIKit
automatically runs thealternate
action when the device doesn’t support multiple windows. How convenient is that? - Then, create a user activity for the note creation scene and configure the request options. You’re already familiar with this step.
- Here, you return an instance of
UIWindowScene.ActivationConfiguration
, passing the user activity and options. It’s like when you passed these items torequestSceneSessionActivation(_:userActivity:options:errorHandler:)
. - Since
newSceneAction
is actually an instance ofUIAction
, you set it as the primary action of the bar button item.
Build and run. Then, try tapping the plus icon. If nothing changes, it means you were successful.
Activation Interaction
While on iPadOS 14 and below, Apple insisted on Drag & Drop as the way to open a new window, on iPadOS 15, it advertises context menus and a new pinch open gesture. Apple also integrated these in its own apps. For instance, open the Notes app.
In the sidebar, you can touch and hold or right-click with a mouse or trackpad to open the context menu. Choosing Open In New Window will open a note in a new window with the prominent style you saw earlier.
You can also pinch open with two fingers on any item in the sidebar to open it in a new window, prominently.
Next, you’ll add these options to NotesLite.
Context Menu
In NotesListViewController.swift, scroll to the mark line // MARK: - UICollectionViewDelegate
.
Look at collectionView(_:contextMenuConfigurationForItemAt:point:)
. This method adds context menu items for each row. For now, it only contains delete
. You’ll add a new action for opening the note in a new window.
First, though, you need to create a helper method for configuration, which you’ll use in the next step. Add this inside NotesListViewController just below the definition of `deleteItem(at:)`:
private func activationConfiguration(
for indexPath: IndexPath
) -> UIWindowScene.ActivationConfiguration? {
// 1
guard let note = dataSource.itemIdentifier(for: indexPath) else {
return nil
}
// 2
var info: [String: Any] = [
NoteUserInfoKey.id.rawValue: note.id,
NoteUserInfoKey.content.rawValue: note.content
]
// 3
if let data = note.image?.jpegData(compressionQuality: 1) {
info[NoteUserInfoKey.image.rawValue] = data
}
// 4
let userActivity = ActivityIdentifier.detail.userActivity(userInfo: info)
let options = UIWindowScene.ActivationRequestOptions()
options.preferredPresentationStyle = .prominent
let configuration = UIWindowScene.ActivationConfiguration(
userActivity: userActivity,
options: options)
return configuration
}
It looks rather long; however, it’s pretty straightforward:
- Get the
note
pertaining to theindexPath
from thecollectionView
‘sdataSource
. It may returnnil
, so useguard-let
syntax and exit the method early if the index isnil
. - The way to pass data to the system for creating a new window is through user activities. Each user activity has
userInfo
, in which you can store property list data. SinceuserInfo
uses a string-based key-value dictionary, decrease possible errors by using some predefined keys, which are inside the starter project. Here, you store the note’sid
andcontent
. - Check if the note has an associated image. If so, compress it to JPEG and save it to
userInfo
asData
. - Like before, create a user activity, set the request options and return a configuration made with them.
Now, return to // MARK: - UICollectionViewDelegate
and replace let actions = [delete]
with the following:
// 1
var actions = [delete]
// 2
if let configuration = self.activationConfiguration(for: indexPath) {
// 3
let newSceneAction = UIWindowScene.ActivationAction { _ in
return configuration
}
// 4
actions.insert(newSceneAction, at: 0)
}
In the code above, you:
- Change
actions
from alet
to avar
, so you can add items later. - Get an instance of
UIWindowScene.ActivationConfiguration
usingactivationConfiguration(for:)
, which you’ll write later. Since it may benil
in certain cases, you conditionally unwrap it. - Create a new activation action as you did earlier, and then return the configuration you got from step 2.
- Insert
newSceneAction
at the top ofactions
.
As in the original code, this returns a menu using the specified actions
.
Build and run. Invoke the context menu in the notes list by touching and holding or right-clicking. You may now open the note in a new window.
Next, you’ll add pinch support on UICollectionView
items.
Pinching
First, implement a new delegate method. Add this at the end of NotesListViewController.swift, just before the closing brace:
override func collectionView(
_ collectionView: UICollectionView,
sceneActivationConfigurationForItemAt
indexPath: IndexPath,
point: CGPoint
) -> UIWindowScene.ActivationConfiguration? {
activationConfiguration(for: indexPath)
}
You return an activation configuration for each item you’d like to support pinching.
Build and run. Then, try pinching open on a note.
The entire row gets bigger while you pinch. You can customize the transition in a way that only the image scales up. To do this, tell the system on which view the scale transition should occur.
Open activationConfiguration(for:)
, and right before the return configuration
line, add:
// 1
if let cell = collectionView.cellForItem(at: indexPath) {
// 2
if let imageView = cell.contentView.subviews.first(
where: { subview in
(subview as? UIImageView)?.image != nil
}
) {
// 3
configuration.preview = UITargetedPreview(view: imageView)
}
}
Here’s what this does:
- First, get the cell the user pinched.
- Find the
imageView
inside the subviews of the cell’scontentView
whereimage
isn’tnil
. - Set the
imageView
you found in step 2 as thepreview
of the activation configuration.
Build and run. Try pinching one more time. It looks much more polished.
UICollectionView
, create a UIWindowScene.ActivationInteraction
and attach it to a custom view anywhere in the hierarchy. It’s easy to do, but beyond the scope of this tutorial.