UITableView indexPath和可重用单元的最大数量

时间:2018-03-08 13:46:50

标签: ios arrays swift uitableview core-data

我的UITableView已自定义可重复使用的单元格已完成,但仍存在两个问题:

  1. 我可以创建6个单元格但是当我添加第7个时,应用程序崩溃了Unexpectedly found nil while unwrapping an Optional value我真的无法理解。当我重新启动应用时,view会立即崩溃。

  2. valueLabel.text以外,正确重复使用单元格的数据。 当我在滚动tableView的单元格上更新此值时,数据会退回一行(如图所示)。我认为这是与indexPath的错误编辑(更新?)相关的data source问题,但是当我重新启动应用时数据位于正确的单元格中时

  3. enter image description here

    我在下面的代码中标记了这些事件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()
    }
    }
    

    TableView功能:

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

    }

    CoreData功能:

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

2 个答案:

答案 0 :(得分:1)

您正在使用UIKit框架完全反向以达到预期目的:您的代码循环遍历您的数据模型,并希望更新表视图中的每个单元格。成像你有成千上万行,然后你会(至少)尝试更新数以千计的单元格。但是,正如@Larme已经提到的那样,这些单元格不存在(因为它们不可见),并且大多数调用都是徒劳的。

要正确使用UIKit,您必须更改您的方法:您不会主动更新,但如果表视图检测到它必须在单元格中(或在多个单元格中显示数据),您将被表视图调用每个一个接一个),因为视图可见,用户滚动等等。因此,如果表视图只显示5个单元格,它只询问5个单元格的数据,而不是整个数据库。

您必须做的是:

  • 删除func updateCellValue()。如果您认为自己必须更新视图,请在表格视图中致电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参数,以便在cryptosnewCryptos数组中访问正确的值。

答案 1 :(得分:0)

真正的问题在于更新单元格的逻辑。您使用名为updateCellValue()的函数来强制更新“从外部”的单元格。这是错的。相反,您将更新数据源并让tableView通过调用self.tableView.reloadData()来提取这些更改。永远不要通过更新其单元格来更新表格视图,但只需更新数据源就可以更新表格视图。