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

Swift中如何合并不同数组模型并在SwiftUI中排序展示?

合并两个Swift结构体模型并在SwiftUI列表中排序展示的问题

问题场景

我尝试合并两个结构体模型,希望在SwiftUI自定义列表中完成合并、排序展示,并根据模型类型跳转对应详情页。单独展示单个列表无异常,但合并后控制台输出Optional([]),视图无法正常启动,不清楚如何正确实现需求。

模型定义

第一个模型:BetModel

struct BetModel: Identifiable, Hashable, BetProtocol {
    
    init(
        id: Int64?,
        date: Date,
        team1: String,
        team2: String,
        selectedTeam: SelectedTeam,
        league: String?,
        amount: NSDecimalNumber,
        odds: NSDecimalNumber,
        category: Category,
        tax: NSDecimalNumber,
        profit: NSDecimalNumber,
        isWon: Bool?,
        betNotificationID: String?,
        score: NSDecimalNumber?
    ) {
        self.id = id
        self.date = date
        self.team1 = team1
        self.team2 = team2
        self.selectedTeam = selectedTeam
        self.league = league
        self.amount = amount
        self.odds = odds
        self.category = category
        self.tax = tax
        self.profit = profit
        self.isWon = isWon
        self.betNotificationID = betNotificationID
        self.score = score
    }

    var id: Int64?
    var date: Date
    let team1: String
    let team2: String
    let selectedTeam: SelectedTeam
    let league: String?
    let amount: NSDecimalNumber
    let odds: NSDecimalNumber
    let category: Category
    let tax: NSDecimalNumber
    let profit: NSDecimalNumber
    let isWon: Bool?
    let betNotificationID: String?
    let score: NSDecimalNumber?
}

enum SelectedTeam: Int {
    case team1
    case team2
}

enum Category: String, CaseIterable {
    case football
    case basketball
    case f1
    case tenis
    case volleyball
}

第二个模型:BetslipModel

struct BetslipModel: Identifiable, Hashable, BetProtocol {

    init(
        id: Int64?,
        date: Date,
        name: String,
        amount: NSDecimalNumber,
        odds: NSDecimalNumber,
        category: Category,
        tax: NSDecimalNumber,
        profit: NSDecimalNumber,
        isWon: Bool?,
        betNotificationID: String?,
        score: NSDecimalNumber?
    ) {
        self.id = id
        self.date = date
        self.name = name
        self.amount = amount
        self.odds = odds
        self.category = category
        self.tax = tax
        self.profit = profit
        self.isWon = isWon
        self.betNotificationID = betNotificationID
        self.score = score
    }

    var id: Int64?
    let name: String
    var date: Date
    let amount: NSDecimalNumber
    let odds: NSDecimalNumber
    let category: Category
    let tax: NSDecimalNumber
    let profit: NSDecimalNumber
    let isWon: Bool?
    let betNotificationID: String?
    let score: NSDecimalNumber?
}

定义的协议

protocol BetProtocol: Identifiable, Hashable {
    var id: Int64? { get set }
    var date: Date { get set }
}

ViewModel中的合并尝试

@Published
var historyBets: [BetModel]? = []

@Published
var betslipHistory: [BetslipModel]? = []

var mergedBets: [any BetProtocol]? {
    ((historyBets ?? []) + (betslipHistory ?? []))
}

SwiftUI视图代码

LazyVStack(spacing: 12) {
    ForEach(
        vm.mergedBets, id: \.id) { bet in
            if let betModel = bet as? BetModel {
                NavigationLink(
                    destination: BetDetailsScreen(bet: bet)
                ) {
                    BetListElement(bet: bet, currency: vm.currency)
                }
            } else if let betslipModel = bet as? BetslipModel {
                NavigationLink(
                    destination: BetslipDetailsScreen(bet: bet)
                ) {
                    BetslipListElement(bet: bet, currency: vm.currency)
                }
            }
        }
}

问题修复与优化方案

1. 修正模型语法错误

原模型中存在无效语法:let team1: "String" 这类写法是错误的,必须改为let team1: String,否则代码无法编译通过,这是合并数组为空的潜在原因之一。

2. 优化ViewModel的合并与排序逻辑

  • 避免使用可选数组,直接用非可选空数组初始化,减少可选绑定复杂度:
@Published var historyBets: [BetModel] = []
@Published var betslipHistory: [BetslipModel] = []
  • 调整mergedBets为非可选数组,并添加按日期排序的逻辑(示例为降序):
var mergedBets: [any BetProtocol] {
    let combined = historyBets + betslipHistory
    return combined.sorted { $0.date > $1.date }
}

3. 修正SwiftUI视图的ForEach使用

处理[any BetProtocol]类型数组时,需明确Hashable标识,同时确保导航链接的泛型参数正确传递:

LazyVStack(spacing: 12) {
    ForEach(vm.mergedBets, id: \.self) { bet in
        Group {
            if let betModel = bet as? BetModel {
                NavigationLink(destination: BetDetailsScreen(bet: betModel)) {
                    BetListElement(bet: betModel, currency: vm.currency)
                }
            } else if let betslipModel = bet as? BetslipModel {
                NavigationLink(destination: BetslipDetailsScreen(bet: betslipModel)) {
                    BetslipListElement(bet: betslipModel, currency: vm.currency)
                }
            }
        }
    }
}

若\.self无法正常工作,可在协议中添加稳定ID的计算属性:

protocol BetProtocol: Identifiable, Hashable {
    var id: Int64? { get set }
    var date: Date { get set }
    
    var stableId: Int64 { get }
}

// 在两个模型中实现该属性
extension BetModel {
    var stableId: Int64 {
        id ?? UUID().uuid.int64Value
    }
}

extension BetslipModel {
    var stableId: Int64 {
        id ?? UUID().uuid.int64Value
    }
}

之后ForEach使用id: \.stableId即可。

4. 确保协议的Hashable一致性

若遇到Hashable相关问题,可在协议中明确要求实现哈希逻辑:

protocol BetProtocol: Identifiable, Hashable {
    var id: Int64? { get set }
    var date: Date { get set }
    
    func hash(into hasher: inout Hasher)
}

// 在BetModel中实现
extension BetModel {
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
        hasher.combine(date)
    }
}

// 在BetslipModel中实现
extension BetslipModel {
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
        hasher.combine(date)
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 09:45:55