iOS 14 widget works locally, but fails via TestFlight

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 appears, but does not show any data. It shows an image, and "SCORE" and "LEVEL" text -- but not the actual score and level.

I've seen some posts on Apple Developer forums about similar problems. One accepted answer says the following:

Make sure that you use Xcode 12 beta 4 and iOS 14 beta 4 on your devices.

Make sure that you have placeholder(in:) implemented. Make sure that you don't have placeholder(with:) because that's what the previous beta of Xcode was suggesting with autocompletion and without that you won't get your placeholder working. I think this whole problem is caused by the WidgetKit methods getting renamed but that's another story.

As per the release notes, you need to set "Dead Code Stripping" to NO in your extension target's build settings. This is only necessary for the extension's target.

When uploading your archive to the App Store Connect, uncheck "Include bitcode for iOS content".

Delete your old build from a device when installing a new beta.



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:
Code Block
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)
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)
}
}
}
.unredacted()
}
}
}
@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 tested via TestFlight? What am I doing wrong?

Thank you!

Accepted Reply

Setting my CloudKit container to "Production" solved the problem.

Replies

I'm having a very similar issue. Although I don't have an answer I'm following as everything is redacted and not working.
Setting my CloudKit container to "Production" solved the problem.