【问题标题】:Update data stored in Firebase using MVVM nested models使用 MVVM 嵌套模型更新存储在 Firebase 中的数据
【发布时间】:2021-08-01 02:18:22
【问题描述】:

我构建了一个 MVVM 架构来支持我的应用程序,该应用程序应该控制前端和我的 firebase 数据库之间的管道。最初,我完全在前端编写代码,成功实现了整个工作,但是当我将它们封装成一个函数时,出现了很多错误。

例如,当当前显示的工作表被关闭时,将显示下一个工作表。有时我需要等待很长时间才能解冻应用程序。更糟糕的是,当我单击按钮时,应用程序崩溃了。

我听说如果使用 SwiftUI,嵌套模型还不能工作 (reference)。但是,如果我的课程未经测试,我就无法提出更好的解决方案。

// This is Model
import Foundation
import SwiftUI

struct userModel {
    var uid = UUID()
    var name = ""
    var bio = ""
    var interest = ""
    var level = 1
    var xp = 0
    var email = ""
    var image: Data = Data(count: 0)
    
    init() {
        
    }
    
    init(_ name:String, _ xp: Int) {
        self.name = name
        self.xp = xp
        self.level = self.xp2Level(xp: xp)
    }
    
    func xp2Level(xp:Int) -> Int {
        if xp < 9500 {
            return xp / 500 + 1
        }
        else if xp < 29500 {
            return (xp - 9500) / 1000 + 1
        }
        else {
            return (xp - 29500) / 2000 + 1
        }
    }
}
// This is ViewModel
import Foundation
import SwiftUI
import Firebase

class userViewModel: ObservableObject {
    @Published var user: userModel = userModel()
    
    @Published var isLoading = false
    @AppStorage("status") var status = false
    private var ref = Firestore.firestore()
    private let store = Storage.storage().reference()
    var picker = false
    
    func updateXP(completion: @escaping () -> Int) -> Int {
        guard let uid = Auth.auth().currentUser?.uid else {
            return 0
        }
        // catch the information of the current user
        let db = ref.collection("Users")
        db.addSnapshotListener { [self] (querySnapshot, error) in
            guard (querySnapshot?.documents) != nil else {
                print("Document is empty")
                return
            }
            let docRef = db.document(uid)
            
            docRef.getDocument { (snapshot, error) in
                if let doc = snapshot,
                   let xp = doc.get("xp") as? Int {
                    self.user.xp = xp
                }
            }
        }
        return completion()
    }
    
    func updateLevel(completion: @escaping () -> Int) -> Int {
        guard let uid = Auth.auth().currentUser?.uid else {
            return 1
        }
        // catch the information of the current user
        let db = ref.collection("Users")
        db.addSnapshotListener { [self] (querySnapshot, error) in
            guard (querySnapshot?.documents) != nil else {
                print("Document is empty")
                return
            }
            let docRef = db.document(uid)
            
            docRef.getDocument { (snapshot, error) in
                if let doc = snapshot,
                   let level = doc.get("level") as? Int {
                    self.user.level = level
                }
            }
        }
        return completion()
    }
    
    func updateName (completion: @escaping () -> String) -> String {
        guard let uid = Auth.auth().currentUser?.uid else {
            return ""
        }
        // catch the information of the current user
        let db = ref.collection("Users")
        db.addSnapshotListener { [self] (querySnapshot, error) in
            guard (querySnapshot?.documents) != nil else {
                print("Document is empty")
                return
            }
            let docRef = db.document(uid)
            
            docRef.getDocument { (snapshot, error) in
                if let doc = snapshot,
                   let name = doc.get("username") as? String {
                    self.user.name = name
                }
            }
        }
        return completion()
    }
    
