SwiftUI Widget报错:Publishing changes from within view updates求助
修复SwiftUI Widget的「Publishing changes from within view updates is not allowed」错误
错误原因
你遇到的问题核心是:在BatteryWidgetEntryView的onAppear回调里直接修改视图的devices属性,这个操作会在视图渲染过程中触发新的视图更新,形成循环,导致系统抛出警告(严重时会引发未定义行为)。而且你的写法完全多余——entry里已经包含了要展示的devices数据,没必要额外维护一个独立的devices变量。
修复步骤
- 移除冗余的
devices属性:从BatteryWidgetEntryView里删掉自定义的var devices: [Device] = [],直接用entry.devices作为列表数据源。 - 删除错误的
onAppear赋值:去掉HStack上的.onAppear { self.devices = self.entry.devices },这是触发错误的根源。 - 修正视图初始化参数:在Widget主结构体和预览里,不再需要给
BatteryWidgetEntryView传递额外的devices参数。
修改后的完整代码
import WidgetKit import SwiftUI import Intents struct Provider: IntentTimelineProvider { func placeholder(in context: Context) -> SimpleEntry { SimpleEntry(date: Date(), devices: [], configuration: ConfigurationIntent()) } func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) { fetchDevices { devices in let entry = SimpleEntry(date: Date(), devices: devices, configuration: configuration) completion(entry) } } func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<SimpleEntry>) -> ()) { var entries: [SimpleEntry] = [] fetchDevices { devices in let currentDate = Date() for hourOffset in 0 ..< 5 { let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)! let entry = SimpleEntry(date: entryDate, devices: devices, configuration: configuration) entries.append(entry) } let timeline = Timeline(entries: entries, policy: .atEnd) completion(timeline) } } private func fetchDevices(completion: @escaping ([Device]) -> ()) { // Fetch the list of Bluetooth devices and their battery state here let devices = [Device(name: "MacBook", batteryLevel: 80), Device(name: "AirPods", batteryLevel: 50)] completion(devices) } } struct SimpleEntry: TimelineEntry { let date: Date let devices: [Device] let configuration: ConfigurationIntent } struct Device: Identifiable { let id = UUID() let name: String let batteryLevel: Int } // 核心修改的视图部分 struct BatteryWidgetEntryView : View { var entry: Provider.Entry var body: some View { List(entry.devices) { device in // 直接使用entry.devices HStack { Text(device.name) Spacer() Text("\(device.batteryLevel)%") } // 删掉了onAppear的赋值代码 } } } @main struct BatteryWidget: Widget { let kind: String = "BatteryWidget" var body: some WidgetConfiguration { IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in // 不再传递额外的devices参数 BatteryWidgetEntryView(entry: entry) } .configurationDisplayName("Battery Widget") .description("This is a Widget to show your battery state from your Bluetooth devices") } } struct BatteryWidget_Previews: PreviewProvider { static var previews: some View { BatteryWidgetEntryView( entry: SimpleEntry( date: Date(), devices: [ Device(name: "MacBook", batteryLevel: 80), Device(name: "AirPods", batteryLevel: 50) ], configuration: ConfigurationIntent() ) // 不再传递额外的devices参数 ) .previewContext(WidgetPreviewContext(family: .systemSmall)) } }
额外说明
在SwiftUI(包括WidgetKit)中,视图应该是数据的纯函数——视图的内容完全由传入的状态/参数决定,不要在视图渲染过程中修改自身的属性(除非是用@State/@Binding等SwiftUI提供的状态管理属性,但这里完全不需要)。Widget的数据源应该由TimelineProvider提供,视图只负责展示entry里的数据即可。
内容的提问来源于stack exchange,提问作者Antoni
相关产品推荐
相关产品推荐

