简体   繁体   English

当应用于绑定时,在 SwiftUI 中展开可选的 @State

[英]unwrapping optional @State in SwiftUI when applied to a Binding

I'm looking for a clean solution to resolve this SwiftUI challenge.我正在寻找一个干净的解决方案来解决这个 SwiftUI 挑战。

The following code compiles but do not work since @State property is outside the ContentView scope.以下代码编译但不工作,因为@State属性位于ContentView scope 之外。

import SwiftUI

struct ContentView: View {
  var state: LocalState?
  
  var body: some View {
    if let state = state {
      Toggle("Toggle", isOn: state.$isOn)
    }
  }
}

extension ContentView {
  struct LocalState {
    @State var isOn: Bool
  }
}

struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    VStack {
      ContentView(
        state: .init(isOn: false)
      )
      .border(Color.red)
      
      ContentView()
        .border(Color.red)
    }
    
  }
}

The following code doesn't compile since the following reasons:由于以下原因,以下代码无法编译:

Value of optional type 'ContentView.LocalState?'可选类型“ContentView.LocalState?”的值must be unwrapped to refer to member 'isOn' of wrapped base type 'ContentView.LocalState'必须解包以引用已包装基类型“ContentView.LocalState”的成员“isOn”

It seems that $ in $state.isOn refer to the original state and not to the unwrapped one.似乎$ $state.isOn中的 $ 指的是原始的state而不是未包装的。

import SwiftUI

struct ContentView: View {
  @State var state: LocalState!
  
  var body: some View {
    if let state = state {
      Toggle("Toggle", isOn: $state.isOn)
    }
  }
}

extension ContentView {
  struct LocalState {
    var isOn: Bool
  }
}

struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    VStack {
      ContentView(
        state: .init(isOn: false)
      )
      .border(Color.red)
      
      ContentView()
        .border(Color.red)
    }
  }
}

What I do NOT want is:我不想要的是:

  • use of failable initializer in ContentView.在 ContentView 中使用失败的初始化程序。
  • move isOn property outside LocalState .isOn属性移到LocalState之外。

How can I achieve those?我怎样才能实现这些?

This works for me:这对我有用:

var body: some View {
    if let isOn = Binding($state)?.isOn {
        Toggle("Toggle", isOn: isOn)
    }
}

Breaking it down: $state is a Binding<LocalState?> , and we use the Binding initialiser (hopefully that's not the failable initialiser that you don't want to use) to convert it to a Binding<LocalState>?分解: $state是一个Binding<LocalState?> ,我们使用Binding初始化程序(希望这不是您不想使用的可失败初始化程序)将其转换为Binding<LocalState>? . . Then we can use optional chaining and if let to get a Binding<Bool> out of it.然后我们可以使用可选链和if let来得到一个Binding<Bool>

Related: How can I unwrap an optional value inside a binding in Swift?相关:如何在 Swift 的绑定中解开可选值?

I believe this can be solved with two techniques.我相信这可以通过两种技术来解决。 1. using the Binding constructor that can create a non-optional binding from an optional. 1. 使用 Binding 构造函数,该构造函数可以从可选项创建非可选绑定。 And 2. use of a constant binding in previews, eg以及 2. 在预览中使用常量绑定,例如

import SwiftUI

struct Config {
    var isOn: Bool
}

struct ContentView: View {
    @State var config: Config?
    
    var body: some View {
        if let config = Binding($config) { // technique 1
            ContentView2(config: config)
        }
    }
}

struct ContentView2: View {
    @Binding var config: Config
    
    var body: some View {
        Toggle("Toggle", isOn: $config.isOn)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView2(config: .constant(Config(isOn: false))) // technique 2
    }
}

$state is syntactic sugar for _state.projectedValue , which gives you a Binding<LocalState?> . $state_state.projectedValue的语法糖,它为您提供Binding<LocalState?> And from here on things are ugly.从这里开始,事情就变得丑陋了。

You might be able to get away with a wrapped binding:您可能能够摆脱包装绑定:

var wrappedIsOn: Binding<Bool> {
    let stateBinding = $state
    return Binding {
        stateBinding.wrappedValue?.isOn ?? false
    } set: {
        stateBinding.wrappedValue?.isOn = $0
    }
}

And then:接着:

Toggle("Toggle", isOn: wrappedIsOn)

And alternative, inspired by @Sweeper's answer:另一种选择,灵感来自@Sweeper 的回答:

Toggle("Toggle", isOn: Binding($state)?.isOn ?? Binding.constant(false))

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM