按照 SwiftUI 的设计原则,View 绑定的数据源变化会触发 View 刷新。但是在某些情况下我们需要在数据源没变化的时候主动刷新 View。系统没有给 View 提供一个统一的 refresh 或者 reload 方法。于是我们只能用一些非主流的方式来实现。本文介绍两种方式实现手动触发 View 刷新。
自定义ObservedObject
SwiftUI 状态管理 @ObservedObject 的生命周期和 View 保持一致。当 View 被重新求值时,关联的ObservedObject 也会被重新初始化。同理 ObservedObject 变化时也会触发 View 重新求值。因此我们可以自定义一个 ObservedObject 实例,通过触发 ObservedObject 状态变化促使 View 刷新。
代码实现
定义一个 ObservableObject:
class TriggerViewModel: ObservableObject {
func updateView() {
self.objectWillChange.send()
}
}
在 View 中声明成 @ObservedObject 属性:
struct ContentView: View {
@ObservedObject var refreshTrigger = TriggerViewModel()
var body: some View {
VStack {
RandomView()
.frame(width: 150, height: 150)
Button("刷新") {
updateViewModel()
}
.font(.headline)
.buttonStyle(.borderedProminent)
}
}
private func updateViewModel() {
refreshTrigger.updateView()
}
}
有了 refreshTrigger 后,我们调用内部的 updateView 方法就可以让 View 刷新。
Tips:因为每次 View 更新都会对 @ObservedObject 重新求值,但是通常情况下 View 的更新并不需要关联的属性重新初始化。因此 SwiftUI 后面推出了 @StateObject。
Demo 说明:RandomView 每次刷新就会变换背景色。
struct RandomView: View {
let randomH = Double.random(in: 0...1.0)
let randomS = Double.random(in: 0...1.0)
let randomL = Double.random(in: 0...1.0)
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 12)
.foregroundColor(Color(hue: randomH, saturation: randomS, brightness: randomL))
VStack {
Text("H: \(randomH)")
Text("S: \(randomS)")
Text("L: \(randomL)")
}
.fontWeight(.medium)
.foregroundColor(.white)
}
}
}
修改 View 的标识:id
虽然 View 没有提供 reload 方法,但是 View 提供了 id 方法绑定 View 的身份标识。
extension View {
/// Binds a view's identity to the given proxy value.
///
/// When the proxy value specified by the `id` parameter changes, the
/// identity of the view — for example, its state — is reset.
@inlinable public func id<ID>(_ id: ID) -> some View where ID : Hashable
}
因此我们可以通过修改一个 View 的 id 的值来告诉系统这个 View 已经变化,需要重新刷新。
我们可以把要刷新的 View 的 id 绑定到一个时间戳上面。每次要更新的时候对 ViewId 重新赋值就可以了。
沿用上一节的代码,实现是这样的:
struct ContentView: View {
@State private var refreshViewId = Date().timeIntervalSince1970
var body: some View {
VStack {
RandomView()
.frame(width: 150, height: 150)
.id(refreshViewId)
Button("刷新") {
updateViewModel()
}
}
}
private func updateViewModel() {
refreshViewId = Date().timeIntervalSince1970
}
}