    func updateBio (completion: @escaping () -> String) -> String {
        guard let uid = Auth.auth().currentUser?.uid else {
            return ""
        }
        // catch the information of the current user
        let db = ref.collection("Users")
        db.addSnapshotListener { [self] (querySnapshot, error) in
            guard (querySnapshot?.documents) != nil else {
                print("Document is empty")
                return
            }
            let docRef = db.document(uid)
            
            docRef.getDocument { (snapshot, error) in
                if let doc = snapshot,
                   let bio = doc.get("bio") as? String {
                    self.user.bio = bio
                }
            }
        }
        return completion()
    }
    
    func updateInterest (completion: @escaping () -> String) -> String {
        guard let uid = Auth.auth().currentUser?.uid else {
            return ""
        }
        // catch the information of the current user
        let db = ref.collection("Users")
        db.addSnapshotListener { [self] (querySnapshot, error) in
            guard (querySnapshot?.documents) != nil else {
                print("Document is empty")
                return
            }
            let docRef = db.document(uid)
            
            docRef.getDocument { (snapshot, error) in
                if let doc = snapshot,
                   let interest = doc.get("interest") as? String {
                    self.user.interest = interest
                }
            }
        }
        return completion()
    }
    
    func updatePhoto (completion: @escaping () -> Data) -> Data {
        guard let uid = Auth.auth().currentUser?.uid else {
            return Data(count: 0)
        }
        
        // catch the information of the current user
        let db = ref.collection("Users")
        db.addSnapshotListener { [self] (querySnapshot, error) in
            guard (querySnapshot?.documents) != nil else {
                print("Document is empty")
                return
            }
            let docRef = db.document(uid)
            
            docRef.getDocument { (snapshot, error) in
                if snapshot != nil {
                    let imageRef = store.child("profile_Photos").child(uid)
                    imageRef.getData(maxSize: 1000 * 64 * 64, completion: { (data, error) in
                        if let error = error {
                            print("Encountered error: \(error) when getting image")
                            self.user.image = Data(count: 0)
                        } else if let data = data,
                                  !data.isEmpty{
//                            self.currentUser.image = Image(uiImage: UIImage(data: data)!).resizable()
                            self.user.image = data
                        } else {
//                            self.currentUser.image = Image(systemName: "person").resizable()
                            self.user.image = Data(count: 0)
                        }
                      })
                } else if let error = error {
                    print(error)
                }
            }
        }
        return completion()
    }
    
    public func getXP() -> Int{
        updateXP {
            return (self.user.xp) as Int
        }
    }
    
    public func getLevel() -> Int {
        updateLevel(completion: {
            return (self.user.level) as Int
        })
    }
    
    public func getName() -> String {
        updateName(completion: {
            return (self.user.name) as String
        })
    }
    
    public func getBio() -> String {
        updateBio(completion: {
            return (self.user.bio) as String
        })
    }
    
    public func getInterest() -> String {
        updateInterest(completion: {
            return (self.user.interest) as String
        })
    }
    
    public func getPhoto() -> Data {
        updatePhoto(completion: {
            return (self.user.image) as Data
        })
    }
    
    func updatePersonalInfo() {
        //sending user data to Firebase
        let uid = Auth.auth().currentUser?.uid
        
        isLoading = true
        self.uploadImage(imageData: self.getPhoto(), path: "profile_Photos") { (url) in
            self.ref.collection("Users").document(uid ?? "").setData([
                            "uid": uid ?? "",
                            "imageurl": url,
                            "username": self.user.name,
                            "bio": self.user.bio,
                            "interest" : self.user.interest
                        ], merge: true) { (err) in
                         
                            if err != nil{
                                self.isLoading = false
                                return
                            }
                            self.isLoading = false
                            // success means settings status as true...
                            self.status = true
                        }
            
        }
    }
    
    func increaseXPnLV() {
        //sending user data to Firebase
        let uid = Auth.auth().currentUser!.uid
        let docRef = ref.collection("Users").document(uid)
        docRef.getDocument { (document, error) in
            if let document = document, document.exists {
                docRef.updateData(["xp": FieldValue.increment(Int64(50))])
                // update level
                let xp = document.data()!["xp"] as! Int
                docRef.updateData(["level": self.user.xp2Level(xp: xp)])
            } else {
                print("Document does not exist")
            }
        }
    }
    
