【问题标题】:Child Updated crashing table (indexPath is out of range error that crashes my app Error)子更新崩溃表(indexPath 超出范围错误导致我的应用程序崩溃错误)
【发布时间】:2021-03-07 20:24:09
【问题描述】:

我很难在我的应用程序中更新一个控制表格的数组。我希望能够更改 Firebase 数据库中的子节点并在我的用户端对其进行更新,而无需编写代码和发送更新。我需要能够更改他们的标签并让它更新表而不会使索引超出范围。

这是我的类结构,供用户参考:

import UIKit

class User: NSObject {
    var Name: String?
    var Email: String?
    var UID: String?
    var Tag: String?

}

在我的控制器中,我有以下代码来使用来自 Firebase 的信息初始化表:

func CreateDataArray() {
    
    Database.database().reference().child("Users").observe(.childAdded, with: { (snapshot) in
        
        if let dictionary = snapshot.value as? [String: AnyObject] {
            
            let user = User()
            //self.setValuesForKeys(dictionary)
            user.Name = dictionary["Name"] as? String
            user.Email = dictionary["Email"] as? String
            user.UID = dictionary["UID"] as? String
            user.Tag = dictionary["Tag"] as? String
            
            if user.Tag == "Macro" {
                            self.macroUsersArray.append(user)
            } else if user.Tag == "Micro" {
                            self.microUsersArray.append(user)
                        }
            
            self.users.append(user)
            self.userName.append(user.Name!)
            self.filteredNames.append(user.Name!)
            
            print(dictionary)
            print(self.macroUsersArray)
            
            DispatchQueue.main.async {
            self.tableView.reloadData()
                }
            }
        }, withCancel: nil)
    
    }

然后我有一个函数可以监听 Firebase 的任何更改并更新数组:

func updateDataArray() {
    
    Database.database().reference().child("Users").observe(.childChanged, with: { (snapshot) in
        
        if let dictionary = snapshot.value as? [String: AnyObject] {
            
            let user = User()
            //self.setValuesForKeys(dictionary)
            user.Name = dictionary["Name"] as? String
            user.Email = dictionary["Email"] as? String
            user.UID = dictionary["UID"] as? String
            user.Tag = dictionary["Tag"] as? String
            
            if user.Tag == "Macro" {
                self.microUsersArray.remove(at: 2)
            } else if user.Tag == "Micro" {
                self.macroUsersArray.remove(at: 2)
                        }
            
            self.users.append(user)
            self.userName.append(user.Name!)
            self.filteredNames.append(user.Name!)
            
            print(dictionary)
            print(self.macroUsersArray)
            
            DispatchQueue.main.async {
            self.tableView.reloadData()
                }
            }
        }, withCancel: nil)
    
    }

我想让最初拥有标签“宏”的用户切换到“微”,从宏列表中删除并显示在微列表中。我无法让数组追加并继续获取 indexPath is out of range 错误,导致我的应用程序崩溃。我真的需要帮助,过去几周我一直在为此苦苦挣扎。

编辑:我决定使用observe.value,以便在我进行更改时读取它。这是我创建用户信息的代码:

func CreateDataArray() {
    
    Database.database().reference().child("Users").observe(.value, with: { (snapshot) in
        
        if let dictionary = snapshot.value as? [String: AnyObject] {
            
            let user = User()
            //self.setValuesForKeys(dictionary)
            user.Name = dictionary["Name"] as? String
            user.Email = dictionary["Email"] as? String
            user.Tag = dictionary["Tag"] as? String
            
            if user.Tag == "Macro" {
                            self.macroUsersArray.append(user)
            } else if user.Tag == "Micro" {
                            self.microUsersArray.append(user)
                        }
            
            self.users.append(user)
            self.userName.append(user.Name!)
            self.filteredNames.append(user.Name!)
            
            print(dictionary)
            print(self.macroUsersArray)
            
            DispatchQueue.main.async {
            self.tableView.reloadData()
                }
            }
        }, withCancel: nil)
    
    }

但是,我可以将所有数据提取到快照中,但是当我尝试追加时它返回 nil。我在任何带有“append(User.Name!)

