2

I have SwiftUI ContentView struct from which I call function in standard Swift class. This function may throw an error which I`d like to show via Alert in ContentView. Appearance of Alert is controlled by Bool @State var declared in ContentView. I was trying to use @Binding property wrapper in the function but it is obviously not correct. Should I rather use ObservableObject or what is the best approach? Thanks.

Fragment of ContentView with Alert

HStack {
    Button("Load data...", action: {
        let panel = NSOpenPanel()
        panel.title = "Select CSV formatted data file"
        panel.canChooseFiles = true
        panel.allowedFileTypes = ["csv"]
        panel.allowsMultipleSelection = false
        panel.begin(completionHandler: {result in
            if result == .OK {
                getDataset(fromFileURL: panel.url!, withHeaderLine: headerLine)
            }
        })
    })
    .padding()
    .alert(isPresented: $isError, content: {
        Alert(title: Text("Error"), message: Text(errorText), dismissButton: .default(Text("OK")))
    })
    Toggle("With header line", isOn: $headerLine)
    }.toggleStyle(SwitchToggleStyle())
}

Fragment of called function which can throw error

do {
            var fromRow = 0
            let fileContent = try String(contentsOf: fromFileURL)
            let rows = fileContent.components(separatedBy: "\n")
            if withHeaderLine { fromRow = 1 }
            for i in fromRow...rows.count - 1 {
                let columns = rows[i].components(separatedBy: ",")
                guard let xValue = Double(columns[0]) else {
                    throw myError.conversionFailed
                }
                guard let yValue = Double(columns[1]) else {
                    throw myError.conversionFailed
                }
                myDataset.append(Dataset(x: xValue, y: yValue))
            }
        } catch myError.conversionFailed {
            errorText = "Value conversion to Double failed."
            isError.toggle()
        } catch let error {
            errorText = error.localizedDescription
            isError.toggle()
        }
}
2
  • 1
    Would you provide your code? Commented Nov 25, 2020 at 13:29
  • I added reasonable fragments of code. Commented Nov 25, 2020 at 13:40

2 Answers 2

1

Here is a demo of possible approach (with some simulation of async call, if it matters)

Tested with Xcode 12.1 / iOS 14.1

class DemoClass {
    func simulate(isError: Binding<Bool>) {
        DispatchQueue.global(qos: .background).async {
            sleep(1)
            DispatchQueue.main.async {
                isError.wrappedValue.toggle()
            }
        }
    }
}

struct ContentView: View {
    let demo = DemoClass()

    @State private var isError = false

    var body: some View {
        VStack {
            Button("Demo") { demo.simulate(isError: $isError) }
        }
        .alert(isPresented: $isError, content: {
            Alert(title: Text("Error"), message: Text("errorText"), dismissButton: .default(Text("OK")))
        })
    }
}
Sign up to request clarification or add additional context in comments.

Comments

1

I would suggest creating a ViewModel for that View. Inside that ViewModel you create the two PublishedValues for the errorText and isError. Then you can the function inside ViewModel and update Published value. ViewModel would look like this and then update your other View accordingly.

class ContentViewModel : ObservableObject {
    @Published var isError : Bool = false
    @Published var errorText : String = ""
    
    func getDataset() {
        //Here you call your function and return the result or call it directly inside here
        errorText = "Value conversion to Double failed." //<< here you can change published values
        isError.toggle()
    }
}

Create ViewModel and map to their States

struct ContentView : View {
    @ObservedObject var viewModel : ContentViewModel = ContentViewModel()
    @State var headerLine : Bool = false
    
    var body : some View {
        HStack {
            Button("Load data...", action: {
                let panel = NSOpenPanel()
                panel.title = "Select CSV formatted data file"
                panel.canChooseFiles = true
                panel.allowedFileTypes = ["csv", "png"]
                panel.allowsMultipleSelection = false
                panel.begin(completionHandler: {result in
                    if result == .OK {
                        viewModel.getDataset()
                    }
                })
            })
            .padding()
            .alert(isPresented: $viewModel.isError, content: {
                Alert(title: Text("Error"), message: Text(viewModel.errorText), dismissButton: .default(Text("OK")))
            })
            Toggle("With header line", isOn: $headerLine)
                .toggleStyle(SwitchToggleStyle())
        }
    }
}

If you still outsourced your function into another view, just return the error String from that function or use closures.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.