You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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变量。

修复步骤

  1. 移除冗余的devices属性:从BatteryWidgetEntryView里删掉自定义的var devices: [Device] = [],直接用entry.devices作为列表数据源。
  2. 删除错误的onAppear赋值:去掉HStack上的.onAppear { self.devices = self.entry.devices },这是触发错误的根源。
  3. 修正视图初始化参数:在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 07:45:29