iOS 14 小部件在本地工作,但通过 TestFlight 失败 [英] iOS 14 widget works locally, but fails via TestFlight

查看:34
本文介绍了iOS 14 小部件在本地工作,但通过 TestFlight 失败的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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.

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

我在 Apple 开发者论坛上看到了一些关于类似问题的帖子.一个被接受的答案说如下:

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

  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 版时从设备中删除旧版本.

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

I've implemented these suggestions, to no avail.

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

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

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

Question: Why isn't my widget working when distributed through TestFlight? What are my options, here?

谢谢!

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

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

更新 #2:在文章让小部件保持最新,有一节讲后台网络请求:

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.

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

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?

推荐答案

您是否尝试将 cloudkit 容器部署到生产环境?您可以在 CloudKit 仪表板上找到它.

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

这篇关于iOS 14 小部件在本地工作,但通过 TestFlight 失败的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