    func uploadImage(imageData: Data, path: String, completion: @escaping (String) -> ()){
        
        let storage = Storage.storage().reference()
        let uid = Auth.auth().currentUser?.uid
        
        storage.child(path).child(uid ?? "").putData(imageData, metadata: nil) { (_, err) in
            print("imageData: \(imageData)")
            if err != nil{
                completion("")
                return
                
            }
            // Downloading Url And Sending Back...
            storage.child(path).child(uid ?? "").downloadURL { (url, err) in
                if err != nil{
                    completion("")
                    return
                    
                }
                completion("\(url!)")
            }
        }
    }
}

// This is View

import SwiftUI
import CoreData
import Firebase
import FirebaseFirestore

struct Goals: View {
    let current_user_id = Auth.auth().currentUser?.uid
    @State private var showingAlert = false
    var ref = Firestore.firestore()
    @StateObject var currentUser: userViewModel
    @StateObject var homeData = HomeViewModel()
    @State var txt = ""
    @State var edge = UIApplication.shared.windows.first?.safeAreaInsets
    @FetchRequest(entity: Goal.entity(), sortDescriptors: [NSSortDescriptor(key: "date",
                                                                            ascending: true)], animation: .spring()) var results : FetchedResults<Goal>
    
//    let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    @State private var greeting : String = "Hello"
    @Environment(\.managedObjectContext) var context
    var body: some View {
    ForEach(results){goal in
    Button(action: {
                                            context.delete(goal)
                                            try! context.save()
                                            if current_user_id != nil {
                                                currentUser.updateXPnLV()
                                                self.showingAlert = true
                                            }
                                        }, label: Text("Something")
    )
                                            .alert(isPresented: $showingAlert) {
                                        () -> Alert in
                                        Alert(title: Text("Congratulations!"), message: Text("You completed a goal today, XP+50!"), dismissButton: .default(Text("OK")))
                                            }
    }
}
}

编辑

我看到的另一个错误是AttributeGraph precondition failure: attribute failed to set an initial value: 805912, ForEachChild&lt;Array&lt;userInfoModel&gt;, ObjectIdentifier, HStack&lt;VStack&lt;HStack&lt;TupleView&lt;(Text, Divider, Text)&gt;&gt;&gt;&gt;&gt;.

【问题讨论】:

  • 如果userInfoModel 没有任何@Published 属性,为什么它是@ObservableObject?似乎它应该是一个 struct 然后它也不会是一个嵌套的 observable。
  • 我注意到并修改了它。但是,延迟(运行时错误)仍然存在。貌似不是模块结构的问题。

标签: swift firebase mvvm swiftui nested


【解决方案1】:
  1. AppStorage 用于在 View 中使用,而除了 @Published 内的 @Published 之外,它似乎可以在所有 SwiftUI 包装器中工作.

https://developer.apple.com/documentation/swiftui/appstorage

  1. 作为标准做法,所有classstruct 都应大写,因此更改class CurrentUserViewModelclass UserInfoModel

另外,将@StateObject var currentUser: currentUserViewModel 更改为@StateObject var currentUser: CurrentUserViewModel = CurrentUserViewModel()

初始化是缺少的最重要的部分。

  1. ForEach 中的所有内容都只是浮动的,它不在变量、函数或body 内。你的body在哪里?

这可能就是错误所说的。将所有代码包装在一个正文中

var body: some View {
    //All the code for the ForEach
}
  1. 您的Button 似乎缺少titlelabel

  2. 删除这一行

    () -&gt; Alert in

我相信还有其他一些小事。我建议你在这个View 中从头开始,然后你开始逐行输入代码。

这是一个起点。 Firebase 部分需要相当多的工作,但您应该能够通过关注我注释掉的代码并删除代码以模仿 Firebase 的响应来开始。

