iOS 14小部件 + SwiftUI + Firebase?

10

我对SwiftUI和Firebase仍然比较新。最近,作为一项兴趣爱好,我一直在开发一款面向我的学校的应用程序。在Xcode 12发布之后,我决定尝试使用诸如小部件之类的新功能。但是,由于我的应用程序从Firebase获取数据,所以我遇到了一些问题。我最近的问题是:"线程1:“无法获取FirebaseApp实例。请在使用Firestore之前调用FirebaseApp.configure()。” 我不确定在小部件中放置“FirebaseApp.configure()”代码应该放在哪里,因为该小部件没有AppDelegate.swift文件。下面是我的代码。

编辑: 我重新排列了代码,现在我正在从原始iOS应用程序数据模型中获取数据。因此,在小部件的Swift文件中,我不再导入Firebase。但是,我仍然遇到相同的错误(“SendProcessControlEvent:toPid:遇到错误:Error Domain=com.apple.dt.deviceprocesscontrolservice Code=8”,以及“->0x7fff5bb6933a <+10>: jae 0x7fff5bb69344 ; <+20> - 线程1:“无法获取FirebaseApp实例。请在使用Firestore之前调用FirebaseApp.configure()。”)。我还包括了@Wendy Liga的代码,但我仍然遇到了相同的错误。下面是我的更新代码:

iOS应用程序数据模型

import Foundation
import SwiftUI
import Firebase
import FirebaseFirestore

struct Assessment: Identifiable {
    var id:String = UUID().uuidString
    var Subject:String
    var Class:Array<String>
    var Day:Int
    var Month:String
    var Title:String
    var Description:String
    var Link:String
    var Crit:Array<String>
}

class AssessmentsViewModel:ObservableObject {
    @Published var books = [Assessment]()
    
    private var db = Firestore.firestore()
    
    // Add assessment variables
    @Published var AssessmentSubject:String = ""
    //@Published var AssessmentClass:Array<String> = [""]
    @Published var AssessmentDay:Int = 1
    @Published var AssessmentMonth:String = "Jan"
    @Published var AssessmentTitle:String = ""
    @Published var AssessmentDescription:String = ""
    @Published var AssessmentLink:String = ""
    @Published var AssessmentCrit:Array<String> = [""]
    @Published var AssessmentDate:Date = Date()
    
    func fetchData() {
        db.collection("AssessmentsTest").order(by: "date").addSnapshotListener { (QuerySnapshot, error) in
            guard let documents = QuerySnapshot?.documents else {
                print("No documents")
                return
            }
            
            self.books = documents.map { (QueryDocumentSnapshot) -> Assessment in
                let data = QueryDocumentSnapshot.data()
                
                let Subject = data["subject"] as? String ?? ""
                let Class = data["class"] as? Array<String> ?? [""]
                let Day = data["day"] as? Int ?? 0
                let Month = data["month"] as? String ?? ""
                let Title = data["title"] as? String ?? ""
                let Description = data["description"] as? String ?? ""
                let Link = data["link"] as? String ?? ""
                let Crit = data["crit"] as? Array<String> ?? [""]
                
                return Assessment(Subject: Subject, Class: Class, Day: Day, Month: Month, Title: Title, Description: Description, Link: Link, Crit: Crit)
            }
        }
    }
    
    func writeData() {
        let DateConversion = DateFormatter()
        DateConversion.dateFormat = "DD MMMM YYYY"
        let Timestamp = DateConversion.date(from: "20 June 2020")
        
        db.collection("AssessmentsTest").document(UUID().uuidString).setData([
            "subject": AssessmentSubject,
            "month": AssessmentMonth,
            "day": AssessmentDay,
            "title": AssessmentTitle,
            "description": AssessmentDescription,
            "link": AssessmentLink,
            "crit": AssessmentCrit,
            "date": AssessmentDate
        ]) { err in
            if let err = err {
                print("Error writing document: \(err)")
            } else {
                print("Document successfully written!")
            }
        }
    }
}

小部件视图

struct WidgetsMainView: View {
    
    @ObservedObject private var viewModel = AssessmentsViewModel()
    
    var body: some View {
        HStack {
            Spacer().frame(width: 10)
            VStack(alignment: .leading) {
                Spacer().frame(height: 10)
                
                ForEach(self.viewModel.books) { Data in
                    HStack {
                        VStack {
                            Text(String(Data.Day))
                                .bold()
                                .font(.system(size: 25))
                            Text(Data.Month)
                        }
                        .padding(EdgeInsets(top: 16, leading: 17, bottom: 16, trailing: 17))
                        .background(Color(red: 114/255, green: 112/255, blue: 110/255))
                        .foregroundColor(Color.white)
                        .cornerRadius(10)
                        
                        VStack(alignment: .leading, spacing: 0) {
                            Text("\(Data.Subject) Crit \(Data.Crit.joined(separator: " + "))")
                                .bold()
                            if Data.Title != "" {
                                Text(Data.Title)
                            } else {
                                Text(Data.Class.joined(separator: ", "))
                            }
                        }
                        .padding(.leading, 10)
                    }
                }
                .onAppear {
                    viewModel.books.prefix(2)
                }
                
                Spacer()
            }
            Spacer()
        }
    }
}

主要的小部件

class AppDelegate: NSObject, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        FirebaseApp.configure()
        return true
    }
}

@main
struct AssessmentsWidget: Widget {
    
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    
    private let kind: String = "Assessments Widget"

    public var body: some WidgetConfiguration {
        StaticConfiguration(kind: kind, provider: Provider(), placeholder: PlaceholderView()) { entry in
            AssessmentsWidgetEntryView(entry: entry)
        }
        .configurationDisplayName("Assessments Widget")
        .description("Keep track of your upcoming assessments.")
        .supportedFamilies([.systemMedium])
    }
}

我无法真正回答这个问题,因为我也遇到了类似的问题。但是为了帮助你找到更多信息,你可能需要将数据模型和逻辑封装到一个 Swift 包中,然后导入模块。在我的情况下,我卡在了将 Firebase 和 Realm 作为依赖项添加到我的 Swift 包中。请看一下这个视频,他们做的是相同的事情,但是使用的是手表扩展而不是小部件扩展:https://developer.apple.com/wwdc19/410 - J Arango
你们找到解决方案了吗,@j-arango @daniel-ho? - lazerrouge
@lazerrouge 我已经做了,我会给出答案。 - J Arango
3个回答

10
你的主应用程序需要向扩展传递数据,这可以通过允许应用程序使用“App Groups”功能来实现。所谓“App Groups”,就是创建一个容器,您的应用程序可以在其中保存数据,以与应用程序扩展共享。因此,请按照以下步骤启用“App Groups”。
1. 选择您的主应用程序目标>签名和功能,然后点击+功能并选择“App Groups”。

enter image description here

2. 点击"+"添加新容器,在group后面添加名称。例如:"group.com.widgetTest.widgetContainer"

enter image description here

一旦在主应用程序中创建了“App Group”,您应该采取相同的步骤,但是在“Widget Extension”目标上。这次,不要创建容器,而应该能够从主应用程序中选择已有的容器。您可以在YouTube上找到一个很好的视频,详细解释了这个过程,网址为如何与应用扩展共享UserDefaults

我建议的下一步是创建一个Swift Package或Framework,并添加一个新的Model Object,这个Model Object是您将从主应用程序传递到小部件扩展的对象。我选择了Swift Package。

按照以下步骤操作:

1. 文件>新建>Swift Package

enter image description here

这里有一段关于WWDC19的好视频,点击这里查看。

2. 在你的Swift Package中,在“Sources”文件夹内创建一个自定义模型,该模型将在主应用程序和小部件扩展中使用。

enter image description here

使您的对象符合 "Codable" 并且是公共的。
重要提示:确保导入 "Foundation",以便在解码/编码对象时它将正确执行。
3. 将您的包添加到主应用程序和小部件扩展中:
- 选择您的应用程序目标 > 通用 > 滚动到 "框架、库和嵌入式内容"。 - 点击 "+" 并搜索您的包。

enter image description here

  • 在您的小部件扩展上执行相同的步骤

enter image description here

