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



我正在寻找一个干净的解决方案来解决这个SwiftUI挑战。

由于@State属性在ContentView作用域之外,以下代码已编译但不起作用。

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

}
}

由于以下原因,以下代码无法编译:

可选类型"ContentView.LocalState?"的值必须展开才能引用已包装基类型"ContentView.LocalState"的成员"isOn">

似乎$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)
}
}
}

我不想要的是:

  • 在ContentView中使用故障初始化器
  • isOn属性移动到LocalState之外

如何实现这些目标?

我相信这可以用两种技术来解决。1.使用Binding构造函数,该构造函数可以从可选绑定创建非可选绑定。和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
}
}

这对我有效:

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

分解:$stateBinding<LocalState?>,我们使用Binding初始化器(希望这不是您不想使用的故障初始化器(将其转换为Binding<LocalState>?。然后我们可以使用可选的链接和if let从中获得Binding<Bool>

相关:如何在Swift中打开绑定中的可选值?

$state_state.projectedValue的语法糖,它为您提供了Binding<LocalState?>。从现在开始,一切都很丑陋。

你也许可以通过包装装订逃脱惩罚:

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

然后:

Toggle("Toggle", isOn: wrappedIsOn)

另一种选择,灵感来自@Sweeper的回答:

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

最新更新