In an iOS app with SwitUI lifecycle that supports multiple windows, I have defined main menu items using .commands on the WindowGroup.
The app's structure is pretty simple, it contains NavigationSplitView with MasterView and DetailView.
Down in the view hierarchy in DetailView, I'm using focusedSceneObject and focusedSceneValue to provide DetailViewModel and currently selected Item to AppCommands.
struct DetailView: View {
@StateObject private var viewModel: DetailViewModel = .init()
let item: Item?
var itemTitle: String { item?.title ?? "nil" }
var body: some View {
let _ = Self._printChanges()
Group {
if let item = item {
SelectedItemView(viewModel: self.viewModel, item: item)
} else {
ContentUnavailableView("Select an Item",
systemImage: "list.bullet",
description: Text("Choose an item from the list to see its details"))
}
}
.alert(
"Alert",
isPresented: self.$viewModel.presentedAlert,
actions: {
Button("OK", role: .cancel) {
self.viewModel.presentedAlert = false
}
},
message: {
Text("Presented from detail view with item: \(self.itemTitle)")
})
.navigationTitle(item?.title ?? "Detail")
.onChange(of: self.item) { _, newValue in
viewModel.updateItem(newValue)
}
// Provide currently selecteItem and viewModel to AppCommands via @FocusedValue/@FocusedObject
.focusedSceneValue(\.selectedItem, self.item)
.focusedSceneObject(self.viewModel)
}
}
Here's how AppCommads are set up:
@main
struct MyApp: App {
var body: some Scene {
WindowGroup(id: "main") {
ContainerView()
}
.commands {
AppCommands()
}
}
}
// MARK: - AppCommands
struct AppCommands: Commands {
@FocusedValue(\.selectedItem) private var selectedItem: Item?
@FocusedObject private var viewModel: DetailViewModel?
var body: some Commands {
let _ = print("AppCommands")
let _ = print("Focused view model: \(pointer(self.viewModel))")
let _ = print("Focused selectedItem: \(selectedItem?.title ?? "nil")")
CommandGroup(after: .newItem) {
Button("Focused View Model Command") {
print("executing command with focused view model: \(pointer(self.viewModel))")
viewModel?.presentedAlert = true
}
.keyboardShortcut(.return, modifiers: [.command])
Button("Selected Item Command") {
print("executing command with selected item: \(self.selectedItem?.title ?? "nil")")
}
.keyboardShortcut(.return, modifiers: [.command, .shift])
}
}
}
The idea is simple, when DetailView is presented, performing a command from the main menu (or hitting the keyboard shortcut) should perform an action on the view model associated with the currently presented DetailView.
This works fine with any number of windows on the Mac. However, when the app has two windows open on the iPad (e.g. Split View), the @FocusedObject and @FocusedValue is not correctly updated with the value and object from the currently active window (window is activated by tapping on it).
Here's a screen recording with the issue demonstrated.
How to make this work? Am I doing something wrong or is this a bug in SwiftUI on iOS?