现在,你需要在创建自定义对象的文件中,在主应用程序和小部件扩展中“导入”你的模块,然后在主应用程序中初始化共享对象并将其编码为JSON格式保存到UserDefaults中,以便首先将对象编码为JSON格式,然后保存到UserDefaults(suiteName:group.com.widgetTest.widgetContainer)
let mySharedObject = MySharedObject(name: "My Name", lastName: "My Last Name")
                   
 do {
     let data = try JSONEncoder().encode(mySharedObject)

      /// Make sure to use your "App Group" container suite name when saving and retrieving the object from UserDefaults
      let container = UserDefaults(suiteName:"group.com.widgetTest.widgetContainer")
          container?.setValue(data, forKey: "sharedObject")
                        
      /// Used to let the widget extension to reload the timeline
      WidgetCenter.shared.reloadAllTimelines()

      } catch {
        print("Unable to encode WidgetDay: \(error.localizedDescription)")
   }

然后在您的小部件扩展中,您需要从UserDefaults中检索您的对象,对其进行解码,然后您就可以开始了。

简短回答

下载您的Firebase数据,从该数据创建一个新对象,将其编码为JSON,在使用UserDefaults保存它时保存到您的容器中,从容器中检索对象,在您的扩展中对其进行解码并用于小部件输入。当然,所有这些都是假设您遵循上述步骤。


1
非常好的解释,但是如果主应用程序不再在后台运行而来自Firebase的远程数据仍在更新,会发生什么?小部件将有一堆过时的数据。在这种情况下,小部件是否必须直接链接到Firebase,并从主应用程序获取身份验证令牌以直接访问信息? - Vivek
这是一个很好的问题。我基于从主应用程序共享数据来回答这个问题。如果您想让您的小部件直接与Firebase交互,那么您需要在小部件和Firebase之间实现某种连接。但请记住,小部件只会在更新时间轴或时间轴更新小部件时调用URL。 - J Arango

7
我可以确认,在测试后,以下方法能够使用Firebase在Widget Target中,而不需要加入应用组、用户默认设置或其他任何东西。
@main
struct FirebaseStartupSequence: Widget {
  init() {
    FirebaseApp.configure()
  }

  let kind: String = "FirebaseStartupSequence"

  var body: some WidgetConfiguration {
    IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
      FirebaseStartupSequenceEntryView(entry: entry)
    }
    .configurationDisplayName("My Widget")
    .description("This is an example widget.")
  }
}

只需在您的小部件中使用init方法即可访问Firebase实例,这是当今最简单的解决方案。

引用自:https://github.com/firebase/firebase-ios-sdk/issues/6683

附加编辑:需要共享身份验证吗? 没问题。 Firebase在此提供了覆盖: https://firebase.google.com/docs/auth/ios/single-sign-on?authuser=1


这种方法是有效的,但是我正在调试我的小部件,它在占位模式下停留了很长一段时间,大约20秒...我觉得是Firebase初始化导致了小部件启动的延迟...:/ 我会进行调查的。 - lorenzo gonzalez

-4

你可以将 appDelegate 添加到你的 @main SwiftUI 视图中

首先在你的小部件扩展中创建你的 appdelegate

import Firebase

class AppDelegate: NSObject, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        FirebaseApp.configure()
        return true
    }
}

看一下@main,在您的小部件扩展内部

@main
struct TestWidget: Widget {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    private let kind: String = "ExampleWidget"

    public var body: some WidgetConfiguration {
       ...
    }
}

@main 是 Swift 5.3 的新功能,允许值类型入口点,因此这将是您的小部件扩展的主要入口点

只需在您的 @main 中添加 @UIApplciationDelegateAdaptor


嗨!感谢您的回答!不幸的是,即使使用了您的代码,我仍然得到了相同的错误。 "-> 0x7fff5bb6933a <+10>: jae 0x7fff5bb69344; <+20>" - "Thread 1:"无法获取FirebaseApp实例。请在使用Firestore之前调用FirebaseApp.configure()"如果有帮助,我已经包含了2张图片。https://send.firefox.com/download/be18829cae081518/#jOq_u01L47J-ocHg1cjOjg,https://send.firefox.com/download/1c7e750ebb55b28a/#bkHZuxZgz0lnve2oTH3MgA - Daniel Ho
这不会起作用,由于某种原因,appDelegate没有从widget范围中被调用,请证明我是错的,我已经测试过了。 - XcodeNOOB

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接