如何通过系统通知重新渲染SwiftUI视图(无需冗余状态)
问题:SwiftUI视图如何通过系统通知触发重渲染,避免冗余占位状态
我有一个SwiftUI视图,布局完全依赖计算属性,部分属性会随时间自然变化。当这些属性变化到特定程度时,我能收到系统通知,希望此时触发视图重渲染,但不想为了强制刷新而维护无意义的占位状态。
举个例子,如下视图仅显示当前日期:
struct TodayView: View { var body: some View { Text(Date.now.formatted(date: .complete, time: .omitted)) } }
如果用户停留在该界面过午夜,视图不会自动刷新显示新日期,但系统会发送.NSCalendarDayChanged通知(我清楚无法保证精准触发,接收该通知已满足需求)。
直接使用.onReceive无法触发重渲染:
struct TodayView: View { var body: some View { Text(Date.now.formatted(date: .complete, time: .omitted)) .onReceive(NotificationCenter.default.publisher(for: .NSCalendarDayChanged)) { _ in } } }
虽然可以用一个未被读取的占位@State触发刷新,但这种冗余状态既占用内存,也会让代码可读性变差:
struct TodayView: View { @State private var dummy = false var body: some View { Text(Date.now.formatted(date: .complete, time: .omitted)) .onReceive(NotificationCenter.default.publisher(for: .NSCalendarDayChanged)) { _ in dummy.toggle() } } }
希望找到无需冗余状态、直接通过通知触发重渲染的方案。
解决方案
方案1:使用空ObservableObject作为刷新触发器
创建一个仅负责发送刷新信号的ObservableObject,通过调用其objectWillChange方法触发视图更新,逻辑清晰且无冗余状态:
struct TodayView: View { // 使用@StateObject持有触发器,确保生命周期与视图一致 @StateObject private var refreshTrigger = RefreshTrigger() var body: some View { Text(Date.now.formatted(date: .complete, time: .omitted)) .onReceive(NotificationCenter.default.publisher(for: .NSCalendarDayChanged)) { _ in // 收到通知时触发刷新信号 refreshTrigger.triggerRefresh() } } } // 仅负责发送刷新通知的空ObservableObject private class RefreshTrigger: ObservableObject { func triggerRefresh() { objectWillChange.send() } }
方案2:使用空值类型作为刷新令牌(iOS 13+)
定义一个空的Equatable结构体,通过更新其@State实例触发视图刷新。空结构体内存占用极低,且命名明确:
struct TodayView: View { @State private var refreshToken = RefreshToken() var body: some View { Text(Date.now.formatted(date: .complete, time: .omitted)) .onReceive(NotificationCenter.default.publisher(for: .NSCalendarDayChanged)) { _ in // 生成新的令牌实例,触发状态更新 refreshToken = RefreshToken() } } } // 空的Equatable结构体,仅作为刷新标识 private struct RefreshToken: Equatable {}
方案3:iOS 17+ 原生观察器简化实现
iOS 17及以上版本可直接使用.observation修饰符,结合简化的触发器实现:
struct TodayView: View { let refreshTrigger = RefreshTrigger() var body: some View { Text(Date.now.formatted(date: .complete, time: .omitted)) .onReceive(NotificationCenter.default.publisher(for: .NSCalendarDayChanged)) { _ in refreshTrigger.triggerRefresh() } .observation(refreshTrigger) } } private class RefreshTrigger: ObservableObject { func triggerRefresh() { objectWillChange.send() } }
这些方案都避免了无意义的占位状态,代码意图清晰,能在收到系统通知时精准触发视图重渲染。
内容的提问来源于stack exchange,提问作者Michael Hulet
相关产品推荐
相关产品推荐

