iOS 14 小部件 + SwiftUI + Firebase?

Dan*_* Ho 9 firebase widgetkit swiftui

我对 SwiftUI 和 Firebase 还是很陌生。最近,作为一种爱好,我一直在为我的学校开发一个应用程序。Xcode 12 发布后,我决定尝试使用 Widgets 等新功能。但是,由于我的应用从 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!")
            }
        }
    }
}

Run Code Online (Sandbox Code Playgroud)

小部件视图

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()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

小工具@main

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])
    }
}
Run Code Online (Sandbox Code Playgroud)

Jpr*_*ial 8

经过测试,我可以确认以下方法可以在 Widget Target 中使用 Firebase,而无需合并应用程序组、用户默认值或其他任何内容。

@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.")
  }
}
Run Code Online (Sandbox Code Playgroud)

只需使用init小部件中的方法即可访问 Firebase 实例。

到目前为止,这对我来说是最简单的解决方案。

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

附加编辑:您需要共享身份​​验证吗?没问题。Firebase 的内容如下:https ://firebase.google.com/docs/auth/ios/single-sign-on?authuser=1


J A*_*ngo 7

您的主应用程序需要将数据传递给您的扩展程序,这可以通过允许您的应用程序使用“应用程序组”功能来实现。App Groups 所做的是,它创建一个容器,您的应用程序可以在其中保存数据以供您与您的应用程序扩展共享。因此,请按照以下步骤启用“应用程序组”。

1. 选择您的主要 App Target>Signing & Capabilities,然后点击 + Capability 并选择“App Groups”

在此处输入图片说明

2. 点击“+”添加一个新容器,并在分组后为其添加名称。例如:“group.com.widgetTest.widgetContainer”

在此处输入图片说明

在主应用程序上创建“应用程序组”后,您应该执行相同的步骤,但在“小部件扩展”目标上。这一次,您应该能够从主应用程序中选择您已有的容器,而不是创建容器。您可以在 YouTube 上找到一个很好的视频,在此处如何与应用扩展程序共享 UserDefaults中非常好地解释了此过程

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

为此,请执行以下步骤:

1.文件>新建>Swift包

在此处输入图片说明

来自 WWDC19 的关于此的好视频可以在这里看到

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

在此处输入图片说明

使您的对象符合“Codable”并且它是公共的。

重要 确保导入“Foundation”,以便在解码/编码对象时,它会正确执行。

3. 将您的包添加到您的主应用程序和小部件扩展

  • 选择您的应用程序的目标>常规>滚动到“框架、库和嵌入式内容”
  • 点击“+”并搜索您的包裹

在此处输入图片说明

  • 对您的 Widget 的扩展执行相同的步骤

在此处输入图片说明

现在,您需要做的就是在您将在主应用程序和 WidgetExtension 上创建自定义对象的文件中“导入”您的模块,然后在主应用程序上初始化共享对象并将其保存到 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)")
   }

Run Code Online (Sandbox Code Playgroud)

然后在你的小部件扩展中,你想从 UserDefaults 检索你的对象,解码它,你应该很高兴。

简答

下载您的 Firebase 数据,从该数据创建一个新对象,将其编码为 JSON,使用 UserDefaults 将其保存在您的容器中,从容器中检索扩展程序中的对象,对其进行解码并将其用于您的小部件条目。当然,所有这些都假设您按照上述步骤操作。


Wen*_*iga -4

您可以将其添加appDelegate到您的@mainSwiftUI 视图中

首先在您的小部件扩展上创建您的 appdelegate

import Firebase

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

看看@main,在你的小部件扩展中,

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

    private let kind: String = "ExampleWidget"

    public var body: some WidgetConfiguration {
       ...
    }
}
Run Code Online (Sandbox Code Playgroud)

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

只需添加@UIApplciationDelegateAdaptor, 在你的@main