的代码行上都收到 nil 错误

我很想知道如何阻止这个 nil 并让我的观察者继续监听我的数据库中的变化。

编辑 2:

这是我的变量:

var allUsersArray = [User]()
var macroUsersArray = [User]()
var microUsersArray = [User]()

这是我的 ViewDidLoad:

 override func viewDidLoad() {
        super.viewDidLoad()
        
        updateDataArray()
        
    }

这是我的 updateDataArray 代码:

func updateDataArray() {
    
    Database.database().reference().child("Users").observe(.childAdded, with: { (snapshot) in
        
        if let dictionary = snapshot.value as? [String: AnyObject] {
            
            let user = User()
            //self.setValuesForKeys(dictionary)
            user.Name = dictionary["Name"] as? String
            user.Email = dictionary["Email"] as? String
            user.UID = dictionary["UID"] as? String
            user.Tag = dictionary["Tag"] as? String
            
            self.allUsersArray.append(user)
            self.users.append(user)
            self.userName.append(user.Name!)
            self.filteredNames.append(user.Name!)

            self.macroUsersArray = self.allUsersArray.filter { $0.Tag == "Macro" }
            self.microUsersArray = self.allUsersArray.filter { $0.Tag == "Micro" }
            self.observeChangeInUserProperty()
            
            DispatchQueue.main.async {
            self.tableView.reloadData()
                }
            }
        }, withCancel: nil)
    
    }

这里是 observeChangeInUserProperty() 函数:

func observeChangeInUserProperty() {
    let ref = Database.database().reference().child("Users").child("Talent")
    ref.observe(.childChanged, with: { snapshot in
        let key = snapshot.key
        
        let tag = snapshot.childSnapshot(forPath: "Tag").value as! String // ! = never optional
        //get the user from the allUsersArray by its key
        if let user = self.allUsersArray.first(where: { $0.Tag == key }) {
            if user.Tag != tag { //if the tag changed, handle it
                user.Tag = tag //update the allUsersArray
                if tag == "Macro" { //if the new tag is Macro remove the user from the Micro array
                    if let userIndex = self.microUsersArray.firstIndex(where: { $0.Tag == key }) {
                        self.microUsersArray.remove(at: userIndex)
                        self.macroUsersArray.append(user) //add user to macro array
                    }
                } else { //new type is micro so remove from macro array
                    if let userIndex = self.macroUsersArray.firstIndex(where: { $0.Tag == key }) {
                        self.macroUsersArray.remove(at: userIndex)
                        self.microUsersArray.append(user)
                    }
                }
                //reload the tableviews to reflect the changes
                self.microTableView.reloadData()
                self.tableView.reloadData()
                
            }
        }
    })
}

我得到了 Macro 和 Micro 的空数组。我不知道我做错了什么。

编辑 3:

这是我从 Firebase 初始化的新用户结构:

import UIKit
import Firebase

class User: NSObject {
    var Name: String?
    var Email: String?
    var UID: String?
    var Tag: String?

    init?(from snapshot: DataSnapshot) {

        let dictionary = snapshot.value as? [String: Any]
    
            self.Name = dictionary!["Name"] as? String
            self.Email = dictionary!["Email"] as? String
            self.UID = dictionary!["UID"] as? String
            self.Tag = dictionary!["Tag"] as? String

        }
    }

我现在可以追加数组,但它们没有填充我的表。我还可以在 Firebase 中更改用户的标签,而不会导致应用崩溃,但它不会观察到更改后的标签并将用户移动到另一个数组。所以我需要这两件事的帮助。

