RXP
RXP

Reputation: 677

Update progress value from fileImporter modifier in SwiftUI

I have a progress bar with its value updated while processing a file using the fileImporter modifier. I am not sure why the progress bar is not updated as the file is being processed (is it because it is part of the closure?). Below is how I am implementing the progress bar. Any help is greatly appreciated!

the ContentView basically has a button that triggers the processing of the selected file using a button and the .fileImporter modifier in a closure.

struct ContentView: View {
@ObservedObject var progress = ProgressItem()
@State var importData: Bool = false

var body: some View {
    VStack {
        Button(action: {importData = true}, label: {Text("Import Data")})
            .fileImporter(isPresented: $importData, allowedContentTypes: [UTType.plainText], allowsMultipleSelection: false) { result in
                do {
                    guard let selectedFile: URL = try result.get().first else { return }
                    let secured = selectedFile.startAccessingSecurityScopedResource()
                    
                    DataManager().importData(fileURL: selectedFile)
                    if secured {selectedFile.stopAccessingSecurityScopedResource()}
                } catch {
                    print(error.localizedDescription)
                }
            }
        ProgressBar(value: $progress.progress).frame(height: 20)
    }
}

}

The ProgressBar is a simple view that takes the progress value and updates it

struct ProgressBar: View {
@Binding var value: Double

var body: some View {
    GeometryReader { geometry in
        ZStack(alignment: .leading) {
            Rectangle().frame(width: geometry.size.width , height: geometry.size.height)
                .opacity(0.3)
                .foregroundColor(Color(UIColor.systemTeal))
            
            Rectangle().frame(width: min(CGFloat(self.value)*geometry.size.width, geometry.size.width), height: geometry.size.height)
                .foregroundColor(Color(UIColor.systemBlue))
                .animation(.linear)
            Text("\(value)")
        }.cornerRadius(45.0)
    }
}

}

I have a ProgressItem class which is an ObservableObject that contains the progress value:

class ProgressItem: ObservableObject {
    @Published var progress: Double = 0
    @Published var message: String = ""
}

The file is processed as part of the DataManager class. For the sake of this question, I have stripped out the processing details and just have a print statement to print out each line.Below is the DataManager class that updates the ProgressValue

class DataManager {
    @Published var progressBar: ProgressItem? = nil
    
    init() {
        progressBar = ProgressItem()
    }
    
    func importData(fileURL: URL, delimeter: String = ",") {
        let lines = try! String(contentsOf: fileURL, encoding: .utf8).components(separatedBy: .newlines).filter({!$0.isEmpty})
        let numlines = lines.count
        for index in 0..<numlines {
            let line = String(lines[index])
            print("\(line)")
            progressBar?.progress = Double(index)/Double(numlines) * 100
        }
    }
}

Upvotes: 1

Views: 744

Answers (1)

Asperi
Asperi

Reputation: 257729

I'd inject progress item in DataManager via constructor

class DataManager {
    private var progressBar: ProgressItem? = nil
    
    init(progressBar: ProgressItem? = nil) {
        self.progressBar = progressBar
    }

    func importData(fileURL: URL, delimeter: String = ",") {

        // ... other code

        // if importing on background queue
        //DispatchQueue.main.async { // this better update async on main queue
            progressBar?.progress = Double(index)/Double(numlines) * 100
        //}
    }

and then inject observed item from view into DataManager, like

    // ... other code

    DataManager(progressBar: self.progress).importData(fileURL: selectedFile)

    // ... other code

of course somewhere/somewhen you need to reset your current progress item. I don't see any completion in your code, but if you have one it might be inside it.

Upvotes: 1

Related Questions