简体   繁体   English

iOS swift 应用程序在滚动到底部时终止于 TableView

[英]iOS swift app terminating on TableView when scrolled to bottom

I am using Firebase to populate a TableView in my iOS app.我正在使用Firebase在我的 iOS 应用程序中填充TableView The first few objects are loaded but once I get to the third item in my list the app crashes with the exception:前几个对象已加载,但是一旦我到达列表中的第三项,应用程序就会崩溃,但有以下异常:

'NSRangeException', reason: '*** __boundsFail: index 3 beyond bounds [0 .. 2]'

I know that this means that I am referring to an array at an index that it does not contain however I do not know why.我知道这意味着我指的是一个不包含索引的数组,但我不知道为什么。

I create the TableView with a TableViewController and initialize it like so:我使用TableViewController TableView像这样初始化它:

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        print(posts.count)
        return posts.count
    }


    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let post = posts[indexPath.row]
        print(post)
        let cell = tableView.dequeueReusableCell(withIdentifier: K.cellIdentifier, for: indexPath) as! PostCell

        let firstReference = storageRef.child(post.firstImageUrl)
        let secondReference = storageRef.child(post.secondImageUrl)

        cell.firstTitle.setTitle(post.firstTitle, for: .normal)
        cell.secondTitle.setTitle(post.secondTitle, for: .normal)
        cell.firstImageView.sd_setImage(with: firstReference)
        cell.secondImageView.sd_setImage(with: secondReference)

        // Configure the cell...

        return cell
    }

I believe that the first function creates an array with the number of objects in posts and that the second function assigns values to the template for the cell.我相信第一个 function 创建了一个包含posts中对象数量的数组,第二个 function 将值分配给单元格的模板。 The print statement in the first method prints 4 which is the correct number of objects retrieved from firebase.第一种方法中的打印语句打印 4,这是从 firebase 检索到的正确对象数。 I assume that means an array is created with 4 objects to be displayed in the TableView .我假设这意味着创建了一个数组,其中包含要在TableView中显示的 4 个对象。 This is what is really confusing because the error states that there are only 3 objects in the array.这是真正令人困惑的地方,因为错误表明数组中只有 3 个对象。 Am I misunderstanding how the TableView is instantiated?我是否误解了TableView是如何实例化的?

Here is the code that fills the TableView :这是填充TableView的代码:

func loadMessages(){
        db.collectionGroup("userPosts")
            .addSnapshotListener { (querySnapshot, error) in

            self.posts = []

            if let e = error{
                print("An error occured trying to get documents. \(e)")
            }else{
                if let snapshotDocuments = querySnapshot?.documents{
                    for doc in snapshotDocuments{
                        let data = doc.data()
                        if let firstImage = data[K.FStore.firstImageField] as? String,
                            let firstTitle = data[K.FStore.firstTitleField] as? String,
                            let secondImage = data[K.FStore.secondImageField] as? String,
                            let secondTitle = data[K.FStore.secondTitleField] as? String{
                            let post = Post(firstImageUrl: firstImage, secondImageUrl: secondImage, firstTitle: firstTitle, secondTitle: secondTitle)
                            self.posts.insert(post, at: 0)
                            print("Posts: ")
                            print(self.posts.capacity)
                            DispatchQueue.main.async {
                                self.tableView.reloadData()
                            }
                        }
                    }
                }
            }
        }

The app builds and runs and displays the first few items but crashes once I scroll to the bottom of the list.该应用程序构建并运行并显示前几个项目,但一旦我滚动到列表底部就会崩溃。 Any help is greatly appreciated.任何帮助是极大的赞赏。

Edit:编辑:

override func viewDidLoad() {
        super.viewDidLoad()
        tableView.dataSource = self
        tableView.register(UINib(nibName: K.cellNibName, bundle: nil), forCellReuseIdentifier: K.cellIdentifier)
        loadMessages()
    }

You're getting an out-of-bounds error because you're dangerously populating the datasource.由于您正在危险地填充数据源,因此您遇到了越界错误。 You have to remember that a table view is constantly adding and removing cells as it scrolls which makes updating its datasource a sensitive task.您必须记住,表格视图在滚动时会不断添加和删除单元格,这使得更新其数据源成为一项敏感任务。 You reload the table on each document iteration and insert a new element in the datasource at index 0 .您在每次文档迭代时重新加载表,并在数据源中的索引0处插入一个新元素。 Any scrolling during an update will throw an out-of-bounds error.更新期间的任何滚动都会引发越界错误。

Therefore, populate a temporary datasource and hand that off to the actual datasource when it's ready (and then immediately reload the table, leaving no space in between an altered datasource and an active scroll fetching from that datasource).因此,填充一个临时数据源并在它准备好时将其交给实际数据源(然后立即重新加载表,在更改的数据源和从该数据源获取的活动滚动之间不留任何空间)。

private var posts = [Post]()
private let q = DispatchQueue(label: "userPosts") // serial queue

private func loadMessages() {
    db.collectionGroup("userPosts").addSnapshotListener { [weak self] (snapshot, error) in
        self?.q.async { // go into the background (and in serial)
            guard let snapshot = snapshot else {
                if let error = error {
                    print(error)
                }
                return
            }
            var postsTemp = [Post]() // setup temp collection
            for doc in snapshot.documents {
                if let firstImage = doc.get(K.FStore.firstImageField) as? String,
                    let firstTitle = doc.get(K.FStore.firstTitleField) as? String,
                    let secondImage = doc.get(K.FStore.secondImageField) as? String,
                    let secondTitle = doc.get(K.FStore.secondTitleField) as? String {
                    let post = Post(firstImageUrl: firstImage, secondImageUrl: secondImage, firstTitle: firstTitle, secondTitle: secondTitle)
                    postsTemp.insert(post, at: 0) // populate temp
                }
            }
            DispatchQueue.main.async { // hop back onto the main queue
                self?.posts = postsTemp // hand temp off (replace or append)
                self?.tableView.reloadData() // reload
            }
        }
    }
}

Beyond this, I would handle this in the background (Firestore returns on the main queue) and only reload the table if the datasource was modified.除此之外,我将在后台处理此问题(Firestore 在主队列上返回),并且仅在修改数据源时才重新加载表。

After some fiddling around and implementing @bsod's response I was able to get my project running.经过一番摆弄和实施@bsod 的响应后,我能够让我的项目运行起来。 The solution was in Main.Storyboard under the Attributes inspector I had to set the content to Dynamic Prototypes.解决方案位于属性检查器下的Main.Storyboard中,我必须将内容设置为动态原型。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM