Bernard
Bernard

Reputation: 31

SwiftUI Widget empty when built onto device or simulator

Playing with SwiftUI and WidgetKit recently and faced a nasty problem. My widget seems to be working in the SwiftUI Canvas but it is completely empty when built onto a simulator or device.

Images:

In SwiftUI canvas:

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

When built onto the device:

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

My code:

'''
 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, in context: Context, 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))
    }
}

'''

**Update I noticed that the widget does show if I remove a VStack. But it doesn't show if I add the code back. Removing the 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, 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)
    }
}

It does show the UI elements: https://github.com/beanut/images/blob/main/IMG_002DAA718D1C-1.jpeg?raw=true

Please help me with it and thanks in advance :)!

Upvotes: 3

Views: 1807

Answers (2)

michalronin
michalronin

Reputation: 41

I stumbled upon this question trying to find a fix for this same issue for myself, and have since found a solution.

I don't believe the VStack was the problem here, but rather force unwrapping one or more of the values for the UI elements inside it, as in this line for example:

Text(DateManager().getDayOfWeekInString(date: entry.date)!)

Force unwrapping a nil value was what was stopping drawing widget's UI for all sizes for me. Try unwrapping the values the safe way or provide a fallback by nil coalescing.

Upvotes: 4

pawello2222
pawello2222

Reputation: 54516

This might be because you call WidgetCenter.shared.reloadAllTimelines in the getTimeline function:

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)
}

The whole point of WidgetCenter.shared.reloadAllTimelines is to force refresh the timeline and effectively call the getTimeline function again.

It's a bad idea to call it from inside getTimeline, especially in the loop.

The code in previews may be working because the getTimeline function is called once only and all repetitive calls are ignored.

Upvotes: 1

Related Questions