I have a SwiftUI app with a widget. When I run the app via Xcode (either straight to my device or on the simulator), the widget works exactly as expected.
However, when I run the app through TestFlight, the widget does appear, but it does not show any data -- it's just the empty placeholder. The widget is supposed to show an image and some text, but it shows neither.
I've seen some posts on Apple Developer forums about similar problems. One accepted answer says the following:
I've implemented these suggestions, to no avail.
Here's my code for the widget. It first fetches game data via CloudKit, then creates a timeline:
import WidgetKit
import SwiftUI
import CloudKit
struct WidgetCloudKit {
static var gameLevel: Int = 0
static var gameScore: String = ""
}
struct Provider: TimelineProvider {
private var container = CKContainer(identifier: "MyIdentifier")
static var hasFetchedGameStatus: Bool = false
func placeholder(in context: Context) -> SimpleEntry {
return SimpleEntry(date: Date(), gameLevel: 0, gameScore: "0")
}
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
let entry: SimpleEntry
if context.isPreview && !Provider.hasFetchedGameStatus {
entry = SimpleEntry(date: Date(), gameLevel: 0, gameScore: "0")
} else {
entry = SimpleEntry(date: Date(), gameLevel: WidgetCloudKit.gameLevel, gameScore: WidgetCloudKit.gameScore)
}
completion(entry)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
let pred = NSPredicate(value: true)
let sort = NSSortDescriptor(key: "creationDate", ascending: false)
let q = CKQuery(recordType: "gameData", predicate: pred)
q.sortDescriptors = [sort]
let operation = CKQueryOperation(query: q)
operation.desiredKeys = ["level", "score"]
operation.resultsLimit = 1
operation.recordFetchedBlock = { record in
DispatchQueue.main.async {
WidgetCloudKit.gameLevel = record.value(forKey: "level") as? Int ?? 0
WidgetCloudKit.gameScore = String(record.value(forKey: "score") as? Int ?? 0)
Provider.hasFetchedGameStatus = true
var entries: [SimpleEntry] = []
let date = Date()
let entry = SimpleEntry(date: date, gameLevel: WidgetCloudKit.gameLevel, gameScore: WidgetCloudKit.gameScore)
entries.append(entry)
// Create a date that's 15 minutes in the future.
let nextUpdateDate = Calendar.current.date(byAdding: .minute, value: 15, to: date)!
let timeline = Timeline(entries: entries, policy: .after(nextUpdateDate))
completion(timeline)
}
}
operation.queryCompletionBlock = { (cursor, error) in
DispatchQueue.main.async {
if let error = error {
print("queryCompletion error: \(error)")
} else {
if let cursor = cursor {
print("cursor: \(cursor)")
}
}
}
}
self.container.publicCloudDatabase.add(operation)
}
}
struct SimpleEntry: TimelineEntry {
var date: Date
var gameLevel: Int
var gameScore: String
}
struct WidgetEntryView : View {
var entry: Provider.Entry
var body: some View {
GeometryReader { geo in
VStack {
Image("widgetImage")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: geo.size.width)
HStack {
VStack {
Text("LEVEL")
Text(entry.gameLevel == 0 ? "-" : "\(entry.gameLevel)")
}
VStack {
Text("SCORE")
Text(entry.gameScore == "0" ? "-" : entry.gameScore)
}
}
}
}
}
}
@main
struct Widget: SwiftUI.Widget {
let kind: String = "MyWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
WidgetEntryView(entry: entry)
}
.configurationDisplayName("Game Status")
.description("Shows an overview of your game status")
.supportedFamilies([.systemSmall])
}
}
Question: Why isn't my widget working when distributed through TestFlight? What are my options, here?
Thank you!
Update:
If I use the unredacted()
view modifier, the widget shows the image and the "LEVEL" and "SCORE" text, but still does not show any actual data. So, my SwiftUI view now looks like this:
struct WidgetEntryView : View {
var entry: Provider.Entry
var body: some View {
GeometryReader { geo in
VStack {
Image("widgetImage")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: geo.size.width)
HStack {
VStack {
Text("LEVEL")
Text(entry.gameLevel == 0 ? "-" : "\(entry.gameLevel)")
}
VStack {
Text("SCORE")
Text(entry.gameScore == "0" ? "-" : entry.gameScore)
}
}
}
.unredacted() // <-- I added this
}
}
}
Update #2: In the article Keeping A Widget Up To Date, there's a section that talks about background network requests:
When your widget extension is active, like when providing a snapshot or timeline, it can initiate background network requests. For example, a game widget that fetches your teammate’s current status, or a news widget that fetches headlines with image thumbnails. Making asynchronous background network requests let you return control to the system quickly, reducing the risk of being terminated for taking too long to respond.
Do I need to set up this (complicated) background request paradigm in order to make CloudKit work for my widget? Am I on the right track?
Widgets not working or updating on iPhone might be due to outdated iOS software. Head to Settings and select General. Go to Software Update and install the latest available iOS build.
If you want to distribute your app to registered devices, to beta testers using TestFlight, or through the App Store, you need to join the Apple Developer Program.
You need iOS 14 or later to use Smart Stacks or create your own widget stacks.
Before iOS 14, widgets and their issues use to be Android’s sole property. Not many iPhone users complained about its absence, but almost all of them wished for Apple to address it and come up with an alternative. The Cupertino giant took its time. But now that it’s released, the wait seems to be worth it.
Unfortunately, iOS 14 has broken the app for many unsatisfied patrons, throwing them out of the app as soon as they try to log in to it. Disappointing as it is, there is no magic fix for the issues the app is facing. You could remove and re-install the app, but that might not fetch the best results.
To update manually, go to App Store > Tap ‘Today’ at the bottom of the screen > Tap profile icon > Scroll down to see the pending updates and tap on Textastic. 2. Default mail app not working Anyone else having major issues with iOS 14? The mail app doesn’t show mails.
Apple’s Mail, of course, is a first-party app, but iOS 14 is not taking any prisoners at the moment. Users have claimed that the default mail app has gone berserk and fails to display emails properly. Apple must have been notified of the grave issue and is likely to fix it with an update in the near future.
Did you try to deploy the cloudkit container to the production? You can find it on the CloudKit Dashboard.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With