Get URL from Open dialog of standard Swift document-based application

Viewed 357

I am trying to extract the URL of the document a user has selected in the default "Open" dialogue of my document based macOS application. I understand, a fileWrapper is passed to the init method but is there a way of extracting the path/URL from said wrapper?

Thanks,

Lars

3 Answers

The only way I could get this to work was to add a custom initializer to the content view that writes the URL back to the document. Very inelegant, yet here we are:

App:

import SwiftUI

@main
struct FileOpenApp: App {
    var body: some Scene {
        DocumentGroup(newDocument: FileOpenDocument()) { file in
            ContentView(document: file.$document, fileURL: file.fileURL)
        }
    }
}

Document:

struct FileOpenDocument: FileDocument {
    var text: String
    var originalFilePath: String
    var firstOpen: Bool
    
    
    init(text: String = "Hello, world!") {
        self.text = text
        self.firstOpen = true
        self.originalFilePath = "NewFile Path"
    }

    static var readableContentTypes: [UTType] { [.exampleText, .CSVtext, .plainText] }

    init(configuration: ReadConfiguration) throws {
        guard let data = configuration.file.regularFileContents,
              let string = String(data: data, encoding: .utf8)
        else {
            throw CocoaError(.fileReadCorruptFile)
        }
        text = string
        self.firstOpen = true
        self.originalFilePath = "ReadPath"
    }
    
    func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
        let data = text.data(using: .utf8)!
        return .init(regularFileWithContents: data)
    }
} 

ContentView:

struct ContentView: View {
    @Binding var document: FileOpenDocument
    var documentURL: URL?

    init(document: Binding<FileOpenDocument>, fileURL: URL?){
        self._document = document
        self.documentURL = fileURL

        if document.firstOpen.wrappedValue == true {
            if let path = self.documentURL?.path {
                self.document.originalFilePath = path
                document.firstOpen.wrappedValue = false
            }
        }
    }
    
    var body: some View {
        TextEditor(text: $document.text)
        Text("ContentView Path: \(self.documentURL?.path ?? "no Document Path")")
        Text("Document Path: " + document.originalFilePath)
    }
}

The FileWrapper has a filename field, so you'd presumably use that.

The open panel gives you the URL if someone clicks the Open (OK) button. NSOpenPanel has a urls property that contains the URLs of the selected files.

SwiftUI file importers give you a URL if the open was successful.

.fileImporter(isPresented: $isImporting, allowedContentTypes: 
    [.png, .jpeg, .tiff], onCompletion: { result in
    
    switch result {
        case .success(let url):
            // Use the URL to do something with the file.
        case .failure(let error):
            print(error.localizedDescription)
    }
})

UPDATE

SwiftUI's document opening panel works differently than the file importer. You could try working with NSOpenPanel directly. The folllowing article should help:

Save And Open Panels In SwiftUI-Based macOS Apps

Related