TableView滚动时自定义Cell书签按钮图片异常切换问题求助
解决TableView滚动时书签按钮状态异常的问题
这是典型的UITableView单元格复用机制引发的UI状态不一致问题,咱们一步步拆解问题并解决:
问题根源分析
setMaterials方法未同步UI与数据:你当前只把数据赋值给cell的materials属性,但没有根据数据里的书签状态更新按钮图片,导致复用cell时,按钮还保留着之前单元格的状态。- 书签点击逻辑颠倒:点击按钮时的判断逻辑搞反了——当
bookmarked为false(未书签),应该设置为填充图片,而不是空图片,反之亦然。 - 数据源未同步更新:调用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
相关产品推荐
相关产品推荐

