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:
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:
Question: Why isn't my widget working when tested via TestFlight? What am I doing wrong?
Thank you!
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!