SwiftUI绑定到特定的枚举大小写

pcrecxhr  于 12个月前  发布在  Swift
关注(0)|答案(3)|浏览(118)

当SwiftUI视图具有复杂的逻辑时,我喜欢使用枚举来表示它的状态(通常在视图模型/服务中)。

struct FooView {

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

        var stateName: String { ... }
    }

    @State var state: ProgressState = .idle
    ...

字符串

问题:

我想要的是一种方法或方法来连接bindables到一个特定的enum case。在这个例子中,显示一个alert(或一个sheet),因为像<<$state.error>>这样的东西显然是不可能的。

...
    var body: some View {
        Text(state.stateName)

        // show alert on error
        .alert("oops there was an error", 
               isPresented: <<$state.error>>) 
        { //1.
            ...
        }
        
    }
}

当前备选方案

最简单的解决方案是使用额外的变量,但我真的不喜欢手动保持它们同步。

更好的解决方案?

我正在探索使用合并在state var更改时更新附加变量,问题是可绑定对象也需要更改其状态,但在这种情况下,警报不知道将其带回到哪个状态。
所以也许是一个enum的协议和一个alert和sheet的扩展来处理这些而不是Bindable?有人尝试过类似的方法吗?或者知道一个更好的方法?
谢谢你

gjmwrych

gjmwrych1#

SwiftUI提供了一个呈现LocalizedError的解决方案

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

字符串
您可以创建一个简单的enum,它可以显示任何标准Error或自定义错误。

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
        }
    }
}


然后你可以修改你的进度枚举。

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


然后,您可以在触发该状态时触发alert(或工作表

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


这里是完整的代码。

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()
}


这提供了一个独立的alert变量,因此.alert的表示不会与其背后的View冲突,也不会做出任何不安全的假设。

2exbekwf

2exbekwf2#

方法1 -不使用辅助变量

您可以直接将其绑定到error状态,如下所示:

.alert("oops there was an error", isPresented: .constant(state == .error)) { Button("idle") { state = .idle } }

字符串
请注意,默认的OK按钮不会更改此方法中的状态

方法2 -中间有一个Binding<Bool>变量

另一种方法是将状态连接到绑定:

var body: some View {
    let shouldShowError = Binding<Bool> {
        state == .error
    } set: { _ in
        guard state == .error else { return }
        state = .idle
    }

    Button(state.stateName) { state = .error }
        .alert("oops there was an error", isPresented: shouldShowError)
    { }
}


请注意,不需要像我们在UIKit中所做的那样手动告诉视图要做什么,但您必须声明在每种情况下的状态。
在这种情况下,很明显,您需要看到error状态的警报,但另一种方式并不清楚。所以无论如何,您应该将其过滤为2个状态(bool)。

vnzz0bqm

vnzz0bqm3#

你只需要使用一个计算绑定,比如:

// show alert on error
    .alert("alert", isPresented: Binding<Bool>(get: {state == .error},
                                               set: {_ in})) {
            Button("idle", action: {state = .idle})
            Button("loading", action: {state = .loading})
            ...
        }

字符串

相关问题