Use user location as a determinant in displaying content - firebase

I ask for user location and then extract their location so I can display it. This is how I extract their location:
func extractLocation(){
CLGeocoder().reverseGeocodeLocation(self.userLocation) { (res, err) in
guard let safeData = res else {return}
var address = ""
// address += safeData.first?.name ?? ""
// address += ", "
address += safeData.first?.locality ?? ""
self.userAddress = address
// print(address)
}
}
To display it, I just use #StateObject to refer to the class and then Text(LocationModel.userAddress)
In the app Im currently working, I want to display different content according to the location the users at. Information is stored in Cloud Firestore. As an example, lets have collection "FRIENDS". There, I have 3 auto ID:
name: Peter, location: New York
name: John, location: Los Angeles
name: James, location: Miami
So, if I'm in New York, only Peter's name will be displayed, if I'm in Los Angeles, only John's name will be displayed, and so on.
I thought that using
db.collection("FRIENDS").whereField("location", isEqualTo: LocationModel.userAddress).getDocuments ...
would work, but the app crashed. How would I solve this problem?
EDIT:
this the exact code im using:
struct Test: Identifiable{
var id: String = UUID().uuidString
var name: String
}
class UseLocationViewModel: NSObject,ObservableObject{
#StateObject var LocationModel = LocationViewModel()
let db = Firestore.firestore()
#Published var test = [Test]()
func useLocation(){
db.collection("FRIENDS").whereField("location", isEqualTo: LocationModel.userAddress).getDocuments { (querySnapshot, err) in
guard let documents = querySnapshot?.documents else {return }
self.test = documents.map { (queryDocumentSnapshot) -> Test in
let data = queryDocumentSnapshot.data()
let name = data["Name"] as? String ?? ""
return Test(name: name)
}
}
}
}
struct AnyView: View{
#StateObject var useLocationModel = UseLocationViewModel()
var body: some View{
VStack{
ForEach(useLocationModel.test){ info in
Text(info.name).foregroundColor(Color.white)
}
}.onAppear(){
useLocationModel.useLocation()
}
}
}
EDIT 2: if I use .whereField(LocationModel.userAddress, isEqualTo: "location" ) I get the following errors after launching:
RED ERROR Thread 1: "Invalid field path (). Paths must not be empty, begin with
'.', end with '.', or contain '..'"
PURPLE ERROR Accessing StateObject's object without being installed
on a View. This will create a new instance each time.
If I use it as in edit 1 (.whereField("location", isEqualTo: LocationModel.userAddress)) I only get purple error

Related

Cannot reach to members of an array with index

I am trying to fill an array with data from Firebase. But after filling the array I can't call its' members from their index. I am filling the array by this function
func loadSounds(){
Firestore.firestore().collection("data").getDocuments{ (snapshot, error) in
if error == nil{
for document in snapshot!.documents{
let name = document.data()["name"] as? String ?? "error"
let sounds = document.data()["sounds"] as? [String : [String : Any]]
var soundsArray = [dataSound]()
if let sounds = sounds{
for sound in sounds {
let soundName = sound.value["name"] as? String ?? "error"
let soundImage = sound.value["image"] as? String ?? "error"
soundsArray.append(dataSound(name: soundName , image: soundImage ))
}
}
categoriesArray.append(Category(category: name , sounds: soundsArray))
}
print(categoriesArray[0].category)
} else {
print(error)
}
} }
When I try to access it from View, It gives index out of bounds error.
struct ContentView: View {
init(){
loadSounds()
}
var body: some View {
Text(categoriesArray[0].category)}}
If I try to access it via ForEach, it works, also when I try to print it from loadSounds function it works, but I need to access them from their index in View. Thanks for any help.
Never access items of an array by index in the rendering area of a SwiftUI View, in almost all cases the array is empty when the view is rendered the first time.
In your case use .first and handle the optional
var body: some View {
Text(categoriesArray.first?.category ?? "No value")}} // or empty string

SwiftUI - How to add sub-collection + document to existing document in Firestore

