7
votes

Widgets iOS 14 + SwiftUI + Firebase?

Je suis encore assez nouveau dans SwiftUI et Firebase. Récemment, en tant que passe-temps, j'ai développé une application pour mon école. Après le lancement de Xcode 12, j'ai décidé d'expérimenter les nouvelles fonctionnalités telles que les widgets. Cependant, depuis que mon application obtient ses données de Firebase, j'ai eu quelques problèmes. Mon problème le plus récent est ce "Thread 1:" Impossible d'obtenir l'instance FirebaseApp. Veuillez appeler FirebaseApp.configure () avant d'utiliser Firestore ". Je ne sais pas trop où placer" FirebaseApp.configure () "car il n'y a pas d'AppDelegate.swift pour le widget. Mon code est ci-dessous.

Edit: J'ai réorganisé mon code pour que j'obtienne maintenant les données du modèle de données d'application iOS d'origine. Je n’importe donc pas Firebase dans le fichier Swift des widgets. Cependant, j'obtiens toujours la même erreur ("SendProcessControlEvent: toPid: a rencontré une erreur: Error Domain = com.apple.dt.deviceprocesscontrolservice Code = 8" et "-> 0x7fff5bb6933a <+10>: jae 0x7fff5bb69344; <+20> - Thread 1: "Impossible d'obtenir l'instance FirebaseApp. Veuillez appeler FirebaseApp.configure () avant d'utiliser Firestore" "). J'ai également inclus le code de @Wendy Liga, mais j'ai toujours la même erreur. Mon nouveau code est ci-dessous:

Modèle de données d'application iOS

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

Vue Widgets

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

Widgets @main

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!")
            }
        }
    }
}


3 commentaires

Je ne peux pas vraiment répondre à cette question car j'en ai le même problème. Mais pour vous aider à trouver plus d'informations, vous devrez peut-être encapsuler vos modèles de données et votre logique dans un package rapide, puis importer les modules. Dans mon cas, je suis coincé dans l'ajout de Firebase et de Realm en tant que dépendance dans mon package swift. Regardez cette vidéo, où ils font de même, mais avec une extension de montre au lieu de l'extension de widget: developer.apple.com/wwdc19/410


Avez-vous trouvé une solution @ j-arango @ daniel-ho?


@lazerrouge je l'ai fait, je vais donner une réponse.


3 Réponses :


-1
votes

Vous pouvez ajouter l' appDelegate à votre vue @main SwiftUI

Créez d'abord votre appdelegate sur votre extension de widget

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

    private let kind: String = "ExampleWidget"

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

regardez @main, dans l'extension de votre widget,

import Firebase

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

@main est une nouvelle fonctionnalité swift 5.3 qui permet un point d'entrée de type valeur, ce sera donc votre principal point d'entrée pour votre extension de widget

ajoutez simplement @UIApplciationDelegateAdaptor , dans votre @main


2 commentaires

Salut! Merci pour votre réponse! Malheureusement, même après avoir utilisé votre code, j'ai toujours la même erreur. "-> 0x7fff5bb6933a <+10>: jae 0x7fff5bb69344; <+20>" - "Thread 1:" Impossible d'obtenir l'instance FirebaseApp. Veuillez appeler FirebaseApp.configure () avant d'utiliser Firestore "" Si cela aide, j'ai inclus 2 images. send.firefox.com/download/be18829cae081518/… , send.firefox.com/download/1c7e750ebb55b28a/...


Cela ne fonctionnera pas, pour une raison quelconque, appDelegate n'est pas appelé à partir de la portée du widget, veuillez me prouver que j'ai déjà testé.



6
votes

Votre application principale doit transmettre des données à votre extension, ceci peut être réalisé en autorisant votre application à utiliser la fonctionnalité "Groupes d'applications". Ce que font les groupes d'applications, c'est qu'ils créent un conteneur dans lequel votre application peut enregistrer des données que vous pouvez partager avec vos extensions d'application. Suivez donc ces étapes pour activer les "Groupes d'applications".

1. Sélectionnez votre cible d'application principale> Signature et capacités, puis appuyez sur + Capacité et sélectionnez "Groupes d'applications"

