Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

  1. 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.
  2. 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.
  3. When uploading your archive to the App Store Connect, uncheck "Include bitcode for iOS content".
  4. 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:

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?

like image 446
West1 Avatar asked May 11 '21 07:05

West1


People also ask

Why are my iOS 14 widgets not working?

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.

Do you need Apple Developer Program for TestFlight?

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.

Do you need iOS 14 for widgets?

You need iOS 14 or later to use Smart Stacks or create your own widget stacks.

Does IOS 14 have widgets?

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.

Is your app broken by iOS 14?

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.

How to update textastic to iOS 14?

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.

Why is Apple’s mail not showing emails properly on iOS 14?

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.


1 Answers

Did you try to deploy the cloudkit container to the production? You can find it on the CloudKit Dashboard.

like image 54
Yu23333 Avatar answered Nov 11 '22 15:11

Yu23333