iOS14小部件在本地工作,但通过TestFlight失败

我有一个带有小部件的 SwiftUI 应用程序。当我通过 Xcode(直接在我的设备上或在模拟器上)运行应用程序时,小部件完全按预期工作。

但是,当我通过 TestFlight 运行应用程序时,小部件确实出现了,但它没有显示任何数据——它只是一个空的占位符。小部件应该显示图像和一些文本,但它既不显示。

我在 Apple Developer 论坛上看到了一些关于类似问题的帖子。一个被接受的答案是这样说的:

  1. 确保您在设备上使用 Xcode 12 beta 4 和 iOS 14 beta 4。确保你已经实现了占位符(in:)。确保您没有 placeholder(with:) 因为这是 Xcode 的先前测试版建议自动完成的,否则您将无法让占位符工作。我认为这整个问题是由 WidgetKit 方法重命名造成的,但那是另一回事了。
  2. 根据发行说明,您需要在扩展目标的构建设置中将“Dead Code Stripping”设置为 NO。这仅对扩展的目标是必需的。
  3. 将您的档案上传到 App Store Connect 时,取消选中“包括 iOS 内容的位码”。
  4. 安装新 Beta 版时从设备中删除旧版本。

我已经实施了这些建议,但无济于事。

这是我的小部件代码。它首先通过 CloudKit 获取游戏数据,然后创建一个时间线:

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])
    }
}

问题:为什么我的小部件在通过 TestFlight 分发时不起作用?我有什么选择,在这里?

谢谢!

更新:
如果我使用unredacted()视图修饰符,小部件会显示图像以及“LEVEL”和“SCORE”文本,但仍不显示任何实际数据。所以,我的 SwiftUI 视图现在看起来像这样:

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

更新 #2:
在文章Keeping A Widget Up To Date 中,有一节讨论了后台网络请求:

当您的小部件扩展处于活动状态时,例如提供快照或时间线时,它可以发起后台网络请求。例如,获取队友当前状态的游戏小部件,或获取带有图像缩略图的标题的新闻小部件。发出异步的后台网络请求可以让您快速将控制权返回给系统,从而降低因响应时间过长而被终止的风险。

我是否需要设置这个(复杂的)后台请求范例才能让 CloudKit 为我的小部件工作?我在正确的轨道上吗?

以上是iOS14小部件在本地工作,但通过TestFlight失败的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>