You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TableView滚动时自定义Cell书签按钮图片异常切换问题求助

解决TableView滚动时书签按钮状态异常的问题

这是典型的UITableView单元格复用机制引发的UI状态不一致问题,咱们一步步拆解问题并解决:

问题根源分析

  1. setMaterials方法未同步UI与数据:你当前只把数据赋值给cell的materials属性,但没有根据数据里的书签状态更新按钮图片,导致复用cell时,按钮还保留着之前单元格的状态。
  2. 书签点击逻辑颠倒:点击按钮时的判断逻辑搞反了——当bookmarked为false(未书签),应该设置为填充图片,而不是空图片,反之亦然。
  3. 数据源未同步更新:调用API修改书签状态后,没有更新主控制器里的materials数组对应条目状态,导致滚动回来时还是旧的状态。

具体解决步骤

1. 修复CustomTableViewCell的setMaterials方法

每次给cell设置数据时,直接根据当前material的书签状态更新按钮图片,确保cell复用后UI和数据一致:

func setMaterials(materials: GetMaterialsByTypeQuery.Data.AllMaterial) {
    self.materials = materials
    // 根据书签状态设置按钮图片
    let isBookmarked = materials.fragments.materialFields.bookmark?.bookmarked ?? false
    let image = isBookmarked ? #imageLiteral(resourceName: "Bookmark Filled") : #imageLiteral(resourceName: "bookmarkEmpty")
    self.bookmarkButton.setImage(image, for: .normal)
}

2. 修正书签按钮点击逻辑

点击时先切换UI状态,更新当前cell保存的materials的书签状态(临时),再调用代理请求API:

@IBAction func bookmarkButtonTapped(_ sender: UIButton) {
    guard var material = self.materials else { return }
    
    // 切换当前书签状态
    let currentIsBookmarked = material.fragments.materialFields.bookmark?.bookmarked ?? false
    let newIsBookmarked = !currentIsBookmarked
    
    // 更新按钮图片
    let newImage = newIsBookmarked ? #imageLiteral(resourceName: "Bookmark Filled") : #imageLiteral(resourceName: "bookmarkEmpty")
    self.bookmarkButton.setImage(newImage, for: .normal)
    
    // 更新cell内的material状态(临时,等待API响应确认)
    // 注意:如果material是不可变结构体,需要创建副本修改后重新赋值
    var mutableMaterial = material
    if var bookmark = mutableMaterial.fragments.materialFields.bookmark {
        bookmark.bookmarked = newIsBookmarked
        mutableMaterial.fragments.materialFields.bookmark = bookmark
    }
    self.materials = mutableMaterial
    
    // 调用代理方法
    if newIsBookmarked {
        delegate?.setBookmark(refId: material.id, isBookmarking: true, refType: "material", refSubType: material.subType)
    } else {
        delegate?.setUnbookmark(refId: material.id, isBookmarking: false, refType: "material", refSubType: material.subType)
    }
}

3. 优化prepareForReuse方法

不需要设置图片为nil,因为setMaterials会在复用后立即覆盖图片,不过如果想更稳妥,可以重置为默认未书签状态:

override func prepareForReuse() {
    super.prepareForReuse()
    self.materials = nil
    self.bookmarkButton.setImage(#imageLiteral(resourceName: "bookmarkEmpty"), for: .normal)
}

4. 同步更新主控制器的数据源

在代理方法的API响应成功后,更新materials数组对应条目的书签状态,确保滚动回来时数据是最新的:

extension BrowseViewController: ViewBookmarkedDelegate {
    func setBookmark( refId: String, isBookmarking: Bool, refType: String, refSubType: String) {
        let query = SetBookmarkMutation(refId: refId, refType: "material", isBookmarking: true, refSubType: refSubType)
        Apollo.shared.client.perform(mutation: query ) { results, error in
            if let bookmarked = results?.data?.bookmark {
                print(refId,bookmarked,"onBookmark")
                // 更新数据源
                if let index = self.materials.firstIndex(where: { $0.id == refId }) {
                    var mutableMaterial = self.materials[index]
                    if var bookmark = mutableMaterial.fragments.materialFields.bookmark {
                        bookmark.bookmarked = bookmarked
                        mutableMaterial.fragments.materialFields.bookmark = bookmark
                    }
                    self.materials[index] = mutableMaterial
                    // 可选:刷新对应行,确保UI同步
                    self.tableView.reloadRows(at: [IndexPath(row: index, section: 0)], with: .automatic)
                }
            } else if let error = error {
                print("Error loading data \(error)")
                // 可选:API失败后回滚UI状态
                if let index = self.materials.firstIndex(where: { $0.id == refId }) {
                    self.tableView.reloadRows(at: [IndexPath(row: index, section: 0)], with: .automatic)
                }
            }
        }
    }
    
    func setUnbookmark(refId: String, isBookmarking: Bool, refType: String, refSubType: String) {
        let query = SetBookmarkMutation(refId: refId, refType: "material", isBookmarking: false, refSubType: refSubType)
        Apollo.shared.client.perform(mutation: query ) { results, error in
            if let bookmarked = results?.data?.bookmark {
                print(refId,bookmarked,"onUnbookmark")
                // 更新数据源
                if let index = self.materials.firstIndex(where: { $0.id == refId }) {
                    var mutableMaterial = self.materials[index]
                    if var bookmark = mutableMaterial.fragments.materialFields.bookmark {
                        bookmark.bookmarked = bookmarked
                        mutableMaterial.fragments.materialFields.bookmark = bookmark
                    }
                    self.materials[index] = mutableMaterial
                    // 可选:刷新对应行,确保UI同步
                    self.tableView.reloadRows(at: [IndexPath(row: index, section: 0)], with: .automatic)
                }
            } else if let error = error {
                print("Error loading data \(error)")
                // 可选:API失败后回滚UI状态
                if let index = self.materials.firstIndex(where: { $0.id == refId }) {
                    self.tableView.reloadRows(at: [IndexPath(row: index, section: 0)], with: .automatic)
                }
            }
        }
    }
}

额外说明

  • 如果你使用的是不可变的Material结构体,修改内部属性时必须创建副本再赋值,否则无法更新状态。
  • API请求失败后刷新对应行,可以让cell重新从数据源获取正确状态,避免UI和实际数据不一致。

内容的提问来源于stack exchange,提问作者msoto

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:05:51