我正在寻找一个干净的解决方案来解决这个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)
}
}
分解:$state
是Binding<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))