如何为 SwiftUI 正确使用已发布的可选属性
How to use published optional properties correctly for SwiftUI
为了提供一些背景信息,我正在编写我们应用程序的订单跟踪部分,它会每隔一段时间从服务器重新加载订单状态。 UI 屏幕上的 UI 是在 SwiftUI 中开发的。我需要屏幕上的可选图像,该图像会随着订单的状态变化而变化。
当我尝试以下操作时一切正常...
我的 viewModel 是一个 ObservableObject:
internal class MyAccountOrderViewModel: ObservableObject {
已发布 属性:
@Published internal var graphicURL: URL = Bundle.main.url(forResource: "tracking_STAGEONE", withExtension: "gif")!
在 SwiftUI 中使用 属性 如下:
GIFViewer(imageURL: $viewModel.graphicURL)
我的问题是 graphicURL
属性 的占位符值可能不正确,我的要求是它是可选的。将已发布的 属性 更改为:@Published internal var graphicURL: URL?
会导致我的 GIFViewer 出现问题,它不接受可选的 URL:
Cannot convert value of type 'Binding<URL?>' to expected argument type 'Binding<URL>'
尝试对 graphicURL
进行明显的展开会产生此错误:
Cannot force unwrap value of non-optional type 'Binding<URL?>'
完成这项工作的正确方法是什么?我不想在 属性 中输入一个值,并检查 属性 是否等于占位符值(即将其视为 nil),或者假设 属性总是非零并且不安全地以某种方式强制打开它。
下面是 Binding
的扩展,您可以使用它来将类型 Binding<Int?>
转换为 Binding<Int>?
。在您的情况下,它将是 URL
而不是 Int
,但此扩展是通用的,因此适用于任何 Binding
:
extension Binding {
func optionalBinding<T>() -> Binding<T>? where T? == Value {
if let wrappedValue = wrappedValue {
return Binding<T>(
get: { wrappedValue },
set: { self.wrappedValue = [=10=] }
)
} else {
return nil
}
}
}
示例视图:
struct ContentView: View {
@StateObject private var model = MyModel()
var body: some View {
VStack(spacing: 30) {
Button("Toggle if nil") {
if model.counter == nil {
model.counter = 0
} else {
model.counter = nil
}
}
if let binding = $model.counter.optionalBinding() {
Stepper(String(binding.wrappedValue), value: binding)
} else {
Text("Counter is nil")
}
}
}
}
class MyModel: ObservableObject {
@Published var counter: Int?
}
结果:
为了提供一些背景信息,我正在编写我们应用程序的订单跟踪部分,它会每隔一段时间从服务器重新加载订单状态。 UI 屏幕上的 UI 是在 SwiftUI 中开发的。我需要屏幕上的可选图像,该图像会随着订单的状态变化而变化。
当我尝试以下操作时一切正常...
我的 viewModel 是一个 ObservableObject:
internal class MyAccountOrderViewModel: ObservableObject {
已发布 属性:
@Published internal var graphicURL: URL = Bundle.main.url(forResource: "tracking_STAGEONE", withExtension: "gif")!
在 SwiftUI 中使用 属性 如下:
GIFViewer(imageURL: $viewModel.graphicURL)
我的问题是 graphicURL
属性 的占位符值可能不正确,我的要求是它是可选的。将已发布的 属性 更改为:@Published internal var graphicURL: URL?
会导致我的 GIFViewer 出现问题,它不接受可选的 URL:
Cannot convert value of type 'Binding<URL?>' to expected argument type 'Binding<URL>'
尝试对 graphicURL
进行明显的展开会产生此错误:
Cannot force unwrap value of non-optional type 'Binding<URL?>'
完成这项工作的正确方法是什么?我不想在 属性 中输入一个值,并检查 属性 是否等于占位符值(即将其视为 nil),或者假设 属性总是非零并且不安全地以某种方式强制打开它。
下面是 Binding
的扩展,您可以使用它来将类型 Binding<Int?>
转换为 Binding<Int>?
。在您的情况下,它将是 URL
而不是 Int
,但此扩展是通用的,因此适用于任何 Binding
:
extension Binding {
func optionalBinding<T>() -> Binding<T>? where T? == Value {
if let wrappedValue = wrappedValue {
return Binding<T>(
get: { wrappedValue },
set: { self.wrappedValue = [=10=] }
)
} else {
return nil
}
}
}
示例视图:
struct ContentView: View {
@StateObject private var model = MyModel()
var body: some View {
VStack(spacing: 30) {
Button("Toggle if nil") {
if model.counter == nil {
model.counter = 0
} else {
model.counter = nil
}
}
if let binding = $model.counter.optionalBinding() {
Stepper(String(binding.wrappedValue), value: binding)
} else {
Text("Counter is nil")
}
}
}
}
class MyModel: ObservableObject {
@Published var counter: Int?
}
结果: