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

SwiftUI遍历实体数组构建树形结构电视节目表视图问题求助

实现方案

核心思路

原有代码问题在于:对每个SchedSlot单独生成HStack,自然无法把同一频道的节目放在同一行。正确的实现需要分两步:先把一维的日程数组按「日期→频道」二级分组,再按分组层级渲染视图。

步骤1:数据分组处理

无需自定义复杂的Node类,直接用Swift高阶函数完成分组,性能更优也更适配SwiftUI的声明式语法:

struct ScheduleDisplayView: View {
    var schedule: [SchedSlot]
    let dfdo = DateFormatter.dateOnly
    let chanwidth: CGFloat = 100.0 // 调整到适合显示频道ID+呼号的宽度
    let programWidth: CGFloat = 200.0
    
    // 二级分组计算属性:外层key是日期字符串,内层key是频道ID,value是对应节目数组
    private var groupedSchedule: [(date: String, channels: [(channel: Channel, programs: [SchedSlot])])] {
        // 第一步:先按日期分组
        let dateGrouped = Dictionary(grouping: schedule) { slot in
            dfdo.string(from: slot.startTime!)
        }
        // 第二步:每个日期内按频道分组
        return dateGrouped.map { date, slots in
            let channelGrouped = Dictionary(grouping: slots) { slot in
                slot.channel!
            }.map { (channel: $0.key, programs: $0.value) }
            // 可以在这里给频道排序,比如按channelID升序
            let sortedChannels = channelGrouped.sorted { $0.channel.channelID < $1.channel.channelID }
            return (date: date, channels: sortedChannels)
        }.sorted { $0.date < $1.date } // 按日期升序排列
    }
    
    // 后续视图部分...
}

如果要沿用之前自定义的Node类,组装逻辑如下:

func buildTree(from slots: [SchedSlot]) -> RootNode {
    let root = RootNode()
    let df = DateFormatter.dateOnly
    for slot in slots {
        let dateStr = df.string(from: slot.startTime!)
        // 查找已存在的日期节点
        var dayNode = root.children.first { $0.date == dateStr }
        if dayNode == nil {
            let newDay = DayNode(date: dateStr)
            root.add(child: newDay)
            dayNode = newDay
        }
        guard let day = dayNode else { continue }
        let chanId = String(slot.channel!.channelID)
        // 查找已存在的频道节点
        var chanNode = day.children.first { $0.channel == chanId }
        if chanNode == nil {
            let newChan = ChannelNode(channel: chanId)
            day.add(child: newChan)
            chanNode = newChan
        }
        chanNode?.add(child: slot)
    }
    return root
}

步骤2:视图渲染实现

直接遍历分组后的二级结构,即可实现预期的排版效果:

var body: some View {
    List {
        // 遍历所有日期
        ForEach(groupedSchedule, id: \.date) { dayItem in
            // 显示日期头
            Text(dayItem.date)
                .font(.headline)
                .frame(maxWidth: .infinity, alignment: .leading)
                .padding(.vertical, 8)
            // 遍历当前日期的所有频道
            ForEach(dayItem.channels, id: \.channel.channelID) { chanItem in
                // 单个频道的行:同一频道的所有节目放在同一个HStack里
                HStack(alignment: .top, spacing: 16) {
                    // 左侧频道信息
                    VStack(spacing: 4) {
                        Text(String(chanItem.channel.channelID))
                        Text(chanItem.channel.callSign ?? "")
                    }
                    .frame(width: chanwidth)
                    // 右侧节目列表,超出屏幕可横向滚动
                    ScrollView(.horizontal, showsIndicators: false) {
                        HStack(spacing: 16) {
                            ForEach(chanItem.programs, id: \.programID) { program in
                                Text(program.program?.title ?? "")
                                    .frame(width: programWidth, alignment: .leading)
                                    .border(Color.black)
                            }
                        }
                    }
                }
                .padding(.vertical, 4)
            }
            // 不同日期之间加分隔
            Divider()
                .padding(.vertical, 8)
        }
    }
    .listStyle(.plain)
}

注意事项

  • 示例代码中所有!强制解包建议替换为可选绑定,避免CoreData数据为空时崩溃
  • 如果不需要横向滚动,去掉ScrollView即可,节目过多时会自动压缩宽度或换行
  • 可根据需要调整频道宽度、节目宽度、间距等布局参数

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.26 10:18:00