I'm very new to Intents in Swift. Using the Dive Into App Intents video from WWDC 22 and the Booky example app, I've gotten my app to show up in the Shortcuts app and show an initial shortcut which opens the app to the main view. Here is the AppIntents
code:
import AppIntents
enum NavigationType: String, AppEnum, CaseDisplayRepresentable {
case folders
case cards
case favorites
// This will be displayed as the title of the menu shown when picking from the options
static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "Navigation")
static var caseDisplayRepresentations: [Self:DisplayRepresentation] = [
.folders: DisplayRepresentation(title: "Folders"),
.cards: DisplayRepresentation(title: "Card Gallery"),
.favorites: DisplayRepresentation(title: "Favorites")
]
}
struct OpenCards: AppIntent {
// Title of the action in the Shortcuts app
static var title: LocalizedStringResource = "Open Card Gallery"
// Description of the action in the Shortcuts app
static var description: IntentDescription = IntentDescription("This action will open the Card gallery in the Hello There app.", categoryName: "Navigation")
// This opens the host app when the action is run
static var openAppWhenRun = true
@Parameter(title: "Navigation")
var navigation: NavigationType
@MainActor // <-- include if the code needs to be run on the main thread
func perform() async throws -> some IntentResult {
ViewModel.shared.navigateToGallery()
return .result()
}
static var parameterSummary: some ParameterSummary {
Summary("Open \(\.$navigation)")
}
}
And here is the ViewModel:
import SwiftUI
class ViewModel: ObservableObject {
static let shared = ViewModel()
@Published var path: any View = FavoritesView()
// Clears the navigation stack and returns home
func navigateToGallery() {
path = FavoritesView()
}
}
Right now, the Shortcut lets you select one of the enums (Folders, Cards, and Favorites), but always launches to the root of the app. Essentially no different then just telling Siri to open my app. My app uses a TabView
in its ContentView with TabItems for the related Views:
.tabItem {
Text("Folders")
Image(systemName: "folder.fill")
}
NavigationView {
GalleryView()
}
.tabItem {
Text("Cards")
Image(systemName: "rectangle.portrait.on.rectangle.portrait.angled.fill")
}
NavigationView {
FavoritesView()
}
.tabItem {
Text("Favs")
Image(systemName: "star.square.on.square.fill")
}
NavigationView {
SettingsView()
}
.tabItem {
Text("Settings")
Image(systemName: "gear")
}
How can I configure the AppIntents above to include something like "Open Favorites View" and have it launch into that TabItem view? I think the ViewModel
needs tweaking... I've tried to configure it to open the FavoritesView()
by default, but I'm lost on the proper path forward.
Thanks!
[EDIT -- updated with current code]
You're on the right track, you just need some way to do programmatic navigation.
With TabView
, you can do that by passing a selection
argument, a binding that you can then update to select a tab. An enum of all your tabs works nicely here. Here's an example view:
struct SelectableTabView: View {
enum Tabs {
case first, second
}
@State var selected = Tabs.first
var body: some View {
// selected will track the current tab:
TabView(selection: $selected) {
Text("First tab content")
.tabItem {
Image(systemName: "1.circle.fill")
}
// The tag is how TabView knows which tab is which:
.tag(Tabs.first)
VStack {
Text("Second tab content")
Button("Select first tab") {
// You can change selected to navigate to a different tab:
selected = .first
}
}
.tabItem {
Image(systemName: "2.circle.fill")
}
.tag(Tabs.second)
}
}
}
So in your code, ViewModel.path could be an enum representing the available tabs, and you could pass a binding to path ($viewModel.path
) to your TabView
. Then you could simply set path = .favorites
to navigate.