我的带有自定义可重用单元格的 UITableView
已完成,但仍然存在两个问题:
我可以创建 6 个单元格,但是当我添加第 7 个单元格时,应用程序崩溃并显示
Unexpectedly found nil while unwrapping an Optional value
我真的无法理解。当我重新启动应用程序时,View
再次立即崩溃。除了
valueLabel.text
之外,单元格的数据被正确重用。 当我在tableView
滚动的单元格上更新此值时,数据回落一行(如图所示)。我相信这是一个与data source
的错误编辑(更新?)相关的indexPath
问题,但是当我重新启动应用程序时,数据位于正确的单元格中。
我在下面的代码中标记了这些事件 1 和 2。
创建数据:
func createCryptoArray(_ addedCrypto: String) {
if addedCrypto == "Bitcoin BTC" {
if CoreDataHandler.saveObject(name: "Bitcoin", code: "bitcoin", symbol: "BTC", placeholder: "BTC Amount", amount: "0.00000000", amountValue: "0.0") {
for _ in CDHandler.fetchObject()! {
}
}
}
if addedCrypto == "Bitcoin Cash BCH" {
if CoreDataHandler.saveObject(name: "Bitcoin Cash", code: "bitcoinCash", symbol: "BCH", placeholder: "BCH Amount", amount: "0.00000000", amountValue: "0.0") {
for _ in CDHandler.fetchObject()! {
}
}
}
//...
}
}
WalletTableViewController:(问题 1)
class WalletTableViewController: UIViewController, UITextFieldDelegate {
@IBOutlet weak var tableView: UITableView!
var cryptos : [CryptosMO] = []
var total : Double = 0.0
static var staticTotal : Double = 0.0
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
if CDHandler.fetchObject() != nil {
cryptos = CDHandler.fetchObject()!
tableView.reloadData()
}
update()
updateWalletValue()
updateWalletLabel()
}
override func viewWillAppear(_ animated: Bool) {
update()
tableView.delegate = self
tableView.dataSource = self
if CDHandler.fetchObject() != nil {
cryptos = CDHandler.fetchObject()!
tableView.reloadData()
}
}
func updateCellValue() {
for section in 0...self.tableView.numberOfSections - 1 {
if (self.tableView.numberOfRows(inSection: section) >= 1) {
for row in 0...self.tableView.numberOfRows(inSection: section) - 1 {
let indexP: IndexPath = IndexPath(row: row, section: section);
self.updateCellValueLabel(self.tableView.cellForRow(at: indexP) as! WalletTableViewCell) // <-------Problem 1
}
}
}
}
func update() {
updateCellValue()
}
}
表格 View 函数:
extension WalletTableViewController: UITableViewDelegate, UITableViewDataSource, CryptoCellDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cryptos.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! WalletTableViewCell
cell.cryptoNameLabel.text = cryptos[indexPath.row].name
cell.cryptoCodeLabel.text = cryptos[indexPath.row].symbol
cell.amountLabel.text = cryptos[indexPath.row].amount
cell.amountTextField.placeholder = cryptos[indexPath.row].placeholder
if cryptos[indexPath.row].amountValue == "0.0" {
cell.cryptoValueLabel.text = ""
}
cell.delegate = self
cell.amountTextField.delegate = self
return cell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let selectedManagedObject = cryptos[indexPath.row]
CDHandler.deleteObject(entity:"CryptosMO", deleteObject: selectedManagedObject)
cryptos.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
updateWalletValue()
updateWalletLabel()
}
}
用户操作、计算和数据更新:(使用 valueLabel.text's
)
// TextFields amounts
//--------------------
func cellAmountEntered(_ walletTableViewCell: WalletTableViewCell) {
if walletTableViewCell.amountTextField.text == "" {
return
}
let str = walletTableViewCell.amountTextField.text
let formatter = NumberFormatter()
formatter.locale = Locale(identifier: "en_US")
let dNumber = formatter.number(from: str!)
let nDouble = dNumber!
let eNumber = Double(truncating: nDouble)
walletTableViewCell.amountLabel.text = String(format:"%.8f", eNumber)
var editAmount = ""
editAmount = String(format:"%.8f", eNumber)
let indexPath = tableView.indexPath(for: walletTableViewCell)
let selectedManagedObject = cryptos[(indexPath?.row)!]
CDHandler.editObject(editObject: selectedManagedObject, amount: editAmount, amountValue: "0.0")
walletTableViewCell.amountTextField.text = ""
}
// Value calculation & label update
//----------------------------------
func updateCellValueLabel(_ walletTableViewCell: WalletTableViewCell) {
if walletTableViewCell.amountLabel.text == "" {
walletTableViewCell.amountLabel.text = "0.00000000"
}
var newCryptos : [CryptosMO] = []
var doubleAmount = 0.0
var cryptoPrice = ""
let indexPath = tableView.indexPath(for: walletTableViewCell)
if CDHandler.fetchObject() != nil {
newCryptos = CDHandler.fetchObject()!
}
cryptoPrice = cryptos[(indexPath?.row)!].code!
guard let cryptoDoublePrice = CryptoInfo.cryptoPriceDic[cryptoPrice] else { return }
let selectedAmount = newCryptos[(indexPath?.row)!]
guard let amount = selectedAmount.amount else { return }
var currentAmountValue = selectedAmount.amountValue
doubleAmount = Double(amount)!
let calculation = cryptoDoublePrice * doubleAmount
currentAmountValue = String(calculation)
CoreDataHandler.editObject(editObject: selectedAmount, amount: amount, amountValue: currentAmountValue)
walletTableViewCell.valueLabel.text = String(calculation) // <-------- `valueLabel.text`
}
核心数据函数:
class CoreDataHandler: NSObject {
class func getContext() -> NSManagedObjectContext {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
return appDelegate.persistentContainer.viewContext
}
class func saveObject(name:String, code:String, symbol:String, placeholder:String, amount:String, amountValue:String) -> Bool {
let context = getContext()
let entity = NSEntityDescription.entity(forEntityName: "CryptosMO", in: context)
let managedObject = NSManagedObject(entity: entity!, insertInto: context)
managedObject.setValue(name, forKey: "name")
managedObject.setValue(code, forKey: "code")
managedObject.setValue(symbol, forKey: "symbol")
managedObject.setValue(placeholder, forKey: "placeholder")
managedObject.setValue(amount, forKey: "amount")
managedObject.setValue(amountValue, forKey: "amountValue")
do {
try context.save()
return true
} catch {
return false
}
}
class func fetchObject() -> [CryptosMO]? {
let context = getContext()
var cryptos: [CryptosMO]? = nil
do {
cryptos = try context.fetch(CryptosMO.fetchRequest()) as? [CryptosMO]
return cryptos
} catch {
return cryptos
}
}
class func deleteObject(entity: String, deleteObject: NSManagedObject) {
let context = getContext()
context.delete(deleteObject)
do {
try context.save()
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
class func editObject(editObject: NSManagedObject, amount: String, amountValue: String) {
let context = getContext()
let managedObject = editObject
do {
managedObject.setValue(amount, forKey: "amount")
managedObject.setValue(amountValue, forKey: "amountValue")
try context.save()
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
}
最佳答案
您使用的 UIKit 框架完全相反其预期方式:您的代码循环遍历您的数据模型并希望更新 TableView 中的每个单元格。假设您有数千行,那么您将(至少)尝试更新数以千计的单元格。但是,正如@Larme 已经提到的,这些单元格不存在(因为它们不可见),而且您的大部分调用都是徒劳的。
要正确使用 UIKit,你必须改变你的做法:你不会主动更新,而是如果表格 View 检测到它必须在一个单元格(或多个单元格,每个一个接一个),因为 View 可见,用户滚动等。所以如果 TableView 只显示 5 个单元格,它只要求 5 个单元格的数据,而不是整个数据库。
你需要做的是:
- 删除
func updateCellValue()
。如果您认为必须更新 View ,请在 TableView 中调用reloadData()
或reloadRowsAtIndexPaths...
。这将导致调用适当的数据源方法,例如... - 在
tableView(cellForRowAt:)
的末尾,您使用出列的单元格调用updateCellValueLabel
,然后它将更新标签。 - 您还应该修改
updateCellValueLabel
并提交IndexPath
(或仅行),因此您不需要调用tableView.indexPath(for: walletTableViewCell)
任何更多。
这只是一个简单的操作提示;你也可能会考虑优化数据获取
补充关于第三点(见评论): 您可以通过以下方式修改您的代码:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// ... same as before
cell.delegate = self
cell.amountTextField.delegate = self
updateCellValueLabel(cell, atRow:indexPath.row)
return cell
}
func updateCellValueLabel(_ walletTableViewCell: WalletTableViewCell, atRow row:Int) {
if walletTableViewCell.amountLabel.text == "" {
walletTableViewCell.amountLabel.text = "0.00000000"
}
var newCryptos : [CryptosMO] = []
var doubleAmount = 0.0
var cryptoPrice = ""
if CDHandler.fetchObject() != nil {
newCryptos = CDHandler.fetchObject()!
}
cryptoPrice = cryptos[row].code!
guard let cryptoDoublePrice = CryptoInfo.cryptoPriceDic[cryptoPrice] else { return }
let selectedAmount = newCryptos[row]
guard let amount = selectedAmount.amount else { return }
var currentAmountValue = selectedAmount.amountValue
doubleAmount = Double(amount)!
let calculation = cryptoDoublePrice * doubleAmount
currentAmountValue = String(calculation)
CoreDataHandler.editObject(editObject: selectedAmount, amount: amount, amountValue: currentAmountValue)
walletTableViewCell.valueLabel.text = String(calculation) // <-------- `valueLabel.text`
}
因此 updateCellValueLabel
获得一个额外的 atRow
参数,以访问 cryptos
和 newCryptos
数组中的正确值.
关于ios - UITableView indexPath 和最大可重用单元格数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49174668/