Mobile Dev

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.

1 answer 1 view

How to return a custom struct from AppIntent’s perform() method in Swift without using AppEntity?

Overview

  • Custom type Statistics with 3 properties:
swift
struct Statistics {
 var countA: Int
 var countB: Int
 var countC: Int
}
  • Goal: Return Statistics from AppIntent’s perform() method.

Problem

  • Statistics is a computed result, not a persisted entity.
  • Avoid making it an AppEntity.

Questions

  1. How to implement returning Statistics from perform()?
  2. Does it need to conform to AppEntity?
  3. 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()

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):

swift
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:


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:

swift
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 expects AppEntity conformance 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) to AppEntity requires you to provide an identifier and a display representation. Once that’s done you can return the entity:

Sketch (conceptual):

swift
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 / IntentDialog is the fastest route.
  • You need callers (other intents/shortcuts) to receive the structured value, or you need persistence/selection behavior: make an AppEntity and return it via ReturnsValue / result(value:).

Other practical tips:

  • Use @MainActor when 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 clear displayRepresentation so 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)

  1. ShowsSnippetView (display-only)
swift
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) }
 }
}
  1. ProvidesDialog (text)
swift
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)"))
 }
}
  1. AppEntity wrapper + ReturnsValue (structured)
swift
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


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.

Authors
Verified by moderation
Moderation
Swift AppIntent: Return Custom Struct Without AppEntity