I'm trying to figure out how to add a new sub-collection + document to an already existing document in Firestore. Here's a quick idea of the db:
orgs <-Collection
Acme <-Document (need to grab documentID from here and pass to function)
employees <-Sub-Collection
Marge Simpson <-Document
Homer Simpson <-Document
Acme2 <-Document
The basic idea is:
The user is presented with a list of company names.
User clicks on Acme, is presented with a list of employees that work for Acme.
There will be some way to add a new employee to Acme.
This will likely be a "Add Employee" button on the navigation bar that opens a sheet with text fields where you enter employee information.
When that data is saved, it should save to a new document under orgs > Acme > Employees
My current code, when run, will add a new doc at orgs/ blank doc /employees. I can't figure out how to grab the document ID of the org I'm currently looking at, in this case Acme, and pass that org document ID to the function so it adds the new employee to the correct org (Acme in this example).
Here's my view models:
Org View Model:
class OrgViewModel: ObservableObject {
#Published var orgs = [Org]()
#Published var newOrg: Org
init(newOrg: Org = Org(orgName: "", orgCity: "")) {
self.newOrg = newOrg
}
private var db = Firestore.firestore()
func fetchOrgData() {
db.collection("orgs").addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents
else {
print("No Documents")
return
}
self.orgs = documents.compactMap { (queryDocumentSnapshot) -> Org? in
return try? queryDocumentSnapshot.data(as: Org.self)
}
}
}
}
Employee View Model:
class EmployeeViewModel: ObservableObject {
#Published var employees = [Employee]()
#Published var newEmployee: Employee
init(newEmployee: Employee = Employee(firstName: "", lastName: "", orgName: "")) {
self.newEmployee = newEmployee
}
private var db = Firestore.firestore()
func addEmployeeData(newEmployee: Employee) {
do {
let orgRef = db.collection("orgs").document() // <--How do I pass org documentID here??
let _ = try orgRef.collection("employees").addDocument(from: newEmployee)
}
catch {
print(error)
}
}
func fetchEmployeeData() {
db.collectionGroup("employees").addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents
else {
print("No Documents")
return
}
self.employees = documents.compactMap { (queryDocumentSnapshot) -> Season? in
return try? queryDocumentSnapshot.data(as: Employee.self)
}
}
}
}
This needs to be able to read an existing documentID from an existing org and pass that documentID to addEmployeeData function. I'm new to Swift, so any help is greatly appreciated.

How to prevent Firestore write race conditions for a reservation button

