Reputation: 31
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
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
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