Home > database >  Show ProgressView when Exporting File in SwiftUI
Show ProgressView when Exporting File in SwiftUI

Time:10-14

I am trying to show a ProgressView when trying to export a file. The file is a large file and I am using fileExporter modifier. Having a ProgressView within a ZStack and setting the zIndex to 1 does not work. I also tried to put the long operation (getting the document) into a DispatchQueue but the challenge there is that it is not a View and does not compile. Below is the code that I currently have.

Here is what I am expecting: After I press the "Export File" button, I should see a spinning circle while the document is being created (in this case the 2 second sleep command that simulates the long operation). After that the "Move" sheet from fileExporter should be presented

Any help is very much appreciated.

Here is a sample application:

struct ContentView: View {
    @State private var isExporting: Bool = false

    var body: some View {
        VStack {
            Button(action: {
                isExporting = true
            }, label: {
                Text("Export File")
            })
                .padding()
            if isExporting {
                ProgressView() {
                    ZStack{}
                    .fileExporter(isPresented: $isExporting, document: FileExport(contents: "This is a simple Text"), contentType: .plainText) { result in
                        if case .success = result {
                            print(try! result.get())
                            print("File Saved")
                        } else {
                            print("File Not Saved")
                        }
                    }
                }
            }
        }
    }
}

struct FileExport: FileDocument {
    static var readableContentTypes: [UTType] {[.plainText]}
    
    var contents: String
    
    init(contents: String) {
        self.contents = contents
        sleep(2) // simulate a long operation
    }
    
    init(configuration: ReadConfiguration) throws {
        contents = ""
    }
    
    func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
        return FileWrapper(regularFileWithContents: contents.data(using: .utf8)!)
    }
}

CodePudding user response:

You have a few problems in your code. First of all, your ProgressView() is not actually contained in a ZStack. The only ZStack you are showing does not contain anything, as the contents would have to be inside the curly braces. In a situation like this, you could generally just wrap your entire view in a ZStack.

Next, your own views don't generally take trailing closures like that. You didn't include your ProgressView code, but the code you have shown would be nonsensical to have as a trailing closure, so I don't think that was your intention. So, I removed the open curly brace the appropriate close curly brace.

When all of this is said and done, the view will show, but it will still be below the file export window. I have never found any way of placing a view above an OS sheet like this. I have seen some "solutions" if it is your sheet, but they were not really recommended.

Also, I am not sure how you are getting data back to show a progress view, other than just as a continuous spinner. I could not find anywhere in the docs where Apple gives you access to that data.

Lastly, because you are keying your progress view to the isPresented var isExporting means your ProgressView() will dismiss when the FileExport sheet does.

Also, while it seems to work with the .fileExporter inside the conditional if block, it feels wrong to me, and I would move it to another part of the view. It just seems like a bad practice.

FWIW, here is the "working" code, but I don't see how you can actually accomplish your goals:

struct FileExporter: View {
    @State private var isExporting: Bool = false
    
    var body: some View {
        ZStack {
            VStack {
                Button(action: {
                    isExporting = true
                }, label: {
                    Text("Export File")
                })
                    .padding()
                
                if isExporting {
                    ProgressView() // { No brace would go here
                        .fileExporter(isPresented: $isExporting, document: FileExport(contents: "This is a simple Text"), contentType: .plainText) { result in
                            if case .success = result {
                                print(try! result.get())
                                print("File Saved")
                            } else {
                                print("File Not Saved")
                            }
                        }
                }
            }
        }
    }
}

If you want to implement a solution that shows a progress view (bar or spinner), I think you would have to roll your own.

CodePudding user response:

Here is a possible solution. There may be a better way to do this! I am using a ZStack{}to attach the fileExporter. Is there a different way or other solution?

struct ContentView: View {

    @State private var isExporting: Bool = false
    @State private var isLoading: Bool = false
    @State private var document: FileExport? = nil
    
    var body: some View {
        VStack {
            Button(action: {
                isExporting = true
                isLoading = true
                DispatchQueue.global(qos: .background).async {
                    document = FileExport(contents: "This is a simple text")
                    
                    DispatchQueue.main.async {
                        isLoading = false
                    }
                }
            }, label: {
                Text("Export File")
            })
                .padding()
            
            if isLoading {
                ProgressView()
                    .zIndex(1.0)
            }
            
            if isExporting && !isLoading {
                ZStack {}
                    .fileExporter(isPresented: $isExporting, document: document, contentType: .plainText) { result in
                        if case .success = result {
                            print(try! result.get())
                            print("File Saved")
                        } else {
                            print("File Not Saved")
                        }
                    }
            }
        }
    }
}
  • Related