4

I am using the new MultiPlatform SwiftUI Document template in Xcode 12 and I don't understand how to get access to the current FileDocument from within a menu item.

My app code looks like this (straight from the template).

@main
struct MyApp: App {
    var body: some Scene {
        DocumentGroup(newDocument: MyDocument()) { file in
            ContentView(document: file.$document)
        }
        .commands {
            CommandMenu("Utilities") {
                Button(action: {
                    // How to get access to the current FileDocument ?
                }) {
                    Text("Test")
                }
            }
        }
    }
}
Markus Moenig
  • 1,614
  • 1
  • 16
  • 17

3 Answers3

5

You can create a FocusedValueKey, publish a binding to the document from your content view, and then observe it in the menu using @FocusedBinding. There is a great explanation here.

Gareth Redman
  • 66
  • 1
  • 4
-1

Here is a demo of possible approach. The idea is to use app state object to store current document in it and access from any place needed.

Tested with Xcode 12 / iOS 14

@main
struct MyApp: App {
    @StateObject var appState = AppState()

    var body: some Scene {
        DocumentGroup(newDocument: MyDocument()) { file in
            createView(for: file)
        }
        .commands {
            CommandMenu("Utilities") {
                Button(action: {
                    if let doc = appState.currentDocument {
                        // do something
                    }
                }) {
                    Text("Test")
                }
            }
        }
    }

    private func createView(for file: FileDocumentConfiguration<MyDocument>) -> some View {
        appState.currentDocument = file.document
        return ContentView(document: file.document)
    }
}

class AppState: ObservableObject {
    @Published var currentDocument: MyDocument?
}
Asperi
  • 228,894
  • 20
  • 464
  • 690
  • 2
    Thanks for this but createView() does not seem to be called under OS X. Also, as you can switch between views, would createView() not just store the last created view instead of the currently selected one ? – Markus Moenig Sep 04 '20 at 03:41
-1

To answer my own question, this is how I solved it. You can just send a signal from the .commands section via Combine.

@main
struct MyApp: App {

    private let exportAsImage = PassthroughSubject<Void, Never>()

    var body: some Scene {
        DocumentGroup(newDocument: MyDocument()) { file in
            ContentView(document: file.$document)
                .onReceive(exportAsImage) { _ in
                    file.document.exportAsImage()
                }
        }
        .commands {
            CommandGroup(replacing: .importExport) {
                Button(action: {
                    exportAsImage.send()
                }) {
                    Text("Export as Image...")
                }
            }
        }
    }
}
Markus Moenig
  • 1,614
  • 1
  • 16
  • 17