Swift AppIntent: Return Custom Struct Without AppEntity
Learn how to return a custom struct from AppIntent's perform() in Swift without AppEntity. Use ShowsSnippetView for UI, ProvidesDialog for text, or AppEntity for structured values in AppIntents.
How to return a custom struct from AppIntent’s perform() method in Swift without using AppEntity?
Overview
- Custom type
Statisticswith 3 properties:
struct Statistics {
var countA: Int
var countB: Int
var countC: Int
}
- Goal: Return
Statisticsfrom AppIntent’sperform()method.
Problem
Statisticsis a computed result, not a persisted entity.- Avoid making it an
AppEntity.
Questions
- How to implement returning
Statisticsfromperform()? - Does it need to conform to
AppEntity? - What is the correct approach for custom return types in AppIntents?
You can’t directly return a plain Swift custom struct from an AppIntent’s perform() using IntentResult.result(value:) unless the type conforms to AppEntity (or is a supported primitive). For an ephemeral, computed Statistics value, present it to the system as UI: either a SwiftUI snippet with ShowsSnippetView or a plain text dialog via IntentDialog/ProvidesDialog. If you need the value passed to other intents or persisted, wrap it in an AppEntity and return it with ReturnsValue / result(value:).
Contents
- ShowsSnippetView: return a custom struct from AppIntent.perform()
- ProvidesDialog / IntentDialog: return a text representation
- Why AppEntity is required for direct value returns
- Which approach to choose: snippet/dialog vs AppEntity
- Complete code examples (ShowsSnippetView, ProvidesDialog, AppEntity)
- Sources
- Conclusion
ShowsSnippetView: return a custom struct from AppIntent.perform()
When Statistics is a computed, transient result (you don’t want persistence or to hand it off to other intents), the easiest pattern is to return a SwiftUI snippet. ShowsSnippetView lets you return a small view that the system displays in Shortcuts / Siri UI — no AppEntity required.
Why pick this? Because you get a nicely formatted, flexible UI for your computed data without the overhead of modeling it as a system entity.
Example (minimal):
import SwiftUI
import AppIntents
struct Statistics {
var countA: Int
var countB: Int
var countC: Int
}
struct StatisticsSnippetView: View {
let stats: Statistics
var body: some View {
VStack(alignment: .leading, spacing: 6) {
Text("Count A: (stats.countA)")
Text("Count B: (stats.countB)")
Text("Count C: (stats.countC)")
}
.padding(8)
}
}
struct GetStatisticsIntent: AppIntent {
static var title = "Get Statistics"
@MainActor
func perform() async throws -> some IntentResult & ShowsSnippetView {
let stats = Statistics(countA: 10, countB: 5, countC: 3)
return .result { StatisticsSnippetView(stats: stats) }
}
}
Notes:
- Use
@MainActorwhen you return a view (you’re creating UI). - The snippet is display-only: the system will render the view, but other intents can’t read a typed
Statisticsvalue out of it. For more on this pattern, see the CreateWithSwift example and WWDC coverage of App Intents: https://www.createwithswift.com/customizing-an-app-intent/ and https://developer.apple.com/videos/play/wwdc2022/10032/.
ProvidesDialog (IntentDialog): return a text representation of your Statistics
If you only need a plain textual result (simple, fast, and compatible with Siri responses), return an IntentDialog. This is a good fit when a short human-readable summary is enough.
Example:
import AppIntents
struct GetStatisticsDialogIntent: AppIntent {
static var title = "Get Statistics (Dialog)"
@MainActor
func perform() async throws -> some IntentResult & ProvidesDialog {
let stats = Statistics(countA: 10, countB: 5, countC: 3)
let summary = "Statistics — A: (stats.countA), B: (stats.countB), C: (stats.countC)"
return .result(dialog: .init(summary))
}
}
Pros and cons:
- Pros: Very simple, works well for Siri and Shortcuts spoken/text output.
- Cons: It’s just text; you lose structured access to the counts for other intents or programmatic consumers. For an example and discussion of returning dialogs, see: https://alexanderlogan.co.uk/blog/wwdc22/04-intents.
Why AppEntity is required for direct value returns
So what if you really want to return a Statistics instance that other intents or the system can programmatically consume? Then you need to represent it in a way the App Intents framework understands — that means conforming to AppEntity (or returning a supported primitive via ReturnsValue<T>).
Key points:
IntentResult.result(value:)/ReturnsValue<T>are intended for values the system can marshal and pass between intents. For custom structured types, the framework expectsAppEntityconformance so it can identify and reconstitute the object and show a display representation. See Apple’s docs on ReturnsValue and integrating custom types: https://developer.apple.com/documentation/appintents/integrating-custom-types-into-your-intents.- Conforming
Statistics(or a wrapper entity) toAppEntityrequires you to provide an identifier and a display representation. Once that’s done you can return the entity:
Sketch (conceptual):
import AppIntents
// This is a wrapper that conforms to AppEntity so the system can use it.
struct StatisticsEntity: AppEntity {
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Statistics"
// unique id for the entity
let id: String
// the actual data
let countA: Int
let countB: Int
let countC: Int
// how the system should display this entity
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(title: "A:(countA) B:(countB) C:(countC)")
}
}
struct GetStatisticsAsEntityIntent: AppIntent {
static var title = "Get Statistics (Entity)"
@MainActor
func perform() async throws -> some IntentResult & ReturnsValue<StatisticsEntity> & ProvidesDialog {
let entity = StatisticsEntity(id: UUID().uuidString, countA: 10, countB: 5, countC: 3)
return .result(value: entity, dialog: .init("Statistics returned as an AppEntity"))
}
}
If you plan to pass the result into a subsequent intent parameter or keep it across shortcut steps, AppEntity is the right path. For guidance on modeling and registering custom types with the App Intents system, read Apple’s integration guide: https://developer.apple.com/documentation/appintents/integrating-custom-types-into-your-intents.
Which approach to choose: snippet/dialog vs AppEntity
Quick decision guide:
- You only need to show computed data to the user (no reuse): use
ShowsSnippetView. It’s flexible, looks good, and keeps your model simple. - You only need a short human-readable reply:
ProvidesDialog/IntentDialogis the fastest route. - You need callers (other intents/shortcuts) to receive the structured value, or you need persistence/selection behavior: make an
AppEntityand return it viaReturnsValue/result(value:).
Other practical tips:
- Use
@MainActorwhen creating UI (snippet or dialog). - Keep snippet views lightweight — they render in system UI.
- If you define
AppEntity, keep its identity stable and provide a cleardisplayRepresentationso the system can present it cleanly.
For real-world patterns and examples of ReturnsValue with primitives and how apps typically expose values, see Superwall’s field guide: https://superwall.com/blog/an-app-intents-field-guide-for-ios-developers/.
Complete code examples (ShowsSnippetView, ProvidesDialog, AppEntity)
- ShowsSnippetView (display-only)
import SwiftUI
import AppIntents
// your plain struct
struct Statistics { var countA: Int; var countB: Int; var countC: Int }
// snippet view
struct StatisticsSnippetView: View {
let stats: Statistics
var body: some View {
VStack(alignment: .leading) {
Text("A: (stats.countA)")
Text("B: (stats.countB)")
Text("C: (stats.countC)")
}
.padding(8)
}
}
// AppIntent that shows the snippet
struct GetStatisticsIntent: AppIntent {
static var title = "Get Statistics"
@MainActor
func perform() async throws -> some IntentResult & ShowsSnippetView {
let stats = Statistics(countA: 7, countB: 3, countC: 12)
return .result { StatisticsSnippetView(stats: stats) }
}
}
- ProvidesDialog (text)
import AppIntents
struct GetStatisticsDialogIntent: AppIntent {
static var title = "Get Statistics (Dialog)"
@MainActor
func perform() async throws -> some IntentResult & ProvidesDialog {
let stats = Statistics(countA: 7, countB: 3, countC: 12)
return .result(dialog: .init("A: (stats.countA) B: (stats.countB) C: (stats.countC)"))
}
}
- AppEntity wrapper + ReturnsValue (structured)
import AppIntents
struct StatisticsEntity: AppEntity {
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Statistics"
let id: String
let countA: Int
let countB: Int
let countC: Int
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(title: "(countA) / (countB) / (countC)")
}
}
struct GetStatisticsAsEntityIntent: AppIntent {
static var title = "Get Statistics (Entity)"
@MainActor
func perform() async throws -> some IntentResult & ReturnsValue<StatisticsEntity> & ProvidesDialog {
let e = StatisticsEntity(id: UUID().uuidString, countA: 7, countB: 3, countC: 12)
return .result(value: e, dialog: .init("Returned as AppEntity"))
}
}
Sources
- https://www.createwithswift.com/customizing-an-app-intent/
- https://developer.apple.com/documentation/appintents/returnsvalue
- https://developer.apple.com/documentation/appintents/integrating-custom-types-into-your-intents
- https://superwall.com/blog/an-app-intents-field-guide-for-ios-developers/
- https://stackoverflow.com/questions/78675045/appintent-parametrized-phrase-with-custom-struct-as-parameter
- https://developer.apple.com/videos/play/wwdc2022/10032/
- https://dev.to/troyhusted/using-app-intents-in-swiftswiftui-2hi9
- https://alexanderlogan.co.uk/blog/wwdc22/04-intents
- https://lickability.com/blog/app-intents/
Conclusion
You can’t return a plain Swift Statistics struct as a typed result from perform() unless it becomes an AppEntity. For computed, ephemeral results, prefer ShowsSnippetView (rich display) or ProvidesDialog / IntentDialog (text). Only convert Statistics into an AppEntity when you need the system or subsequent intents to consume the structured value programmatically.