Summary
I'm developing an application where users can reserve and cancel reservations for classes. In a ReservationButtonView I two buttons that add and remove a user to a workout class respectively. Currently the button I show is based off whether the user's Firebase Auth uid is listed in a Firestore document.
I was having issues when rapidly tapping on the reservation button. Specifically, the reservationCnt would become inaccurate by showing more or less than the actual users reserved for a class.
The only way I have found to resolve this is be using a Firestore transaction that checks to see if a user is in a workout class already. If they are, addReservation() now does nothing. If they aren't, removeReservation() would also do nothing.
At first I thought I could just disable the button and via the logic still in place the code below (.disabled()), but that alone didn't work as I ran into the above described race conditions. What I found out is that arrayUnion and arrayRemove still succeed even when the object I'm looking to add is there and not there respectively. Meaning it is possible for my transaction to not remove a reservedUser that isn't there and also decrease the reservationCnt which can leave me with say no reserved users and a reservationCnt of -1
The Ask
Is there a better way to handle this reservation process? Can I accomplish this without a transaction for at least the removal of users in some way. Ideally, I'd like to have a spinner replace the button as I add or remove a user's reservation to indicate to the user that the app is processing the request. Perhaps I need two variables to manage the disabled() state instead of one?
MVVM Code Snippets
NOTE: I pulled out some button styling to make the code a bit less verbose
ReservationButtonView
struct ReservationButtonView: View {
var workoutClass: WorkoutClass
#ObservedObject var viewModel: WorkoutClassViewModel
#EnvironmentObject var authViewModel: AuthViewModel
var body: some View {
if checkIsReserved(uid: authViewModel.user?.uid ?? "", reservedUsers: workoutClass.reservedUsers ?? []) {
Button(action: {
viewModel.isDisabled = true
viewModel.removeReservation(
documentId: workoutClass.id!,
reservedUserDetails: ["uid": authViewModel.user?.uid as Any, "photoURL": authViewModel.user?.photoURL?.absoluteString ?? "" as Any, "displayName": authViewModel.user?.displayName ?? "Bruin Fitness Member" as Any],
uid: authViewModel.user?.uid ?? "")
}){
Label(
title: { Text("Cancel Reservation")
.font(.title) },
icon: { Image(systemName: "person.badge.minus")
.font(.title) }
)
}.disabled(viewModel.isDisabled)
} else{
Button(action: {
viewModel.isDisabled = true
viewModel.addReservation(
documentId: workoutClass.id!,
reservedUserDetails: ["uid": authViewModel.user?.uid as Any, "photoURL": authViewModel.user?.photoURL?.absoluteString ?? "" as Any, "displayName": authViewModel.user?.displayName ?? "Bruin Fitness Member" as Any],
uid: authViewModel.user?.uid ?? "")
}){
Label(
title: { Text("Reserve")
.font(.title) },
icon: { Image(systemName: "person.badge.plus")
.font(.title) }
)
}
.disabled(viewModel.isDisabled)
}
}
}
func checkIsReserved(uid: String, reservedUsers: [reservedUser]) -> Bool {
return reservedUsers.contains { $0.uid == uid }
}
WorkoutClassModel
struct reservedUser: Codable, Identifiable {
var id: String = UUID().uuidString
var uid: String
var photoURL: URL?
var displayName: String?
enum CodingKeys: String, CodingKey {
case uid
case photoURL
case displayName
}
}
struct WorkoutClass: Codable,Identifiable {
#DocumentID var id: String?
var reservationCnt: Int
var time: String
var workoutType: String
var reservedUsers: [reservedUser]?
enum CodingKeys: String, CodingKey {
case id
case reservationCnt
case time
case workoutType
case reservedUsers
}
}
WorkoutClassViewModel
class WorkoutClassViewModel: ObservableObject {
#Published var isDisabled = false
private var db = Firestore.firestore()
func addReservation(documentId: String, reservedUserDetails: [String: Any], uid: String){
let incrementValue: Int64 = 1
let increment = FieldValue.increment(incrementValue)
let addUser = FieldValue.arrayUnion([reservedUserDetails])
let classReference = db.document("schedules/Redwood City/dates/\(self.stateDate.dbDateFormat)/classes/\(documentId)")
db.runTransaction { transaction, errorPointer in
let classDocument: DocumentSnapshot
do {
print("Getting classDocument for docId: \(documentId) in addReservedUser()")
try classDocument = transaction.getDocument(classReference)
} catch let fetchError as NSError {
errorPointer?.pointee = fetchError
return nil
}
guard let workoutClass = try? classDocument.data(as: WorkoutClass.self) else {
let error = NSError(
domain: "AppErrorDomain",
code: -3,
userInfo: [
NSLocalizedDescriptionKey: "Unable to retrieve workoutClass from snapshot \(classDocument)"
]
)
errorPointer?.pointee = error
return nil
}
let isReserved = self.checkIsReserved(uid: uid, reservedUsers: workoutClass.reservedUsers ?? [])
if isReserved {
print("user is already in class so therefore can't be added again")
return nil
} else {
transaction.updateData(["reservationCnt": increment, "reservedUsers": addUser], forDocument: classReference)
return nil
}
} completion: { object, error in
if let error = error {
print(error.localizedDescription)
self.isDisabled = false
} else {
print("Successfully ran transaction with object: \(object ?? "")")
self.isDisabled = false
}
}
}
func removeReservation(documentId: String, reservedUserDetails: [String: Any], uid: String){
let decrementValue: Int64 = -1
let decrement = FieldValue.increment(decrementValue)
let removeUser = FieldValue.arrayRemove([reservedUserDetails])
let classReference = db.document("schedules/Redwood City/dates/\(self.stateDate.dbDateFormat)/classes/\(documentId)")
db.runTransaction { transaction, errorPointer in
let classDocument: DocumentSnapshot
do {
print("Getting classDocument for docId: \(documentId) in addReservedUser()")
try classDocument = transaction.getDocument(classReference)
} catch let fetchError as NSError {
errorPointer?.pointee = fetchError
return nil
}
guard let workoutClass = try? classDocument.data(as: WorkoutClass.self) else {
let error = NSError(
domain: "AppErrorDomain",
code: -3,
userInfo: [
NSLocalizedDescriptionKey: "Unable to retrieve reservedUsers from snapshot \(classDocument)"
]
)
errorPointer?.pointee = error
return nil
}
let isReserved = self.checkIsReserved(uid: uid, reservedUsers: workoutClass.reservedUsers ?? [] )
if isReserved {
transaction.updateData(["reservationCnt": decrement, "reservedUsers": removeUser], forDocument: classReference)
return nil
} else {
print("user not in class so therefore can't be removed")
return nil
}
} completion: { object, error in
if let error = error {
print(error.localizedDescription)
self.isDisabled = false
} else {
print("Successfully ran removeReservation transaction with object: \(object ?? "")")
self.isDisabled = false
}
}
}
func checkIsReserved(uid: String, reservedUsers: [reservedUser]) -> Bool {
return reservedUsers.contains { $0.uid == uid }
}
}
App screenshot
Reservation button is the green/grey button at the bottom of the view
As this is a race condition, You have already acknowledged the use of Transactions for the update which is the most desirable as this can ensure the update is successful before allowing the App to change button status.
I.e. by using a transaction and only updating the UI Button state on success, which is explained here
The recommendation is to keep the state of the button mapped to what is in the document, therefore you are likely to exceed rate limits by updating the same field continuously based on the flipping of the button.
Another way to handle this tracking of the state of enrollment is to add a new document that indicates the state of the enrollment for the user to a collection that is the class they are enrolling in.
I.e. Rather than having the class user enrolling into being a document, make that a collection and each time the enrollment state changes, write a new document. This will allow for updates to occur without using transactions and the current state of enrollments is contained within the latest document. This latest document can be read and used as the status of the button within the App with the added benefit that the state will always update to the status contained within Firestore.
I ended up resolving this by adding a disable check conditional before the conditional that decides whether to show the "Reserve" or "Cancel" button.
This way when my Firestore transaction is running the user will see a spinner instead and can't monkey test the button. The spinner helps to show that the reservation operation is in progress. When the transaction hits its completion block I disable the isDisabled Bool and the listener is in sync (the user then sees the newly toggled button state)
if workoutClassVM.isDisabled {
ProgressView()
.progressViewStyle(CircularProgressViewStyle(tint: Color("bruinGreenColor")))
} else if checkIsReserved(uid: authVM.user?.uid ?? "", reservedUsers: workoutClass.reservedUsers ?? []) {
...

How do I map my ViewModel's ID to the Document ID in Firestore?

I have the fetch Data code here, but I don't understand how I am supposed to delete documents without setting the ID to the Document's ID. I was following this tutorial here. https://medium.com/swift-productions/swiftui-easy-to-do-list-with-firebase-2637c878cf1a I'm assuming I need to do so in the data mapping but I don't understand how with this code. I want to remove a todo from a SwiftUI list and also delete it's entire Firestore Document.
func fetchData() {
db.collection("todos").addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
print("No documents")
return
}
self.todos = documents.map { (QueryDocumentSnapshot) -> Todo in
let data = QueryDocumentSnapshot.data()
let todoDetails = data["todo"] as? String ?? ""
return Todo(todoDetais: todoDetails)
}
}
}
View Model
struct Todo: Codable, Identifiable {
var id: String = UUID().uuidString
var todoDetais: String?
}
I recommend using Codable to map your Firestore documents to Swift structs. This will make your code easier to write, less prone to errors, and more type-safe.
Specifically, it will also enable you to use #DocumentID to map the Firestore document ID to the id attribute of your Swift struct.
Here's a quick example:
struct Book: Codable {
#DocumentID var id: String?
var title: String
var numberOfPages: Int
var author: String
}
func fetchBook(documentId: String) {
let docRef = db.collection("books").document(documentId)
docRef.getDocument { document, error in
if let error = error as NSError? {
self.errorMessage = "Error getting document: \(error.localizedDescription)"
}
else {
if let document = document {
do {
self.book = try document.data(as: Book.self)
}
catch {
print(error)
}
}
}
}
}
For more details, see this comprehensive guide I wrote about mapping Firestore documents to Swift structs (and back).
For more information about how to delete a Firestore document from a SwiftUI app, check out this article

