I never had this problem before but it all of a sudden occurred when I added deleting the user profiles in firebase user auth on my Swift UI app. What happens is when I start up the app a blank screen appears and after around 30 seconds a runtime error pops up in the terminal saying:
8.9.1 - [GoogleUtilities/AppDelegateSwizzler][I-SWZ] App Delegate does not conform to UIApplicationDelegate protocol.
I am fairly new to this so I am not sure what kinda code to even start providing since the app is kinda big but I hope what I provide is somewhat helpful.
This is my main view:
import SwiftUI
import Firebase
#main
struct ExampleApp: App {
init() {
FirebaseApp.configure()
}
var body: some Scene {
WindowGroup {
HomeView()
}
}
}
This is the class which manages user data:
class UsersManagerModel: ObservableObject {
// observed classes
#ObservedObject var fireViewModel = FirebaseViewModel()
// shared info
#AppStorage("email") var email = ""
// variable used for user info
#Published var firstName = ""
#Published var lastName = ""
func deleateProfile() {
// remove user from storage
let db = Firestore.firestore()
let docRef = db.collection("Users").document("\(email)")
docRef.delete()
// remove user from firebase auth
if let user = Auth.auth().currentUser {
user.delete { error in
if let error = error {
print("error \(error)")
} else {
print("success")
self.fireViewModel.loginPresented.toggle()
}
}
}
}
func fetchUserData() {
let db = Firestore.firestore()
let docRef = db.collection("Users").document("\(email)")
docRef.getDocument { (document, error) in
guard error == nil else {
print("error", error ?? "")
return
}
if let document = document, document.exists {
let data = document.data()
if let data = data {
print("data", data)
self.firstName = data["FirstName"] as? String ?? ""
}
}
}
}
}
I really appreciate any help I can get. Thanks. Once again the runtime error is:
8.9.1 - [GoogleUtilities/AppDelegateSwizzler][I-SWZ] App Delegate does not conform to UIApplicationDelegate protocol.
Related
I need to display user.username on Text in ProfileView but I got error when I try to fill current user with User. I have to get User in currentUser var.
import Foundation
import FirebaseAuth
import Firebase
class AuthViewModel: ObservableObject
{
#Published var userSession: FirebaseAuth.User?
#Published var currentUser: User?
private var tempUserSession: FirebaseAuth.User?
private let service = UserService()
init()
{
self.userSession = Auth.auth().currentUser
}
func login(withEmail email: String, password: String)
{
Auth.auth().signIn(withEmail: email, password: password) { authResult, error in
if let e = error
{
print(e.localizedDescription)
}
else
{
guard let user = authResult?.user else {return}
self.userSession = user
guard let uid = self.userSession?.uid else { return }
self.service.fetchUser(withUid: uid)
print("Did User log IN")
}
}
}
func fetchUser()
{
guard let uid = self.userSession?.uid else { return }
service.fetchUser(withUid: uid) { user in <----- HERE I GOT AN ERROR Extra trailing closure passed in call
self.currentUser = user
}
}
}
import Foundation
import UIKit
import FirebaseAuth
import SwiftUI
class ProfileViewController: UIViewController
{
var authViewModel = AuthViewModel()
#IBOutlet weak var userNameLabel: UILabel!
override func viewDidLoad()
{
navigationItem.hidesBackButton = true
userNameLabel.text = authViewModel.currentUser?.username // HERE I WANT TO DISPLAY CURRENT USER - USERNAME
print(userNameLabel.text)
}
}
I tried fill currentuser with user but nothings worked. Still in profile view controller I got nill. (currentUser.username = nil)
The code calls fetchUser like it's an asynchronous function and it's not; it's a synchronous function that does not return a value, nor has an escaping completion handler. So that's the cause of the error.
Here's how I would do it. Start with a a simple user class
class MyUser {
var userName = ""
var uid = ""
}
and then a simplified fetchUser using async/await
func fetchUser() {
Task {
let uid = "uid_0"
let foundUser = await self.getUserAsync(withUid: "uid_0")
print(foundUser.userName)
}
}
and then the code to fetch the user from Firestore, instantiate a MyUser object and return it
func getUserAsync(withUid: String) async -> MyUser {
let usersCollection = self.db.collection("users") //self.db points to my firestore
let thisUserDoc = usersCollection.document(withUid)
let snapshot = try! await thisUserDoc.getDocument()
let user = MyUser()
user.userName = snapshot.get("userName") as? String ?? "No Name"
user.uid = withUid
return user
}
I have an app Im trying to build with sleep data for sleep apnea. I successfully setup CoreData with some help and its saving the first user input from the textfields and displaying it in the log from CoreData (changing the strings to Doubles was a challenge but success). However, whenever I put another 2nd input in, it saves and displays the first input again. It won't change. What am I doing wrong? A side note, it also is not creating a unique ID for each entry for some reason. Thank you guys....you'd been awesome so far helping as Im a newbie.
SleepModel.swift
struct SleepModel: Identifiable, Hashable {
var id = UUID().uuidString // ID variable
var hoursSlept: Double // Hours slept variable
var ahiReading: Double // AHI variable
}
Save function in AddEntryView.swift which is called when the button is pushed
private func saveSleepModel() {
guard let hoursSleptDouble = Double(hoursSleptString) else {
print("hours slept is invalid")
return
}
guard let ahiReadingDouble = Double(ahiReadingString) else {
print("ahi reading is invalid")
return
}
// Creates Sleep Model for user input & sends parameters
var sleepModel = SleepModel(hoursSlept: hoursSleptDouble, ahiReading: ahiReadingDouble)
coreDataViewModel.saveRecord(sleepModel: sleepModel) {
print("Success")
}
}
CoreDataViewModel
import SwiftUI
import CoreData
class CoreDataViewModel: ObservableObject {
let container: NSPersistentContainer
#Published var savedRecords: [SleepEntity] = []
init() {
container = NSPersistentContainer(name: "SleepContainer")
container.loadPersistentStores { description, error in
if let error = error {
print("Error loading contaienr - \(error.localizedDescription)")
} else {
print("Core data loaded successfully")
}
}
fetchRecords()
}
func saveRecord(sleepModel: SleepModel, onSuccess: #escaping() -> Void) {
let newRecord = SleepEntity(context: container.viewContext)
newRecord.hoursSlept = sleepModel.hoursSlept
newRecord.ahiReading = sleepModel.ahiReading
saveData(onSuccess: onSuccess)
}
func saveData(onSuccess: #escaping() -> Void) {
do {
try container.viewContext.save()
fetchRecords()
onSuccess()
} catch let error {
print("Error saving items: \(error)")
}
}
func fetchRecords() {
let request = NSFetchRequest<SleepEntity>(entityName: "SleepEntity")
request.sortDescriptors = [
NSSortDescriptor(keyPath: \SleepEntity.id, ascending: false)
]
do {
savedRecords = try container.viewContext.fetch(request)
} catch let error {
print("Error fetching requests - \(error.localizedDescription)")
}
}
}
I am trying to replace manual mapping when fetching a user by using Codable. In doing so, I am getting some errors. The errors I am getting are 'cannot find self in scope'.
Here is my fetchUser function:
import SwiftUI
import Firebase
import FirebaseFirestoreSwift
func fetchUser(documentId: String) {
let db = Firestore.firestore()
let docRef = db.collection("Users").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.user = try document.data(as: UserModel.self)
}
catch {
print(error)
}
}
}
}
}
Here is my UserModel:
import SwiftUI
import FirebaseFirestoreSwift
struct UserModel: Identifiable, Codable{
#DocumentID var id: String?
var username : String
var pic : String
var bio: String
var uid : String
var isVerified: Bool
var favouriteProducts: [FavourtieProducts]
}
What's going wrong here?
The following is how I did it before. This worked but when I added more variables to the UserModel I wanted to change how it is fetched to make the code better.
import SwiftUI
import Firebase
import FirebaseFirestoreSwift
let ref = Firestore.firestore()
func fetchUser(uid: String,completion: #escaping (UserModel) -> ()){
let db = Firestore.firestore()
#EnvironmentObject var sharedData: SharedDataModel
ref.collection("Users").document(uid).getDocument { (doc, err) in
guard let user = doc else{return}
let username = user.data()?["username"] as? String ?? "No Username"
let pic = user.data()?["imageurl"] as? String ?? "No image URL"
let bio = user.data()?["bio"] as? String ?? "No bio"
let uid = user.data()?["uid"] as? String ?? ""
let isVerified = user.data()?["isVerified"] as? Bool ?? false
DispatchQueue.main.async {
completion(UserModel(username: username, pic: pic, bio: bio, uid: uid, isVerified: isVerified))
}
}
}
How do I turn the first(new one) fetchUser function from a global function to a local function?
I am learning Swift to develop macOS applications and I ran into a problem. I am trying to get certain data from a JSON from the internet. I have managed to get such data and put it in simple text labels in the view but when I run Xcode and get the values, if the values from the JSON get updated, I can't see it reflected in my app. I know that I must perform a function to refresh the data but what I have always found is the function to refresh the data that is in a table, not a simple text label.
Regardless of this problem, if I wanted to add a table with 3 columns (each structure has 3 data, at least) with the values from the JSON. When I run the refresh of the table, I should include in the function the function that gets the data from the internet, right? I'm a bit lost with this too.
This is what I have:
ViewController.swift
import Cocoa
class ViewController: NSViewController, NSTextFieldDelegate {
let user_items = UserItems()
#IBOutlet var item_salida: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
let struc_item = user_items.Item_Struct()
let position = struc_item.firstIndex(where: { $0.name == "Leanne Graham" })!
print(struc_item[position].state!)
item_salida.stringValue = struc_item[position].state!
} }
Struct.swift
import Foundation
import SwiftyJSON
// MARK: - Dato
struct User: Codable {
var name: String?
var username: String?
var email: String?
}
typealias Datos = [User]
class UserItems {
func Item_Struct() -> Datos {
let urlString = "https://jsonplaceholder.typicode.com/users"
var items_available: [User] = []
if let url = NSURL(string: urlString){
if let data = try? NSData(contentsOf: url as URL, options: []){
let items = try! JSONDecoder().decode([User].self, from: data as Data)
for item in items {
items_available.append(item)
}
}
}
return items_available
}
}
Thanks, a lot!
First of all - as you are learning Swift - please stop using snake_case variable names and also the NS.. classes NSURL and NSData.
Never load data from a remote URL with synchronous Data(contentsOf. It blocks the thread.
You need URLSession and an asynchronous completion handler.
// MARK: - Dato
struct User: Codable {
let name: String
let username: String
let email: String
}
typealias Datos = [User]
class UserItems {
func loadData(completion: #escaping (Datos) -> Void) {
let url = URL(string: "https://jsonplaceholder.typicode.com/users")!
URLSession.shared.dataTask(with: url) { data, _, error in
if let error = error { print(error); return }
do {
let items = try JSONDecoder().decode([User].self, from: data!)
completion(items)
} catch {
print(error)
}
}.resume()
}
}
And use it in the controller
class ViewController: NSViewController, NSTextFieldDelegate {
#IBOutlet var itemSalida: NSTextField!
let userItems = UserItems()
override func viewDidLoad() {
super.viewDidLoad()
userItems.loadData { users in
if let position = users.firstIndex(where: { $0.name == "Leanne Graham" }) {
DispatchQueue.main.async {
print(users[position].username)
self.itemSalida.stringValue = users[position].username
}
}
}
}
}
And forget SwiftyJSON. It's not needed anymore in favor of Codable.
I've made a method to fetch my coredata Objects array using generics and privateQueueConcurrencyType. However, I'm doing something wrong somewhere and I'm experiencing crashes because of that.
The place where I get the crash as follow. what am I missing here ??
func fetchData<T: NSFetchRequestResult>(entity: String, model: T.Type, _ custom_predicate: NSPredicate?=nil) throws -> [T] {
let request = NSFetchRequest<T>(entityName: entity)
if custom_predicate != nil {
request.predicate = custom_predicate
}
request.returnsObjectsAsFaults = false
//Crash is the bellow line
return try privateMOC.fetch(request)//This line throws the crash
}
My privateMOC initialisation as follow.
class StorageManager: NSObject {
let privateMOC: NSManagedObjectContext!
private override init() {
privateMOC = CoreDataManager.sharedManager.updateContext
}
private static var SMInstance: StorageManager?
lazy var managedObjectContext: NSManagedObjectContext = {
return CoreDataManager.sharedManager.persistentContainer.viewContext
}()
}
My CoreData stack as follow.
class CoreDataManager {
static let sharedManager = CoreDataManager()
let persistentContainer: NSPersistentContainer!
let viewContext: NSManagedObjectContext!
let updateContext: NSManagedObjectContext!
private init() {
let container: NSPersistentContainer = {
let container = NSPersistentContainer(name: "Store")
container.loadPersistentStores(completionHandler: { (_, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
self.persistentContainer = container
self.viewContext = persistentContainer.viewContext
//This is where I use the privateQueueConcurrencyType formy privateMOC
let _updateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
_updateContext.parent = self.viewContext
self.updateContext = _updateContext
}
Stacktrace as follow.
When you are using background context (private queue), you should wrap it in perform or performAndWait. With that said the fetch method should be called like this:
context.performAndWait {
context.fetch(request)
}
Because the queue is private and internal to the NSManagedObjectContext instance, it can only be accessed through the perform(:) and the performAndWait(:) methods.
More about using core data in background read here
Edit 1:
performAndWait takes a closure as its parameter that has no return value, so you can't return result/value from it. You need to understand these concepts in Swift.
Let's take your problem for example, you have a function that you want to return array of some values after the fetch request is performed. What you did is not going to work because of what I said earlier, so we will "extract" the context outside of the function.
func fetch<T>(_ type: T.Type, in context: NSManagedObjectContext) -> [T] {
return context.fetch(request)
}
In that way we can wrap the function in whatever context we want either background or viewContext(main).
context.performAndWait {
let products = fetch(SubProduct.self, in: context)
// do some other stuff
}
I can't replicate your crash but I was getting an Error in the line you highlighted.
I didn't have info on you SubProduct so I used the generic Item that comes with Xcode Projects
There are a ton of comments that come with the code.
This is just a standard View to test functionality.
struct CoreDataBackgroundView: View {
#StateObject var vm: CoreDataBackgroundViewModel2 = CoreDataBackgroundViewModel2()
public init(){}
var body: some View {
VStack{
List(vm.items){ item in
Button(action: {
vm.fetchItem(timestamp: item.timestamp)
}, label: {
Text(item.timestamp!.description)
})
}
Text("fetched Item = \(vm.item?.timestamp?.description ?? "nil")")
Text(vm.items.count.description)
Button("fetch", action: {
vm.fetchItems()
})
Button("add", action: {
vm.addItem()
})
}
}
}
struct CoreDataBackgroundView_Previews: PreviewProvider {
static var previews: some View {
CoreDataBackgroundView()
}
}
This is how I incorporated your code
class CoreDataBackgroundViewModel2: ObservableObject{
private let manager = StorageManager.SMInstance
#Published var items: [Item] = []
#Published var item: Item? = nil
///Fetch everything
func fetchItems() {
do{
items = try manager.fetchData(entity: "Item", model: Item.self)
}catch{
print(error)
items = []
}
}
///This way you can just fetch the item(s) that you need
func fetchItem(timestamp: Date?) {
if timestamp != nil{
do{
item = try manager.fetchData(entity: "Item", model: Item.self, NSPredicate(format: "timestamp == %#", timestamp! as CVarArg)).first
}catch{
print(error)
item = nil
}
}else{
item = nil
}
}
func addItem() {
manager.addItem()
}
}
//Something had to change here there is no entry
class StorageManager: NSObject {
let manager = CoreDataManager.sharedManager
let privateMOC: NSManagedObjectContext
private override init() {
privateMOC = manager.container.newBackgroundContext()
}
//I made it a singleton
static var SMInstance: StorageManager = StorageManager()
lazy var managedObjectContext: NSManagedObjectContext = {
return manager.container.viewContext
}()
func fetchData<T: NSFetchRequestResult>(entity: String, model: T.Type, _ custom_predicate: NSPredicate?=nil) throws -> [T] {
let request = NSFetchRequest<T>(entityName: entity)
if custom_predicate != nil {
request.predicate = custom_predicate
}
request.returnsObjectsAsFaults = false
//Didn't get a crash but an Error
//Value of optional type 'NSManagedObjectContext?' must be unwrapped to refer to member 'fetch' of wrapped base type 'NSManagedObjectContext'
// It is bad practice to use ! try to minimize those as much as possible
return try privateMOC.fetch(request)
}
func addItem()
{
privateMOC.perform {
let newItem = Item(context: self.privateMOC)
newItem.timestamp = Date()
newItem.text = "sample"
do{
try self.privateMOC.save()
}catch{
print(error)
}
}
}
}
//I changed your manager because there were a bunch of nil objects that are unnecessary also, I added the ability to use an inMemory conxtext for when you are using previews/canvas
//This is basically the standard setup with comes with all New Projects in Xcode
//Some of the errors probably had to do with the let variables that were after loadPersistentStores. They were called before the store was done loading so they stayed nil. The speed of the load might have given you mixed results.
class CoreDataManager {
static let sharedManager = CoreDataManager.previewAware()
private static func previewAware() -> CoreDataManager{
if ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1"{
return CoreDataManager.preview
}else{
return CoreDataManager.shared
}
}
private static let shared = CoreDataManager()
private static var preview: CoreDataManager = {
let result = CoreDataManager(inMemory: true)
let viewContext = result.container.viewContext
for n in 0..<2 {
let newItem = Item(context: viewContext)
newItem.timestamp = Date()
}
do {
try viewContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
return result
}()
let container: NSPersistentContainer
init(inMemory: Bool = false) {
//This is usually the AppName
container = NSPersistentContainer(name: "AppName")
//If you are in Preview the setup work take place only on a device or simulator
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = false
container.viewContext.mergePolicy = NSMergePolicy.mergeByPropertyStoreTrump
}
}