ios – SwiftUI binding to specific enum case or cases


SwiftUI provides a solution to present LocalizedError

func alert<E, A>(
    isPresented: Binding<Bool>,
    error: E?,
    @ViewBuilder actions: () -> A
) -> some View where E : LocalizedError, A : View

You can create a simple enum that can present any standard Error or custom errors.

enum LocalError: LocalizedError {
    //Use for any built in error
    case error(Error)
    //Use for something custom
    case invalidId
    
    var errorDescription: String? {
        switch self {
        case .error(let error):
            return error.localizedDescription
        case .invalidId:
            return "\(self)"
        }
    }
    var recoverySuggestion: String? {
        switch self {
        case .error(let error):
            let nsError = error as NSError
            return nsError.localizedRecoverySuggestion
        default:
            return nil
        }
    }
}

Then you can modify your progress enum.

enum ProgressState {
    case idle
    case loading
    case completed(Model)
    case error(Error)

You can then trigger an alert (or sheet) when that state is triggered

switch state {
case .error(let error):
    Text(state.stateName)
        .task {
            alert = (true, .error(error))
        }
default :
    Text(state.stateName)
}

Here is the full code.

import SwiftUI

struct FooView: View {
    
    enum ProgressState {
        case idle
        case loading
        case completed(Model)
        case error(LocalError)
        
        
        var stateName: String {
            switch self {
            case .completed(_):
                return "Complete"
            case .error(_):
                return "Something went wrong"
            default:
                return "\(self)"
            }
        }
    }
    
    @State private var state: ProgressState = .idle
    @State private var alert: (isPresented: Bool, error: LocalError?) = (false, nil)
    var body: some View {
        Group {
    switch state {
    case .error(let error):
        Text(state.stateName)
            .task {
                alert = (true, error)
            }
    default :
        Text(state.stateName)
    }
        }.alert(isPresented: $alert.isPresented, error: alert.error) {
            Button("Ok") {
                alert = (false, nil)
            }
        }
        .task {
            try? await Task.sleep(for: .seconds(1))
            state = .error(.invalidId)
        }
    }
    
    struct Model {
        
    }
    
    enum LocalError: LocalizedError {
        //Use for any built in error
        case error(Error)
        //Use for something custom
        case invalidId
        
        var errorDescription: String? {
            switch self {
            case .error(let error):
                return error.localizedDescription
            case .invalidId:
                return "\(self)"
            }
        }
        var recoverySuggestion: String? {
            switch self {
            case .error(let error):
                let nsError = error as NSError
                return nsError.localizedRecoverySuggestion
            default:
                return nil
            }
        }
    }
}

#Preview {
    FooView()
}

This provides an independent alert variable so the presentation of the .alert does not conflict with the View behind it or make any unsafe assumptions.

Latest articles

spot_imgspot_img

Related articles

Leave a reply

Please enter your comment!
Please enter your name here

spot_imgspot_img