entrez la description de l'image ici

2. Appuyez sur "+" pour ajouter un nouveau conteneur et ajoutez-lui un nom après le groupe. exemple: "group.com.widgetTest.widgetContainer"

entrez la description de l'image ici

Une fois que vous avez créé le "Groupe d'applications" sur votre application principale, vous devez suivre les mêmes étapes mais sur votre cible "Extension de widget". Cette fois, au lieu de créer un conteneur, vous devriez pouvoir sélectionner le conteneur que vous avez déjà dans l'application principale. Vous pouvez trouver une bonne vidéo sur YouTube expliquant très bien ce processus ici Comment partager les paramètres par défaut avec les extensions d'application

La prochaine étape que je recommande est de créer un package Swift ou un Framework, et d'ajouter un nouvel objet modèle, cet objet modèle est celui que vous passerez de votre application principale à votre extension de widget. J'ai choisi un package Swift.

Pour le faire, suivez ces étapes:

1. Fichier> Nouveau> Package Swift

entrez la description de l'image ici

Une bonne vidéo de la WWDC19 à ce sujet peut être vue ici

2. Dans votre package Swift, dans le dossier "Sources", créez un modèle personnalisé que vous utiliserez à la fois dans votre application principale et dans l'extension de widget.

entrez la description de l'image ici

Rendre votre objet conforme à "Codable" et qu'il est public.

Important Assurez-vous d'importer "Foundation" afin que lorsque vous décodez / encodez votre objet, il le fasse correctement.

3. Ajoutez votre package à votre application principale et à l'extension de widget

  • Sélectionnez la cible de votre application> Général> Faites défiler jusqu'à "Cadres, bibliothèques et contenu intégré"
  • Appuyez sur "+" et recherchez votre forfait

entrez la description de l'image ici

  • Faites les mêmes étapes sur l'extension de votre widget

entrez la description de l'image ici

Maintenant, tout ce que vous avez à faire est «d'importer» votre module dans le fichier que vous allez créer votre objet personnalisé à la fois dans votre application principale et sur votre extension WidgetExtension, puis initialisez votre objet partagé sur votre application principale et enregistrez-le dans UserDefaults en encoder d'abord l'objet en JSON, puis l'enregistrer dans 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)")
   }

Ensuite, dans votre extension de widget, vous souhaitez récupérer votre objet de UserDefaults, le décoder et vous devriez être prêt à partir.

Réponse courte

Téléchargez vos données Firebase, créez un nouvel objet à partir de ces données, encodez-le en JSON, enregistrez-le sur votre conteneur à l'aide de UserDefaults, récupérez l'objet dans votre extension à partir du conteneur, décodez-le et utilisez-le pour votre entrée de widget. Bien sûr, tout cela suppose que vous suivez les étapes ci-dessus.


2 commentaires

Excellente explication mais que se passe-t-il si l'application principale ne fonctionne plus en arrière-plan et que les données distantes de Firebase sont toujours à jour? Le widget aurait un tas de données périmées. Dans ce cas, le widget devrait-il se lier directement à Firebase et obtenir le jeton d'authentification de l'application principale pour accéder directement aux informations?


C'est une excellente question. J'ai répondu à cela en partageant les données de l'application principale vers le widget. Je suis sûr que si vous souhaitez que votre widget interagisse directement avec Firebase, vous devrez implémenter une sorte de connectivité entre le widget et Firebase. Mais gardez à l'esprit que le widget n'appellera une URL que lors de la mise à jour de la chronologie ou lorsque la chronologie met à jour le widget.



0
votes

Je peux confirmer après avoir testé que la méthode suivante fonctionne pour utiliser Firebase dans la cible de widget sans incorporer un groupe d'applications, les valeurs par défaut de l'utilisateur ou autre.

@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.")
  }
}

Utilisez simplement la méthode init dans votre widget pour accéder à une instance de Firebase.

C'était la solution la plus simple pour moi à ce jour.

Tiré de: https://github.com/firebase/firebase-ios-sdk/issues/6683


0 commentaires