SwiftUI onChange(of: ...) 更新 StateObject
Posted
技术标签:
【中文标题】SwiftUI onChange(of: ...) 更新 StateObject【英文标题】:SwiftUI onChange(of: ...) to update StateObject 【发布时间】:2021-09-06 00:28:41 【问题描述】:我构建了一个自定义AsyncImage
视图,其中ImageLoader
类获取图像并在检索图像时更新视图。由于 @StateObject
仅在第一次构建视图时被初始化,如果从父级提供的 url 发生更改,我需要一种方法让 ImageLoader 获取新图像,我在底部使用 onChange(of: url)...
执行此操作。如果传递的父参数发生更改,是否可以这样做或者是否有更好的机制来更新 @StateObjects
参数?
struct URLImage: View, Equatable
@StateObject private var loader = ImageLoader()
private var url: URL?
private var placeholder: UIImage?
init(url: URL?, placeholder: UIImage? = nil)
self.url = url
self.placeholder = placeholder
var body: some View
Image(uiImage: loader.image ?? UIImage())
.resizable()
.opacity(loader.isLoading ? 0 : 1)
.zIndex(5)
.overlay(
VStack
if loader.isLoading
ProgressView()
.progressViewStyle(CircularProgressViewStyle())
)
.background(
VStack
if placeholder != nil && loader.isLoading == false && loader.image == nil
GeometryReader geo in
VStack(alignment: .center)
Image(uiImage: placeholder!)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: geo.size.width / 3)
.foregroundColor(Theme.Colors.primary400)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Theme.Colors.primary200)
)
.onAppear
loader.url = url
.onChange(of: url) newValue in
if loader.url != newValue
loader.url = newValue
static func == (lhs: Self, rhs: Self) -> Bool
lhs.url == rhs.url
【问题讨论】:
即使使用绑定,我也必须定义一个自定义Binding
,它有一个 set
,它会在更改时更新 @StateObject
。为什么这比我拥有它的方式有任何好处?
【参考方案1】:
您应该将传入 URL 设为 Binding
。然后,它将更改,并随后更新视图主体。当重新计算视图主体时,onChange(of:perform:)
将被触发,因此您可以对新的url
、newValue
进行任何操作。
struct URLImage: View, Equatable
@StateObject private var loader = ImageLoader()
@Binding private var url: URL?
private var placeholder: UIImage?
init(url: Binding<URL?>, placeholder: UIImage? = nil)
_url = url
self.placeholder = placeholder
var body: some View
Image(uiImage: loader.image ?? UIImage())
.resizable()
.opacity(loader.isLoading ? 0 : 1)
.zIndex(5)
.overlay(
VStack
if loader.isLoading
ProgressView()
.progressViewStyle(CircularProgressViewStyle())
)
.background(
VStack
if placeholder != nil && loader.isLoading == false && loader.image == nil
GeometryReader geo in
VStack(alignment: .center)
Image(uiImage: placeholder!)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: geo.size.width / 3)
.foregroundColor(Theme.Colors.primary400)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Theme.Colors.primary200)
)
.onAppear
loader.url = url
.onChange(of: url) newValue in
if loader.url != newValue
loader.url = newValue
static func == (lhs: Self, rhs: Self) -> Bool
lhs.url == rhs.url
另一种方法是将url
传递到ImageLoader
,如果您创建一个接受Binding<URL?>
的init。我认为这是更好的方法,因为它更简单,重复的代码更少。这意味着您还可以删除所有 onAppear
和 onChange
内容:
struct URLImage: View, Equatable
@StateObject private var loader: ImageLoader
private var placeholder: UIImage?
init(url: Binding<URL?>, placeholder: UIImage? = nil)
_loader = StateObject(wrappedValue: ImageLoader(url: url))
self.placeholder = placeholder
var body: some View
Image(uiImage: loader.image ?? UIImage())
.resizable()
.opacity(loader.isLoading ? 0 : 1)
.zIndex(5)
.overlay(
VStack
if loader.isLoading
ProgressView()
.progressViewStyle(CircularProgressViewStyle())
)
.background(
VStack
if placeholder != nil && loader.isLoading == false && loader.image == nil
GeometryReader geo in
VStack(alignment: .center)
Image(uiImage: placeholder!)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: geo.size.width / 3)
.foregroundColor(Theme.Colors.primary400)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Theme.Colors.primary200)
)
static func == (lhs: Self, rhs: Self) -> Bool
lhs.loader.url == rhs.loader.url
旁注:我不建议您在此处使用此 Equatable
实现。当您添加其他变量和对象时,这将在将来导致许多问题 - 您只是不需要它。 SwiftUI 通过检查 Equatable
的所有内容来隐式计算视图是否是可等价的。我建议您只删除此视图的一致性。
【讨论】:
我在使用绑定时遇到的问题是创建@State
对象并不总是很方便。例如,如果我有一个 Person
模型并且该人模型具有图像属性,例如let image: URL
,如果我要从这个模型创建一个视图,我需要在视图中创建一个@State
变量并将图像属性分配给@State
对象,而使用我的方法你可以直接传递属性到视图。并同意 Equatable。只是我试图解决另一个问题并搞砸了:)
@Darkisa 如果你有一个ObservableObject
,你可以直接使用$objectName.propertyName
访问一个属性的绑定。所以你说你有一个Person
模型,所以你会做类似$person.image
的事情,它的类型是Binding<URL>
。以上是关于SwiftUI onChange(of: ...) 更新 StateObject的主要内容,如果未能解决你的问题,请参考以下文章
SwiftUI 2 .onChange() 中的选取器不会更改 UINavigationBar.appearance()
在 SwiftUI 视图中结合 onChange 和 onAppear 事件?