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

SwiftUI List报错:CustomDataModel需遵循Identifiable协议

Watch端SwiftUI列表渲染自定义类型报错修复

问题背景

开发Watch应用事件列表展示功能:视图打开时向iPhone端发送获取事件列表请求,目前跨设备通信链路正常,Watch端可成功接收返回的事件数据,但使用SwiftUI的List组件渲染自定义事件类型时出现类型报错。
预期交互:事件数据加载完成后正常展示列表,无事件数据时展示空状态提示文本。

注意:Watch Connectivity框架不支持直接传递自定义数据类型,现有实现中CustomDataModel已遵循Codable协议:传输前将实例编码为Data类型,Watch端接收到Data后解码还原为模型实例。

报错信息

报错位置:CustomView中List(viewModel.eventsList)代码行
报错内容:Initializer 'init(_:rowContent:)' requires that 'CustomDataModel' conform to 'Identifiable'
已确认CustomViewModel中的eventsList可正常接收解码后的事件数据,但无法完成视图渲染。

现有实现代码

CustomDataModel

import Foundation

struct CustomDataModel: Codable {
    var startEndDate: String?
    
    var startEndTime: String?

    var locationDetails: String?
    
    var actions: Dictionary<String, String> = [:]
    
    var title: String?
    
    // Has more properties
    
    func encodeIt() -> Data {
        let data = try! PropertyListEncoder.init().encode(self)
        return data
    }
    
    static func decodeIt(_ data:Data) -> CustomDataModel {
        let customDataModel = try! PropertyListDecoder.init().decode(CustomDataModel.self, from: data)
        return customDataModel
    }
}

CustomViewModel

import Foundation
import Combine

class CustomViewModel: ObservableObject, SessionCommands {
    @Published var eventsList:[CustomDataModel] = []
    
    init() {
        NotificationCenter.default.addObserver(forName: Notification.Name("eventsListReady"), object: nil, queue: nil, using: self.processEventsList(_:))
    }
    
    @objc func processEventsList(_ notification: Notification) {
        if let response = (notification.object as! CommandStatus).response as? [Data] {
            response.forEach { event in
                eventsList.append(CustomDataModel.decodeIt(event))
            }
        }
     }
    
    func getEventsList() {
        self.eventsList = []
        sendMessage(["functionality": Functionality.events.rawValue, "notificationName": Notification.Name("requestEventsList")])
    }
}

CustomView

import SwiftUI

struct CustomView: View {
    @ObservedObject var viewModel: CustomViewModel
    
    var body: some View {
        VStack {
            if !viewModel.eventsList.isEmpty {
                List(viewModel.eventsList) { item in
                    let _ = print("##### !viewModel.eventsList.isEmpty")
                    NavigationLink(destination: EventsDetailsView(eventItem: item)) {
                        EventRowView(eventItem: item)
                    }.navigationViewStyle(StackNavigationViewStyle())
                }
            } else {
                let _ = print("##### viewModel.eventsList.isEmpty")
                VStack {
                    Text("No events to display")
                }.frame(maxWidth: .infinity, maxHeight: .infinity)
            }
        }.navigationBarTitle("Back").onAppear() {
            self.viewModel.getEventsList()
        }
    }
}

问题原因

你使用的List(_:rowContent:)构造器,要求传入的集合元素必须遵循Identifiable协议——SwiftUI通过该协议要求的唯一id属性区分不同列表项,以此完成视图复用、更新和状态匹配。当前CustomDataModel仅遵循了Codable协议,未实现Identifiable相关要求,因此触发类型报错。

修复方案

优先选择方案1,稳定性更高:

方案1:让CustomDataModel遵循Identifiable协议

如果事件本身有业务侧的唯一标识(比如eventId、日程ID这类从iPhone端传输过来的固定字段),直接将该字段作为id,不需要修改传输的数据结构,也不影响Codable编解码,示例:

struct CustomDataModel: Codable, Identifiable {
    // 假设事件唯一标识字段为eventId,替换为你实际的业务字段即可
    var id: String { eventId }
    var eventId: String
    // 其余原有属性保持不变
}

如果暂时没有业务唯一标识,可以新增自动生成的UUID作为id,注意这种方式每次解码生成的id都不同,列表刷新时可能出现视图跳动:

struct CustomDataModel: Codable, Identifiable {
    let id = UUID()
    // 其余原有属性保持不变
}

方案2:手动指定List的id路径(无需修改模型)

如果不想调整模型结构,可以换用List的另一个构造器,手动指定元素上可作为唯一标识的字段路径,示例:

// 这里指定用title作为唯一标识,注意必须保证该字段在所有列表项中唯一,否则会出现渲染错乱
List(viewModel.eventsList, id: \.title) { item in
    // 原有行内容保持不变
}

额外隐藏问题修复

现有ViewModel的通知回调存在两个隐患:

  1. 通知回调默认不保证在主线程执行,直接在回调中修改@Published属性触发UI更新,可能出现后台线程更新UI的崩溃
  2. 循环append数据会多次触发视图刷新,数据量大时存在不必要的性能损耗
    将processEventsList方法修改为如下实现即可:
@objc func processEventsList(_ notification: Notification) {
    if let response = (notification.object as! CommandStatus).response as? [Data] {
        // 一次性解码所有数据
        let decodedEvents = response.map { CustomDataModel.decodeIt($0) }
        // 切到主线程一次性赋值,触发单次视图刷新
        DispatchQueue.main.async {
            self.eventsList = decodedEvents
        }
    }
 }

内容的提问来源于stack exchange,提问作者tech_human

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 20:48:17