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

如何从Array<any IRow>创建SwiftUI List?解决编译报错

问题描述

尝试用遵循IRow协议的类型数组创建SwiftUI List,在.onAppear中能正常映射并向下转型这些类型,但直接将数组作为List数据源时遇到编译错误:

  • 直接使用List(item.rows) { row in ... }时,编译器提示:

    'Type 'any IRow' cannot conform to ‘Identifiable’’

  • 改用List(item.rows, id: .self)时,编译器提示:

    ‘cannot conform to ‘Hashable’’

相关代码

protocol IRow: Identifiable, Hashable {
    var id: UUID { get }
    var rowType: String { get }
}

struct TextItemRow: IRow {
    let id = UUID()
    let rowType = "text"
    
    var text: String = ""
}

struct HeadlineItemRow: IRow {
    let id = UUID()
    let rowType = "headline"
    
    var text: String = ""
}

struct Item: Identifiable {
    let id = UUID()
    var rows: [any IRow] = [HeadlineItemRow(text: "H_mock"), TextItemRow(text: "T_mock")]
}

struct ContentView: View {
    let item = Item()
    
    var body: some View {
        VStack {
            List(item.rows) { row in
                
            }
        }
        .onAppear {
            var _ = item.rows.map() {
                if let r = $0 as? HeadlineItemRow {
                    print("\(r.id.uuidString) : \(r.text)")
                } else if let r = $0 as? TextItemRow {
                    print("\(r.id.uuidString) : \(r.text)")
                }
            }
        }
    }
}

解决方案

方案1:类型擦除包装器

创建一个类型擦除结构体,将any IRow包装为可识别、可哈希的类型:

struct AnyRow: Identifiable, Hashable {
    let id: UUID
    let rowType: String
    let base: any IRow
    
    init(_ row: any IRow) {
        self.id = row.id
        self.rowType = row.rowType
        self.base = row
    }
    
    static func == (lhs: AnyRow, rhs: AnyRow) -> Bool {
        lhs.id == rhs.id
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
}

修改Item的rows属性为[AnyRow]:

struct Item: Identifiable {
    let id = UUID()
    var rows: [AnyRow] = [AnyRow(HeadlineItemRow(text: "H_mock")), AnyRow(TextItemRow(text: "T_mock"))]
}

在ContentView中使用:

List(item.rows) { anyRow in
    switch anyRow.rowType {
    case "headline":
        if let headlineRow = anyRow.base as? HeadlineItemRow {
            Text(headlineRow.text)
                .font(.headline)
        }
    case "text":
        if let textRow = anyRow.base as? TextItemRow {
            Text(textRow.text)
        }
    default:
        EmptyView()
    }
}

方案2:用枚举替代协议

如果行类型是固定的几种,枚举是更简洁的方案,天然支持Identifiable和Hashable:

enum Row: Identifiable, Hashable {
    case headline(text: String)
    case text(text: String)
    
    var id: UUID {
        UUID() // 可根据业务需求替换为稳定的ID生成逻辑
    }
    
    var rowType: String {
        switch self {
        case .headline: return "headline"
        case .text: return "text"
        }
    }
}

修改Item的rows属性:

struct Item: Identifiable {
    let id = UUID()
    var rows: [Row] = [.headline(text: "H_mock"), .text(text: "T_mock")]
}

在ContentView中使用:

List(item.rows) { row in
    switch row {
    case .headline(let text):
        Text(text)
            .font(.headline)
    case .text(let text):
        Text(text)
    }
}

问题原因

Swift的存在类型any IRow虽然协议继承了Identifiable和Hashable,但存在类型本身无法直接满足这些协议的要求。Hashable的hash(into:)和==方法依赖具体的Self类型,而存在类型是动态的,编译器无法确认其具体实现,因此会出现编译错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 10:57:21