所有这些都在FirebaseManagerclass

一旦它起作用了,其余的都会起作用。

代码按原样运行,因此您可以通过虚假响应看到它的实际效果

///Keep all the Firebase Code HERE
class FirebaseManager{
    //private var ref = Firestore.firestore()
    //private let store = Storage.storage().reference()
    func retrieveFromDB(collectionName: String, variableName: String, completion:  @escaping (Result<Any, Error>) -> Void) {
        print(#function)
        //This is sample code, likely has errors because I dont have Firebase setup but you can see the logic so you can touch up
        
        //        guard let uid = Auth.auth().currentUser?.uid else {
        //            completion(.failure(FirebaseError.notLoggedIn))
        //            return
        //        }
        // catch the information of the current user
        //        let db = ref.collection(collectionName)
        //        db.addSnapshotListener { [self] (querySnapshot, error) in
        //
        //            if let error = error{
        //                completion(.failure(error))
        //                return
        //            }
        //            guard (querySnapshot?.documents) != nil else {
        //                print("Document is empty")
        //                completion(.failure(FirebaseError.emptyDocument))
        //                return
        //            }
        //            let docRef = db.(document(uid)
        //
        //            docRef.getDocument { (snapshot, error) in
        //                if let error = error{
        //                    completion(.failure(error))
        //                    return
        //                }
        //
        //            completion(.success(snapshot.get(variableName)))
        //            }
        //        }
        //For sample purposes I will mimic response remove this in your actual code
        DispatchQueue.main.async {
            if variableName == "xp" || variableName == "level"{
                completion(.success(Int.random(in: 0...200)))
            }else{
                let strings = ["apple", "orange", "banana", "kiwi", "startfruit"]
                completion(.success(strings.randomElement()!))
            }
        }
    }
    ///For Int variables
    func retrieveFromUsers(variableName: String, completion: @escaping (Result<Int, Error>) -> Void) {
        print(#function)
        retrieveFromDB(collectionName: "Users", variableName: variableName, completion: {result in
            switch result {
            case .success(let value):
                let xp = value as? Int
                if xp != nil{
                    completion(.success(xp!))
                }else{
                    completion(.failure(FirebaseError.wrongType))
                }
                return
            case .failure(let error):
                print(error)
                completion(.failure(error))
            }
        })
    }
    ///For String variables
    func retrieveUserProperty(variableName: String, completion: @escaping (Result<String, Error>) -> Void) {
        print(#function)
        retrieveFromDB(collectionName: "Users", variableName: variableName, completion: {result in
            switch result {
            case .success(let value):
                let username = value as? String
                if username != nil{
                    completion(.success(username!))
                }else{
                    completion(.failure(FirebaseError.wrongType))
                }
                return
            case .failure(let error):
                print(error)
                completion(.failure(error))
            }
        })
    }
    func retrieveXP(completion: @escaping (Result<Int, Error>) -> Void) {
        print(#function)
        retrieveFromUsers(variableName: "xp", completion: completion)
    }
    
    func retrieveLevel(completion: @escaping (Result<Int, Error>) -> Void) {
        print(#function)
        retrieveFromUsers(variableName: "level", completion: completion)
    }
    
    func retrieveName (completion: @escaping (Result<String, Error>) -> Void) {
        print(#function)
        retrieveUserProperty(variableName: "username", completion: completion)
    }
    
    func retrieveBio (completion: @escaping (Result<String, Error>) -> Void) {
        print(#function)
        retrieveUserProperty(variableName: "bio", completion: completion)
    }
    
    func retrieveInterest (completion: @escaping (Result<String, Error>) -> Void) {
        print(#function)
        retrieveUserProperty(variableName: "interest", completion: completion)
    }
    
    //Database code to retrieve Image needs to be added
    
    func updateDB(collectionName: String, variableName: String, incrementBy: Int, completion:  @escaping (Result<Int, Error>) -> Void) {
        print(#function)
        //sending user data to Firebase
        //        let uid = Auth.auth().currentUser!.uid
        //        let docRef = ref.collection(collectionName).document(uid)
        //        docRef.getDocument { (document, error) in
        //            if let document = document, document.exists {
        //                docRef.updateData([variableName: FieldValue.increment(incrementBy)])
        //let newValue = document.data()![variableName] as! Int
        //                completion(.success(newValue))
        //            } else {
        //                completion(.failure(FirebaseError.documentDoesntExist))
        //            }
        //        }
        //For sample purposes I will mimic response remove this in your actual code
        DispatchQueue.main.async {
            completion(.success(Int.random(in: 0...200) + incrementBy))
            
        }
    }
    
    func updateDB(collectionName: String, variableName: String, value: String, completion:  @escaping (Result<String, Error>) -> Void) {
        print(#function)
        //sending user data to Firebase
        //        let uid = Auth.auth().currentUser!.uid
        //        let docRef = ref.collection(collectionName).document(uid)
        //        docRef.getDocument { (document, error) in
        //            if let document = document, document.exists {
        //                docRef.updateData([variableName: value])
        //let newValue = document.data()![variableName] as! Int
        //                completion(.success(newValue))
        //            } else {
        //                completion(.failure(FirebaseError.documentDoesntExist))
        //            }
        //        }
        //For sample purposes I will mimic response remove this in your actual code
        DispatchQueue.main.async {
            
            let strings = ["apple", "orange", "banana", "kiwi", "startfruit"]
            completion(.success(strings.randomElement()!))
            
        }
    }
    func updateDB(collectionName: String, variableName: String, value: Int, completion:  @escaping (Result<Int, Error>) -> Void) {
        print(#function)
        //sending user data to Firebase
        //        let uid = Auth.auth().currentUser!.uid
        //        let docRef = ref.collection(collectionName).document(uid)
        //        docRef.getDocument { (document, error) in
        //            if let document = document, document.exists {
        //                docRef.updateData([variableName: value])
        //let newValue = document.data()![variableName] as! Int
        //                completion(.success(newValue))
        //            } else {
        //                completion(.failure(FirebaseError.documentDoesntExist))
        //            }
        //        }
        //For sample purposes I will mimic response
        DispatchQueue.main.async {
            completion(.success(Int.random(in: 0...200)))
            
        }
    }
    func updateUsers(variableName: String, value: String, completion:  @escaping (Result<String, Error>) -> Void) {
        print(#function)
        updateDB(collectionName: "Users", variableName: variableName, value: value, completion: completion)
    }
    func updateUsers(variableName: String, value: Int, completion:  @escaping (Result<Int, Error>) -> Void) {
        print(#function)
        updateDB(collectionName: "Users", variableName: variableName, value: value, completion: completion)
    }
    func updateUsers(variableName: String, incrementBy: Int, completion:  @escaping (Result<Int, Error>) -> Void) {
        print(#function)
        updateDB(collectionName: "Users", variableName: variableName, incrementBy: incrementBy, completion: completion)
    }
    
    //Code to update Image will need to be added
}

剩下的就到这里了

import SwiftUI
import CoreData
//Capitalized no other changes here
struct UserModel {
    var uid = UUID()
    var name = ""
    var bio = ""
    var interest = ""
    var level = 1
    var xp = 0
    var email = ""
    var image: Data = Data(count: 0)
    
    init() {
        print(#function)
    }
    
    init(_ name:String, _ xp: Int) {
        print(#function)
        self.name = name
        self.xp = xp
        self.level = self.xp2Level(xp: xp)
    }
    
    func xp2Level(xp:Int) -> Int {
        print(#function)
        if xp < 9500 {
            return xp / 500 + 1
        }
        else if xp < 29500 {
            return (xp - 9500) / 1000 + 1
        }
        else {
            return (xp - 29500) / 2000 + 1
        }
    }
}
//This is to standardize what comes from your Firebase Code. Try to condense code that is duplicated
enum FirebaseError: Error {
    case notLoggedIn
    case emptyDocument
    case wrongType
    case documentDoesntExist
}

Capitalize
class UserViewModel: ObservableObject {
    let alertVM = AlertViewModel.shared
    @Published var user: UserModel = UserModel()
    @Published var isLoading = false
    //AppStorage wont work here
    var status: Bool{
        get{
            UserDefaults.standard.bool(forKey: "status")
        }
        set{
            UserDefaults.standard.set(newValue, forKey: "status")
        }
        
    }
    //Separate all Firebase Code
    let firebaseManager = FirebaseManager()
    
    init() {
        populateAllVariables()
    }
    
    func increaseXPnLV() {
        print(#function)
        //sending xp to Firebase
        firebaseManager.updateUsers(variableName: "xp", incrementBy: 50, completion: {result in
            switch result {
            case .success(let newXp):
                self.user.xp = newXp
                //sending level to Firebase
                self.firebaseManager.updateUsers(variableName: "level", value: self.user.xp2Level(xp: newXp), completion: {result in
                    switch result {
                    case .success(let newLevel):
                        print("newLevel = \(newLevel)")
                        self.user.level = newLevel
                        self.alertVM.scheduleAlert(title: "Congratulations!", message: "You completed a goal today, XP+50!")
                        return
                    case .failure(let error as NSError):
                        //Show alert here
                        self.alertVM.scheduleAlert(error: error)
                        print(error)
                    }
                })
                return
            case .failure(let error):
                //Show alert here
                self.alertVM.scheduleAlert(error: error)
                print(error)
                
            }
        })
    }
    func populateAllVariables() {
        print(#function)
        getXP()
        getLevel()
        getName()
        getBio()
        getInterest()
    }
    public func getXP() {
        print(#function)
        firebaseManager.retrieveXP(completion: {result in
            switch result {
            case .success(let xp):
                self.user.xp = xp
            case .failure(let error):
                //Show alert here
                self.alertVM.scheduleAlert(error: error)
                print(error)
            }
        })
    }
    
    public func getLevel() {
        print(#function)
        firebaseManager.retrieveLevel(completion: {result in
            switch result {
            case .success(let level):
                self.user.level = level
            case .failure(let error):
                //Show alert here
                self.alertVM.scheduleAlert(error: error)
                print(error)
            }
        })
    }
    
    public func getName() {
        print(#function)
        firebaseManager.retrieveName(completion: {result in
            switch result {
            case .success(let name):
                self.user.name = name
            case .failure(let error):
                //Show alert here
                self.alertVM.scheduleAlert(error: error)
                print(error)
            }
        })
    }
    
    public func getBio() {
        print(#function)
        firebaseManager.retrieveBio(completion: {result in
            switch result {
            case .success(let bio):
                self.user.bio = bio
            case .failure(let error):
                //Show alert here
                self.alertVM.scheduleAlert(error: error)
                print(error)
            }
        })
    }
    
    public func getInterest() {
        print(#function)
        firebaseManager.retrieveInterest(completion: {result in
            switch result {
            case .success(let interest):
                self.user.interest = interest
            case .failure(let error):
                //Show alert here
                self.alertVM.scheduleAlert(error: error)
                print(error)
            }
        })
    }
    ///This will need work
    //    public func getPhoto() -> Data {
    //        updatePhoto(completion: {
    //            return (self.user.image) as Data
    //        })
    //    }
    //It is best to separate work from the View
    func deleteGoal(moc: NSManagedObjectContext, goal: Goal) -> Bool{
        print(#function)
        var result = false
        moc.performAndWait  {
            moc.delete(goal)
            do{
                try moc.save()
                result = true
            }catch{
                self.alertVM.scheduleAlert(error: error)
                result = false
            }
        }
        return result
    }
}
//This is to centralize alerts. When you are using the web there will be errors and therefore alerts that the user should be aware of 
struct CustomAlert: Identifiable {
    let id: UUID = UUID()
    let title: String
    let message: String
    let dismissButtonTitle: String
}
//Again to centralize the alerts. I like putting this on the uppermost View so you can send alerts from anywhere
class AlertViewModel: ObservableObject {
    //Singleton keeps everything connected
    static let shared: AlertViewModel = AlertViewModel()
    @Published var currentAlert: CustomAlert?
    private init() {
        //Required because you need to share the instance
    }
    //Use this for a custom message
    func scheduleAlert(title: String = "ERROR", message: String, dismissButtonTitle: String = "OK") {
        currentAlert = CustomAlert(title: title, message: message, dismissButtonTitle: dismissButtonTitle)
    }
    //Use this if you have a fully formed Error
    func scheduleAlert(error: Error) {
        let error = error as NSError
        currentAlert = CustomAlert(title: "ERROR", message:  (error.localizedFailureReason ?? "") + error.localizedDescription + (error.localizedRecoverySuggestion ?? ""), dismissButtonTitle: "OK")
    }
    
}
struct Goals: View {
    //The View should never be aware of where your data is stored. all Firebase code should be removed
    @StateObject var currentUser: UserViewModel = UserViewModel()
    //This observes the alerts that are sent from anywhere
    @StateObject var alertVM: AlertViewModel = AlertViewModel.shared
    //No code provided
    //@StateObject var homeData = HomeViewModel()
    @FetchRequest(entity: Goal.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Goal.date, ascending: true)], animation: .spring()) var results : FetchedResults<Goal>
    
    @Environment(\.managedObjectContext) var context
    var body: some View {
        VStack{
            Text("Name = " + currentUser.user.name.description)
            Text("Level = " + currentUser.user.level.description)
            Text("XP = " + currentUser.user.xp.description)
            ForEach(results){goal in
                Button(action: {
                    //There should be some kind of check here to make sure the goal got deleted
                    if currentUser.deleteGoal(moc: context, goal: goal){
                        //No code provided
                        //if current_user_id != nil {
                        currentUser.increaseXPnLV()
                    }
                    //}
                }, label: {
                    //Missing Brackets
                    Text("Goal \(goal.name?.description ?? "") Completed")
                    
                })
                //This gets presented from everywhere
                .alert(item: $alertVM.currentAlert, content: {current in
                    Alert(title: Text(current.title), message: Text(current.message), dismissButton: .cancel(Text(current.dismissButtonTitle)))
                })
                
            }
        }
    }
}

【讨论】:

  • 感谢您的退房!是的,我犯了一个错误,没有将主体支架包含在我的 sn-p 中。并且后来我对模型架构做了一些改动,如果不打扰你可以去看看。提到@AppStorage 是什么意思?哪些变量应该是@Appstorage?
  • 不要在 ViewModel 中使用 AppStorage,它只能在 View 中使用。我传统上不使用 Firebase,因此我对这部分没有太大帮助,但乍一看可能存在不一致之处,需要全面修订
  • 看上面我发布了一些示例代码,但 Firebase 部分需要大量工作
  • Organization.. 现在您可以在其他 Views 中使用该类,而无需重复代码。您复制和粘贴的任何内容都应简化/压缩以避免复制和粘贴。现在想想你的 ViewModel,如果你决定切换到另一个数据库服务,比如 Azure,该怎么办?你所要做的就是复制这些方法,你的 ViewModel 就可以工作了。理想情况下,您应该能够从一个数据库快速切换到另一个数据库,并且只需更改一行代码。这需要更多设置,但当您的 View/ViewModel 不关心持久性时,可以轻松完成。做一些解耦方面的研究
  • 你的“视图”不应该做“工作”。它严格用于用户交互。随着您的应用程序的发展,您将看到这条规则的好处。
猜你喜欢
  • 2015-11-15
  • 1970-01-01
  • 2019-02-18
  • 1970-01-01
  • 2017-10-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多