【问题讨论】:

    标签: arrays swift uitableview firebase-realtime-database updating


    【解决方案1】:

    这个答案需要一些设置和重述目标

    目标是有两个数组,用作两个的数据源 表视图。一个数组包含用户类型:宏和另一个数组 包含用户类型:micro。

    如果用户类型从宏更改为微(反之亦然),则 OP 想要从一个表中删除用户并将其添加到另一个表中。

    概念上的答案是:向 Firebase 中的 users 节点添加一个观察者,当用户发生更改时,查看它是否是 type 属性,如果是,则从一个表中删除并将其添加到另一个表中。

    有很多解决方案,但让我提出这个非常冗长的答案,可以在代码和属性观察者中减少。

    从三个数组开始,一个保存所有用户,另一个保存宏观和微观用户。假设 UserClass 具有 macromicro 的 String type 属性

    var allUsersArray = [UserClass]()
    var macroArray = [UserClass]()
    var microArray = [UserClass]()
    

    读入所有用户,然后从中将用户划分为各自的类型

    ...read firebase single event of .value, with: { snapshot in
       and populate allUsersArray from the snapshot..
       self.macroArray = self.allUsersArray.filter { $0.type == "macro" }
       self.microArray = self.allUsersArray.filter { $0.type == "micro" }
       self.observeChangeInUserProperty()
    }
    

    下面的代码向用户数组添加了一个观察者,当用户的任何属性发生变化时,它将通知应用程序,并在快照中显示该用户节点。

    func observeChangeInUserProperty() {
        let ref = self.ref.child("users")
        ref.observe(.childChanged, with: { snapshot in
            let key = snapshot.key
            let type = snapshot.childSnapshot(forPath: "type").value as! String // ! = never optional
            //get the user from the allUsersArray by its key
            if let user = self.allUsersArray.first(where: { $0.key == key }) {
                if user.type != type { //if the type changed, handle it
                    user.type = type //update the allUsersArray
                    if type == "macro" { //if the new type is macro remove the user from the micro array
                        if let userIndex = self.microArray.firstIndex(where: { $0.key == key }) {
                            self.microArray.remove(at: userIndex)
                            self.macroArray.append(user) //add user to macro array
                        }
                    } else { //new type is micro so remove from macro array
                        if let userIndex = self.macroArray.firstIndex(where: { $0.key == key }) {
                            self.macroArray.remove(at: userIndex)
                            self.microArray.append(user)
                        }
                    }
                    //reload the tableviews to reflect the changes
                    self.microTable.reloadData()
                    self.mactoTable.reloadData()
                }
            }
        })
    }
    

    如前所述,这是很多无关代码,可以通过属性观察器进行压缩,或者将单个数组用于两个表或许多其他解决方案。

    编辑

    如果您想知道如何从 Firebase 填充所有用户数组,您需要有一个从快照初始化的 User 类,这是代码

    func loadAllUsersAndPopulateArray() {
        let ref = self.ref.child("users")
        ref.observeSingleEvent(of: .value, with: { snapshot in
            let allUsersSnapshot = snapshot.children.allObjects as! [DataSnapshot]
            for userSnap in allUsersSnapshot {
                let user = UserClass(withSnap: userSnap)
                self.allUsersArray.append(user)
            }
        })
    }
    

    【讨论】:

    • 嘿,杰。我用 Edit 2 更新了我的问题,显示了我如何获取您的代码并将其调整到我的项目,但我仍然遇到问题。
    • @NewCoder 你的代码会有问题,因为 Firebase 是异步的,闭包中的代码只有在从 Firebase 返回数据后才会执行。这意味着此代码 self.macroUsersArray = self.allUsersArray.filter 将在 Firebase 填充 allUsersArray 之前运行。代码比 Internet 更快,因此您需要在填充 allUsers 数组后在闭包中填充这些数组。请参阅我的答案中的代码。
    • 我添加了该代码并调用函数来更新 updateDataArray 函数中的子元素,但我仍然得到空的宏和微数组。接下来我应该尝试什么?
    • @NewCoder 问题仍然存在。在viewDidLoad 中,您的调用updateDataArray() 和紧随其后的行是self.macroUsersArray = self.allUsersArray.filter,因此该行将在updateDataArray() 内的Firebase 闭包中执行之前 代码,因此allUsersArray 将是空的。您需要异步编码。查看我的回答 herehere
    • 嘿,杰。对不起,我没有说清楚。请参阅编辑二中所做的编辑。我将 = self.allUsersArray.filter 移至 updateDataArray()。
    最近更新 更多