【问题标题】:Exception when setting an optional binding to nil in SwiftUI after checking检查后在 SwiftUI 中将可选绑定设置为 nil 时出现异常
【发布时间】:2020-09-18 14:53:52
【问题描述】:

我有一个带有State 变量的视图,它是一个Optional。我通过首先检查可选变量是否为 nil 来渲染视图,如果不是,则强制展开它并使用 Binding 将其传递到子视图中。

但是,如果我在值和 nil 之间切换可选变量,应用程序会崩溃,并且我会在函数 BindingOperations.ForceUnwrapping.get(base:) 中得到 EXC_BAD_INSTRUCTION。如何仅显示“Nil”Text 视图来获得视图的预期功能?

struct ContentView: View {
    @State var optional: Int?
    
    var body: some View {
        VStack {
            if optional == nil {
                Text("Nil")
            } else {
                TestView(optional: Binding($optional)!)
            }
            
            Button(action: {
                if optional == nil {
                    optional = 0
                } else {
                    optional = nil
                }
            }) {
                Text("Toggle")
            }
        }
    }
}

struct TestView: View {
    @Binding var optional: Int
    
    var body: some View {
        VStack {
            Text(optional.description)
            
            Button(action: {
                optional += 1
            }) {
                Text("Increment")
            }
        }
    }
}

【问题讨论】:

    标签: exception swiftui optional


    【解决方案1】:

    这是解决此问题的一种可能方法。使用 Xcode 12 / iOS 14 测试。

    The-Variant! - 永远不要使用可选的状态/绑定和强制解包 :)

    Variant1:使用绑定包装器(无其他更改)

    CRTestView(optional: Binding(
            get: { self.optional ?? -1 }, set: {self.optional = $0}
        ))
    

    Variant2:按原样传输绑定

    struct ContentView: View {
        @State var optional: Int?
    
        var body: some View {
            VStack {
                if optional == nil {
                    Text("Nil")
                } else {
                    CRTestView(optional: $optional)
                }
    
                Button(action: {
                    if optional == nil {
                        optional = 0
                    } else {
                        optional = nil
                    }
                }) {
                    Text("Toggle")
                }
            }
        }
    }
    
    struct CRTestView: View {
        @Binding var optional: Int?
    
        var body: some View {
            VStack {
                Text(optional?.description ?? "-1")
    
                Button(action: {
                    optional? += 1
                }) {
                    Text("Increment")
                }
            }
        }
    }
    

    【讨论】:

    • 这在此示例中确实有效,但是,在较大的应用程序中,这意味着必须始终检查不理想的可选选项。进行 nil 检查的目的是避免在子视图中出现可选项。
    • 谢谢,这很有帮助,但我不会将其标记为答案,因为在您完成 nil 检查后提供默认值似乎是多余的。
    【解决方案2】:

    我刚刚找到了一个不涉及手动创建绑定和/或硬编码默认值的解决方案,因此您可以执行以下操作:

    if let unwrappedBinding = $optional.unwrappedValue {
      TestView(optional: unwrappedBinding)
    } else {
      Text("Nil")
    }
    

    这里是扩展:

    protocol OptionalType: ExpressibleByNilLiteral {
      associatedtype Wrapped
      var optional: Wrapped? { get set }
    }
    extension Optional: OptionalType {
      var optional: Wrapped? {
        get { return self }
        mutating set { self = newValue }
      }
    }
    
    extension Binding where Value: OptionalType {
      /// Converts a Binding with an optional values (`Binding<T?>`) to an optional `Binding<T>?` with an unwrapped value.
      var unwrappedValue: Binding<Value.Wrapped>? {
        guard let unwrappedValue = wrappedValue.optional else {
          return nil
        }
        
        return .init(get: { unwrappedValue }, set: { wrappedValue.optional = $0 })
      }
    }
    
    

    这个更容易指定默认值的扩展 ($optional.defaulting(to: 0)) 也很方便:

      /// Converts a Binding with an optional values (`Binding<T?>`) to a non-optional (`Binding<T>`) with a default value.
      func defaulting(to defaultValue: Value.Wrapped) -> Binding<Value.Wrapped> {
        .init(get: { self.wrappedValue.optional ?? defaultValue }, set: { self.wrappedValue.optional = $0 })
      }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-04-03
      • 1970-01-01
      • 2011-02-03
      • 1970-01-01
      • 2018-10-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多