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

iOS Widget开发:如何在Timeline中展示多条内容实现大尺寸多单元格需求

实现方案

你不需要为每个单元格创建单独的Entry结构体,最优方案是在现有NewsEntry中新增数组类型字段存储4条内容数据,比多Entry方案逻辑更简洁、性能更好:Timeline中的Entry是用来区分不同时间点的展示内容,同一时间点你只需要1个Entry携带所有需要展示的4条数据即可。

步骤1:修改数据结构

首先在NewsEntry中新增数组字段,你可以先定义一个轻量的子结构体存储单条新闻内容,保持结构清晰:

// 新增子结构体存储单条新闻数据
struct NewsItem: Codable {
    let header: String
    let imageUrl: String
    // 其他需要的字段按需添加
}

struct NewsEntry: TimelineEntry {
    var date: Date
    let configuration: ConfigurationIntent
    let newsList: [NewsItem] // 新增数组字段存储4条数据
    // 原有单个header、imageUrl字段可以删除
}

步骤2:修改getTimeline请求逻辑

请求到数据后,将4条数据全部存入Entry的newsList字段,不需要创建多个Entry:

func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
    let currentDate = Date()
    let later = Calendar.current.date(byAdding: .minute, value: 5, to: Date())!
    
    NewsProvider.request() { (data, error) in
        guard let data = data else {
            if let error = error {
                print(error)
            }
            // 请求失败也要返回兜底的Entry,避免Widget空白
            let fallbackEntry = NewsEntry(date: currentDate, configuration: configuration, newsList: [])
            let timeline = Timeline(entries: [fallbackEntry], policy: .after(later))
            completion(timeline)
            return
        }
        
        // 将接口返回的data转成NewsItem数组,取前4条即可
        let newsList = data.prefix(4).map { item in
            NewsItem(header: item.title ?? "", imageUrl: item.imageUrlDetail ?? "")
        }
        let entry = NewsEntry(date: currentDate, configuration: configuration, newsList: newsList)
        
        let timeline = Timeline(entries: [entry], policy: .after(later))
        completion(timeline)
    }
}

步骤3:修改EntryView渲染逻辑

遍历entry.newsList渲染每个单元格,每个格子取对应下标的数据即可:

struct WidgetEntryView : View {
    var entry: Provider.Entry
    
    @Environment(\.widgetFamily)
    var widgetFamily

    var body: some View {
        VStack(alignment: .trailing, spacing: 6) {
            Image("logo")
                .frame(maxWidth: .infinity, alignment: .leading)

            if widgetFamily == .systemLarge {
                let columns: [GridItem] = Array(repeating: .init(), count: 2)

                LazyVGrid(columns: columns) {
                    // 遍历新闻数组,而不是写死0..<4
                    ForEach(entry.newsList.indices, id: \.self) { index in
                        let news = entry.newsList[index]
                        ZStack (alignment: .bottomLeading) {
                            if let url = URL(string: news.imageUrl), let imageData = try? Data(contentsOf: url),
                               let uiImage = UIImage(data: imageData) {
                                Image(uiImage: uiImage)
                                    .centerCropped()
                                    .frame(maxHeight: 150, alignment: .center)
                                    .cornerRadius(10)
                                    .overlay(RoundedRectangle(cornerRadius: 10)
                                        .stroke(Color.gray, lineWidth: 1))
                                    .shadow(radius: 10)
                            } else {
                                Image("ph_background")
                                    .centerCropped()
                                    .frame(maxHeight: 150, alignment: .center)
                                    .cornerRadius(10)
                                    .overlay(RoundedRectangle(cornerRadius: 10)
                                        .stroke(Color.gray, lineWidth: 1))
                                    .shadow(radius: 10)
                            }
                                
                            Text(news.header)
                                .font(.system(size: 12))
                                .foregroundColor(.white)
                                .fontWeight(.light)
                                .background(Rectangle().fill(Color.black).blur(radius: 20))
                                .padding(.all, 5)
                        }
                    }
                    .frame(height: 160)
                }
            }
        }
        .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
        .padding()
        .background(LinearGradient(gradient: Gradient(colors: [Color(red:0.2, green:0.2, blue:0.2), .black]), startPoint: .top, endPoint: .bottom))
    }
}

额外优化建议

  • 不要在View中同步加载网络图片:Widget的渲染窗口时间非常短,同步读取网络数据很容易触发系统限制导致加载失败,建议在getTimeline请求新闻数据的时候就提前把所有图片下载好,将图片Data存入NewsItem,渲染时直接用本地数据
  • 做兜底处理:如果接口返回的新闻数量不足4条,可以补充占位的NewsItem,避免出现空白格子
  • 低版本适配:iOS 15及以下不支持SwiftUI的AsyncImage,提前下载图片的方案兼容性更好

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.03 01:45:05