iOS阅读进度追踪App:TableView Cell属性更新失败求助
书籍阅读进度追踪App同步更新问题解决方案
问题背景
我正在开发一款书籍阅读进度追踪App,核心模块功能:
- HomeViewController:包含TableView,展示已添加的书籍,每行显示书籍标题、进度条及当前页码/总页码
- AddBookController:负责添加书籍数据,通过代理将数据传递给HomeViewController,生成TableView行
- BookDetailViewController:点击TableView任意行进入该页面,用于修改书籍的当前阅读页码
遇到的问题:在BookDetailViewController中更新页码后,TableView对应Cell的currentPage无法同步更新。虽然能把更新后的页码传递到HomeViewController,但不知道如何让Cell的数值与新值对齐,尝试过didSet观察者但未成功。
相关代码
HomeViewController
class HomeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, SendingBookDataProtocol { // 无用变量,之前尝试的didSet未生效 // var updatedPage = String() { // didSet { // print(updatedPage) // tableView?.reloadData() // } // } var items = [BookItem]() var item: BookItem? override func viewDidLoad() { super.viewDidLoad() tableView?.delegate = self tableView?.dataSource = self let nib = UINib(nibName: "BookCell", bundle: nil) tableView?.register(nib, forCellReuseIdentifier: "BookCell") } func sendDataToHomeController(bookEntry item: BookItem) { items.append(item) tableView.reloadData() } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { items.count } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let bookDetailVc = self.storyboard?.instantiateViewController(withIdentifier: "BookDetailView") as? BookDetailViewController let item = items[indexPath.row] let currentPageInt = Float(item.currentPage)! let totalPagesInt = Float(item.totalPages)! let result = Int(totalPagesInt - currentPageInt) let percentageRead = Int((currentPageInt / totalPagesInt) * 100) bookDetailVc?.lblName = item.title bookDetailVc?.lblCurrentPage = Int(Float(item.currentPage)!) // ...其他传递数据的代码 self.navigationController!.pushViewController(bookDetailVc!, animated: true) } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "BookCell", for: indexPath) as! BookCell item = items[indexPath.row] cell.title.text = item?.title // ...其他Cell赋值代码 cell.pageNumbers.text = "S. " + item!.currentPage + " / " + item!.totalPages // 需要更新currentPage return cell } }
BookDetailViewController
class BookDetailViewController: HomeViewController, UIPickerViewDelegate, UIPickerViewDataSource{ @IBOutlet weak var bookTitle: UILabel! @IBOutlet weak var numberPicker: UIPickerView! var lblName = String() var lblCurrentPage = Int() var pickerData: [String] = [] // 假设已初始化页码数据 override func viewDidLoad() { super.viewDidLoad() self.numberPicker.delegate = self self.numberPicker.dataSource = self // ...其他初始化代码 } func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { let valueSelected = pickerData[row] as String // 错误:实例化了新的HomeViewController,而非当前导航栈中的实例 if let homeVc = storyboard?.instantiateViewController(withIdentifier: "getBookData") as? HomeViewController{ homeVc.updatedPage = valueSelected homeVc.tableView?.reloadData() } } // 补充Picker数据源方法 func numberOfComponents(in pickerView: UIPickerView) -> Int { return 1 } func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { return pickerData.count } }
AddBookController
protocol SendingBookDataProtocol { func sendDataToHomeController(bookEntry: BookItem) } struct BookItem { let title,author,currentPage,totalPages:String let image: UIImage? } class AddBookController: UIViewController, UITextFieldDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate { var delegate: SendingBookDataProtocol? = nil @IBAction func buttonSave(_ sender: Any) { let bookEntry = BookItem(title: textfieldTitle.text!, author: textfieldAuthor.text!, currentPage: fieldCurrentPage.text!, totalPages: fieldTotalPages.text!, image: bookImage) self.delegate?.sendDataToHomeController(bookEntry: bookEntry) dismiss(animated: true, completion: nil) } }
问题根源
- 数据模型不可修改:BookItem的
currentPage用let声明,是不可变属性,无法修改值 - 错误的页面实例引用:在BookDetailViewController中通过storyboard实例化了新的HomeViewController,而非导航栈中已存在的实例,导致更新的是新对象的变量,对原页面无影响
- 未更新数据源:即使传递了新页码,也没有修改
items数组中对应BookItem的currentPage值,reloadData后仍显示旧数据
解决方案
步骤1:修改数据模型,允许修改currentPage
将BookItem的currentPage从let改为var:
struct BookItem { let title, author, totalPages: String var currentPage: String // 改为可变属性 let image: UIImage? }
步骤2:给BookDetailViewController添加回调闭包
用闭包代替错误的页面实例化,实现数据回传:
class BookDetailViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource{ @IBOutlet weak var bookTitle: UILabel! @IBOutlet weak var numberPicker: UIPickerView! // 替换原有的lblName、lblCurrentPage,用更清晰的命名 var bookTitleText: String = "" var currentPage: Int = 0 var totalPages: Int = 0 // 回调闭包,用于传递更新后的页码 var updatePageHandler: ((Int) -> Void)? var pickerData: [String] = [] override func viewDidLoad() { super.viewDidLoad() numberPicker.delegate = self numberPicker.dataSource = self // 初始化页面数据 bookTitle.text = bookTitleText // 生成Picker的页码数据(从1到总页数) pickerData = (1...totalPages).map { String($0) } // 默认选中当前页码 if let selectedIndex = pickerData.firstIndex(of: String(currentPage)) { numberPicker.selectRow(selectedIndex, inComponent: 0, animated: false) } } func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { let selectedPage = Int(pickerData[row]) ?? currentPage // 调用闭包传递更新后的页码 updatePageHandler?(selectedPage) } // Picker数据源方法实现 func numberOfComponents(in pickerView: UIPickerView) -> Int { return 1 } func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { return pickerData.count } func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { return pickerData[row] } }
步骤3:修改HomeViewController的跳转逻辑,传递索引和回调
在跳转到BookDetailViewController时,传递必要数据并设置回调闭包,更新数据源并刷新对应Cell:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { guard let bookDetailVc = storyboard?.instantiateViewController(withIdentifier: "BookDetailView") as? BookDetailViewController else { return } let item = items[indexPath.row] // 传递页面所需数据 bookDetailVc.bookTitleText = item.title bookDetailVc.currentPage = Int(item.currentPage) ?? 1 bookDetailVc.totalPages = Int(item.totalPages) ?? 1 // 设置回调闭包,更新数据源并刷新当前行 bookDetailVc.updatePageHandler = { [weak self] newPage in guard let self = self else { return } // 修改数据源中对应书籍的currentPage self.items[indexPath.row].currentPage = String(newPage) // 只刷新当前行,性能更优 self.tableView.reloadRows(at: [indexPath], with: .automatic) } navigationController?.pushViewController(bookDetailVc, animated: true) }
步骤4:移除无用代码
删除HomeViewController中无用的updatedPage变量及didSet观察者,因为已经通过闭包实现了正确的数据更新逻辑。
效果验证
修改完成后,在BookDetailViewController中选择新的页码,返回HomeViewController时,对应Cell的当前页码会自动更新,且只刷新修改的行,提升性能。
内容的提问来源于stack exchange,提问作者alias.marv
相关产品推荐
相关产品推荐