iOS 14 Widgets + SwiftUI + Firebase?

I'm still pretty new to SwiftUI and Firebase. Recently, as a hobby, I have been developing an app for my school. After the launch of Xcode 12, I decided to experiment with the new features such as Widgets. However, since my app gets its data from Firebase, I've been having some problems. My most recent problem is this "Thread 1: "Failed to get FirebaseApp instance. Please call FirebaseApp.configure() before using Firestore". I'm not entirely sure where to put "FirebaseApp.configure()" as there is no AppDelegate.swift for the widget. My code is below.
Edit:
I've rearranged my code so that I am now getting the data from the original iOS app data model. I am therefore not importing Firebase within the widgets Swift file. However, I still get the same error ("SendProcessControlEvent:toPid: encountered an error: Error Domain=com.apple.dt.deviceprocesscontrolservice Code=8" and "-> 0x7fff5bb6933a <+10>: jae 0x7fff5bb69344 ; <+20> - Thread 1: "Failed to get FirebaseApp instance. Please call FirebaseApp.configure() before using Firestore""). I've also included #Wendy Liga's code, but I still got the same error. My newer code is below :
iOS App Data Model
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!")
}
}
}
}
Widgets View
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
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])
}
}
Your main app needs to pass data to your extension, this can be achieved by allowing your app to use "App Groups" capability. What App Groups does is, it creates a container where your app can save data for you to share with your app extensions. So follow these steps to enable "App Groups".
1. Select your main App Target>Signing & Capabilities then tap + Capability and select "App Groups"
2. Tap on "+" to add a new container, and add a name to it after group. example : "group.com.widgetTest.widgetContainer"
Once you have created the "App Group" on your main app, you should take the same steps but on your "Widget Extension" target. This time, instead of creating a container, you should be able to select the container you already have from the main app. You can find a good video on YouTube explaining this process really well on here How to Share UserDefaults with app extensions
The next step I recommend is to create a Swift Package or a Framework, and add a new Model Object, this model object is the one you will be passing from your main app, to your widget extension. I chose a Swift Package.
To do this follow these steps:
1. File>New>Swift Package
A good video from the WWDC19 about this can be seen here
2. In your Swift Package, inside the "Sources" folder, Create a Custom Model which you will use in both your Main App, and Widget Extension
Make your object conform to "Codable" and that it is Public.
Important Make sure you import "Foundation" so that when you are decoding/encoding your object, it will do it properly.
3. Add your Package to your Main App and Widget Extension
Select your App's Target>General> Scroll to "Frameworks, Libraries, and Embedded Content"
Tap "+" and search for your Package
Do the same steps on your Widget's Extension
Now, all you need to do is "import" your module in the file that you will be creating your custom object in both your Main App, and on your WidgetExtension, then initialize your shared object on your main app and save it to UserDefaults by first encoding the object to JSON and then saving it to 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)")
}
Then in your widget extension, you want to retrieve your object from UserDefaults, decode it and you should be good to go.
Short Answer
Download your Firebase data, create a new object from that data, encode it to JSON, save it on your container by using UserDefaults, retrieve the object in your extension from the container, decode it and use it for your widget entry. Of course, all of this is assuming you follow the steps above.
I can confirm after testing that the following method works to use Firebase in the Widget Target without incorporating an app group, user defaults or anything else.
#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.")
}
}
Simply use the init method in your widget to access a firebase instance.
This was the easiest solution for me as of today.
Taken from: https://github.com/firebase/firebase-ios-sdk/issues/6683
Additional Edit: Do you need to share authentication? No problem. Firebase has that covered here: https://firebase.google.com/docs/auth/ios/single-sign-on?authuser=1
You can add the appDelegate to your #main SwiftUI view
First create your appdelegate on your widget extension
import Firebase
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
FirebaseApp.configure()
return true
}
}
look at #main, inside your widget extension,
#main
struct TestWidget: Widget {
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
private let kind: String = "ExampleWidget"
public var body: some WidgetConfiguration {
...
}
}
#main is new swift 5.3 feature that allows value type entry point, so this is will be your main entry point for your widget extension
just add #UIApplciationDelegateAdaptor, inside your #main

Resources