Instruction
Introduced in iOS 16, App Intents helps developers expose different sets of functionality in their apps to specific iOS system frameworks. App Intents were initially designed to work with App Shortcuts for Siri, App Shortcuts for the Shortcuts app, the Action button, and Spotlight. In iOS 17, the squeeze action on the Apple Pencil and interactive widgets were added.
Now, in iOS 18, Siri is the latest framework to get App Intents integration and some initial support for Apple Intelligence, as of the iOS 18 betas. Take a brief look at what this looks like from an architectural perspective.
The App Intents Layer
Apps in iOS, and other Apple operating systems, are sandboxed from one another. This means that each app is isolated from others on the system. This is very important from a security perspective. For example, other apps on the system can’t see into your app’s documents folder and vice versa.
This same security feature, however, also applies to any functionality you have in your app. Without help from the operating system, you can’t share functionality with other apps or system frameworks. Some features, such as share sheets, allow for the transfer of information between apps, but functionality remains behind the sandbox wall.
The App Intents framework introduces a layer between your app and system frameworks, as shown in Figure 1 below.
The bottom row in the figure represents your app. As mentioned, it’s sandboxed by itself, keeping its data and functionality isolated from other parts of the system.
However, this app uses App Intents, which is shown in the middle row. The App Intents framework, which contains interfaces, structures, classes, and macros, lets you decorate or define your existing data objects, exposing entities or functionality defined by those data objects to the broader system.
App Intents provides access to the System frameworks in the top row in Figure 1. As mentioned earlier, before iOS 18, this included Shortcuts, Search, Widgets and more. Why is this layered setup so important?
Focusing On Reuse
The top two rows of Figure 1 should really be modified in one more way to make it demonstrate how things work in code realistically.
In Figure 2, the system framework layer, the top row, is now thinner than the other two layers. This demonstrates that the App Intents layer is pulling a lot of weight when it comes to implementing App Intents in your app. The framework is thicker than the other frameworks that use it, such as Shortcuts. What exactly does this mean?
When you add App Intents support to your app, you are mostly adding a set of generic definitions to your code that allows the App Intents framework to use your entities and intents. At first, there’s no direct connection to the actual frameworks for which you’ll add support in your app.
This means that the definitions you initially create can be used throughout the framework implementations in the thinner top layer. You still have to create some system framework implementations, such as a shortcut, but they can use the more generic definitions created in the App Intents layer. Once you create those definitions, you can easily add some extra code to hook into any of the system frameworks that App Intents provides access to.
It’s time to add Siri to your App Intents figure.
Introducing Siri to App Intents
With the introduction of support for Siri in iOS 18, Figure 2 now looks like the figure below.
Before iOS 18, SiriKit was the framework that developers used to interface their apps with Siri. Now, with Siri able to take advantage of App Intents, all of the entities you defined in your app’s versions that supported iOS 16 and iOS 17 are instantly available to Siri. All you have to do is add a little support for Siri-specific objects in your code.
Before you get into that Siri-specific implementation, which you’ll learn about in the next lesson, look at what is needed to add a simple App Intent to your app.
How to Prepare Your App for App Intents
App Intents are like sentences. Verbs, or intents, act upon nouns, known as entities. First, take a look at entities.
You need to adopt two primary interfaces to get your app ready for App Intents. First, AppEntity
is a protocol that exposes a custom object in your app to system experiences such as Shortcuts. This is the “noun” in the App Intents sentence structure. For example, if you wanted to define an entity for a Session
data object, which represents a WWDC video session, you might start with:
struct SessionEntity: AppEntity {
let id: String
@Property(title: "Name of WWDC Session")
let name: String = ""
init(session: Session) {
self.id = session.id
self.name = session.name
}
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(
title: "\(name)"
)
}
static var typeDisplayRepresentation: TypeDisplayRepresentation {
TypeDisplayRepresentation(
name: LocalizedStringResource("Session", table: "AppIntents"),
numericFormat: LocalizedStringResource("\(placeholder: .int) sessions", table: "AppIntents")
)
}
}
Here, instead of adopting AppEntity
directly on the Session
object of the data model, you create a separate struct called SessionEntity
. This lets you ignore certain parts of the data model object that aren’t applicable when used with App Intents.
The @Property
property wrapper exposes this property to the App Intents system so you can use it with an associated intent.
The displayRepresentation
property contains information on how to display the entity to people. Here, it uses the name, but it could include descriptions or images.
The typeDisplayRepresentation
property is a localized name representing the entity as a concept people are familiar with in the app. Here, the lookup may return something more verbose, such as “A particular WWDC session video”, instead of just “Session”.
The second protocol to adopt is AppIntent
. This is the “verb” in the App Intents system structure or what you do with your entity. For the session example, an intent to open that session in your app might look like this to start:
struct OpenSessionIntent: AppIntent {
static let title: LocalizedStringResource = "Open Session"
func perform() async throws -> some IntentResult {
return .result()
}
}
The title
property, which is just the title of this intent, and a perform()
function help the adoption of this protocol. The perform()
function returns an IntentResult
, or the result of performing this intent. Here, the result is empty, but it can pass back values if required.
In the next segment, you’ll examine this code and expand on it in a demo.