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
相关产品推荐
相关产品推荐

