【问题标题】:iOS swift app terminating on TableView when scrolled to bottom滚动到底部时,iOS swift 应用程序在 TableView 上终止
【发布时间】:2020-06-09 18:30:29
【问题描述】:

我正在使用 Firebase 在我的 iOS 应用程序中填充 TableView。前几个对象已加载,但一旦我到达列表中的第三个项目,应用程序就会崩溃并出现异常:

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

我知道这意味着我指的是一个不包含索引的数组,但我不知道为什么。

我用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
    }

我相信第一个函数创建了一个数组,其中包含 posts 中的对象数,并且第二个函数将值分配给单元格的模板。第一种方法中的 print 语句打印 4,这是从 firebase 检索到的正确数量的对象。我假设这意味着创建了一个数组,其中包含要在TableView 中显示的 4 个对象。这是真正令人困惑的地方,因为错误表明数组中只有 3 个对象。我是否误解了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()
                            }
                        }
                    }
                }
            }
        }

应用程序构建并运行并显示前几个项目,但在我滚动到列表底部时崩溃。非常感谢任何帮助。

编辑:

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

【问题讨论】:

  • 代码中没有证据表明出现越界崩溃。它不相关,但将 self.tableView.reloadData() 替换为 self.tableView.insertRows(at: [0,0], with: .automatic) 以获得动画插入或将 DispatchQueue 块移动到循环之后。
  • @vadian,谢谢您的评论是否有可能我可以添加的代码会有所帮助?我认为这是 TableView 创建的,所以这将是问题的根源
  • 还可以考虑在后台队列中解析数据。 Firestore 在主线程上返回。
  • @bsod 我不熟悉如何做到这一点,你能指出一个可以帮助我的资源吗?我将非常感激。非常感谢您的所有帮助。
  • 编辑了我的答案以包括后台排队。首先阅读:stackoverflow.com/questions/19179358/…

标签: ios swift firebase tableview


【解决方案1】:

您遇到了越界错误,因为您正在危险地填充数据源。您必须记住,表格视图在滚动时会不断添加和删除单元格,这使得更新其数据源成为一项敏感任务。您在每次文档迭代时重新加载表,并在索引0 处的数据源中插入一个新元素。更新期间的任何滚动都会引发越界错误。

因此,填充一个临时数据源并在实际数据源准备就绪时将其移交给实际数据源(然后立即重新加载表,在更改的数据源和从该数据源获取的活动滚动之间不留任何空间)。

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
            }
        }
    }
}

除此之外,我将在后台处理此问题(Firestore 在主队列上返回),并且仅在修改数据源时重新加载表。

【讨论】:

  • 感谢您的回答。我应用了你的解决方案,但我仍然遇到同样的问题。我已经编辑了我的问题以包括我的viewDidLoad,以防它成为问题的一部分。
  • 你的问题出在其他地方。将视图控制器剥离到最低限度并找出你哪里出错了。但我可以肯定地告诉你,以滚动时的方式修改数据源会引发该错误。
【解决方案2】:

经过一番摆弄和实施@bsod 的响应后,我能够让我的项目运行起来。解决方案在属性检查器下的Main.Storyboard 中,我必须将内容设置为动态原型。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-09-29
    • 1970-01-01
    • 1970-01-01
    • 2021-11-17
    • 2014-12-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多