SwiftUI Widget 在设备或模拟器上构建时为空

问题描述

最近在玩 SwiftUI 和 WidgetKit,遇到了一个棘手的问题。我的小部件似乎可以在 SwiftUI Canvas 中运行,但在构建到模拟器或设备上时它完全是空的。

图片

在 SwiftUI 画布中:

https://github.com/beanut/images/blob/main/Screenshot%202020-12-19%20at%204.00.57%20PM.png?raw=true

内置到设备上时:

https://github.com/beanut/images/blob/main/Screenshot%202020-12-19%20at%204.01.13%20PM.png?raw=true

我的代码

'''
 import WidgetKit
import SwiftUI
import Intents

struct Provider: IntentTimelineProvider {
    func placeholder(in context: Context) -> SimpleEntry {
        SimpleEntry(date: Date())
    }
    
    func getSnapshot(for configuration: ConfigurationIntent,in context: Context,completion: @escaping (SimpleEntry) -> ()) {
        let entry = SimpleEntry(date: Date())
        completion(entry)
    }
    
    func getTimeline(for configuration: ConfigurationIntent,completion: @escaping (Timeline<Entry>) -> ()) {
        var entries = [SimpleEntry]()
        let currentDate = Date()
        let midnight = Calendar.current.startOfDay(for: currentDate)
        let nextMidnight = Calendar.current.date(byAdding: .day,value: 1,to: midnight)!
        //To refresh timeline every min
        for offset in 0 ..< 60 * 24 {
            let entryDate = Calendar.current.date(byAdding: .minute,value: offset,to: midnight)!
            entries.append(SimpleEntry(date: entryDate))
        }
        
        let timeline = Timeline(entries: entries,policy: .after(nextMidnight))
        completion(timeline)
    }
}

struct SimpleEntry: TimelineEntry {
    let date: Date
}

struct WidgetEntryView : View {
    var entry: Provider.Entry
    
    var body: some View {
            HStack() {
                vstack {
                    Spacer()
                    vstack(alignment: .leading) {
                        Text(DateManager().getDayOfWeekInString(date: entry.date)!)
                            .font(Font(UIFont(name: "HoeflerText-Italic",size: 44)!))
                            .foregroundColor(.white)
                            .multilineTextAlignment(.trailing)
                            .opacity(1)
                        Text("\(DateManager().getDayAsstring(date: entry.date)) \(DateManager().monthAsstring(date: entry.date))")
                            .font(Font(UIFont(name: "copperplate",size: 26)!))
                            .multilineTextAlignment(.trailing)
                            .opacity(1)
                            .foregroundColor(.gray)
                    }
                }
                .padding(.leading,20)
                .padding(.bottom,20)
                Spacer()
                vstack(alignment: .trailing,spacing: -20) {
                    Text(DateManager().getHour(date: entry.date))
                        .font(Font(UIFont(name: "copperplate-Bold",size: 86)!))
                        .foregroundColor(.white)
                        .multilineTextAlignment(.trailing)
                        .opacity(1)
                    Text(DateManager().getMinuteWithTwoDigits(date: entry.date))
                        .font(Font(UIFont(name: "copperplate-Bold",size: 86)!))
                        .multilineTextAlignment(.trailing)
                        .opacity(1)
                        .foregroundColor(.gray)
                }
                .padding(.trailing,15)
            }
            .frame(maxWidth: .infinity,maxHeight: .infinity)
            .background(Image(uiImage: #imageLiteral(resourceName: "moon")),alignment: .center)
    }
}

@main
struct Widget: SwiftUI.Widget {
    let kind: String = "Widget"
    
    var body: some WidgetConfiguration {
        IntentConfiguration(kind: kind,intent: ConfigurationIntent.self,provider: Provider()) { entry in
            WidgetEntryView(entry: entry)
        }
        .configurationdisplayName("TestWidget")
        .description("This is an example widget.")
    }
}

struct Widget_Previews: PreviewProvider {
    static var previews: some View {
        WidgetEntryView(entry: SimpleEntry(date: Date()))
            .previewContext(WidgetPreviewContext(family: .systemMedium))
    }
}

'''

**更新 我注意到如果我删除 vstack,小部件会显示。但是如果我重新添加代码,它不会显示移除 vstack:

struct WidgetEntryView : View {
    var entry: Provider.Entry
    
    var body: some View {
            HStack() {
//                vstack {
//                    Spacer()
//                    vstack(alignment: .leading) {
//                        Text(DateManager().getDayOfWeekInString(date: entry.date)!)
//                            .font(Font(UIFont(name: "HoeflerText-Italic",size: 44)!))
//                            .foregroundColor(.white)
//                            .multilineTextAlignment(.trailing)
//                            .opacity(1)
//                        Text("\(DateManager().getDayAsstring(date: entry.date)) \(DateManager().monthAsstring(date: entry.date))")
//                            .font(Font(UIFont(name: "copperplate",size: 26)!))
//                            .multilineTextAlignment(.trailing)
//                            .opacity(1)
//                            .foregroundColor(.gray)
//                    }
//                }
//                .padding(.leading,20)
//                .padding(.bottom,alignment: .center)
    }
}

它确实显示了 UI 元素: https://github.com/beanut/images/blob/main/IMG_002DAA718D1C-1.jpeg?raw=true

请帮助我并提前致谢:)!

解决方法

这可能是因为您在 WidgetCenter.shared.reloadAllTimelines 函数中调用了 getTimeline

func getTimeline(for configuration: ConfigurationIntent,in context: Context,completion: @escaping (Timeline<Entry>) -> ()) {
    ...
    for offset in 0 ..< 60 * 24 {
        WidgetCenter.shared.reloadAllTimelines() // <- this is wrong
        let entryDate = Calendar.current.date(byAdding: .minute,value: offset,to: midnight)!
        entries.append(SimpleEntry(date: entryDate))
    }
    
    let timeline = Timeline(entries: entries,policy: .after(nextMidnight))
    completion(timeline)
}

WidgetCenter.shared.reloadAllTimelines 的全部意义在于强制刷新时间线并有效地再次调用 getTimeline 函数。

getTimeline 内部调用它是个坏主意,尤其是在循环中。

预览中的代码可能有效,因为 getTimeline 函数仅被调用一次并且所有重复调用都被忽略。