如何使用KVO根据底层数组元素更改更新tableViewCells?

问题描述 投票:2回答:2

我有一个表示底层数组的表视图。单元格有一个标签和一个滑块,它应该显示数组的percentage属性的值。

我希望在百分比属性发生变化时使用键值观察来更新标签。 (我知道KVO在这个例子中有点过分,但最终滑动一个滑块会影响其他单元格,包括滑块的位置,底层数组将从应用程序中的多个位置设置,随时都可以使用KVO。 )

我有一堆帮助from this answer,但我无法解决它并更新标签。我在这里包含了所有代码。不知道我哪里出错了。

import UIKit

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, CustomCellDelegate {

    @IBOutlet weak var tableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.dataSource = self
        tableView.delegate = self

        for i in 0...4 {
            items.append(Items(ID: i, percentage: 50))
        }
    }

    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        if let cell = tableView.dequeueReusableCell(withIdentifier: myTableViewCell.ID) as? myTableViewCell {
            cell.object = items[indexPath.row]
            cell.mySlider.tag = indexPath.row

            return cell

        } else {
            return UITableViewCell()
        }

    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 100
    }


    @IBAction func sliderValueChanged(_ sender: UISlider) {
        items[sender.tag].percentage = Double(sender.value)
        print("percentage at \(items[sender.tag].ID) is \(items[sender.tag].percentage)")
    }

    func didUpdateObject(for cell: UITableViewCell) {
        if let indexPath = tableView.indexPath(for: cell) {
            tableView.reloadRows(at: [indexPath], with: .automatic)
            print("hello")
        }
    }
}

class myTableViewCell: UITableViewCell {
    static let ID = "myCell"
    weak var delegate: CustomCellDelegate?
    private var token: NSKeyValueObservation?

    var object: Items? {
        willSet {
            token?.invalidate()
        }
        didSet {
            myLabel.text = "\(object?.percentage ?? 0)"
            token = object?.observe(\.percentage) { [weak self] object, change in
                if let cell = self {
                    cell.delegate?.didUpdateObject(for: cell)
                }
            }
        }
    }

    override func awakeFromNib() {
        super.awakeFromNib()
    }

    @IBOutlet weak var myLabel: UILabel!
    @IBOutlet weak var mySlider: UISlider!

}

class Items: NSObject {
    let ID: Int
    @objc dynamic var percentage: Double

    init(ID: Int, percentage: Double){
        self.ID = ID
        self.percentage = percentage
        super.init()
    }
}

var items: [Items] = []


protocol CustomCellDelegate: class {
    func didUpdateObject(for cell: UITableViewCell)
}

Label not updating

ios swift key-value-observing
2个回答
2
投票

要在Swift 4中执行KVO,您必须将该属性声明为dynamic并在该对象上调用observe(_:options:changeHandler:),从而保存生成的NSKeyValueObservation标记。当该令牌超出范围(或替换为另一个令牌)时,将自动删除原始观察者。

在您的情况下,您让观察者调用委托,然后重新加载单元格。但你似乎永远不会设置delegate属性,所以我怀疑该方法没有被调用。

但这一切似乎有点脆弱。我倾向于直接在观察者的changeHandler中更新标签。我也认为你可以更直接地更新单元格(我将“值更改”IBAction放在单元格中,而不是表格视图中),并消除了使用tag来识别模型中哪一行的相当尴尬数组的滑块已更新(如果插入或删除行,则可能会出现问题)。

所以考虑这个对象:

class CustomObject: NSObject {
    let name: String
    @objc dynamic var value: Float       // this is the property that the custom cell will observe

    init(name: String, value: Float) {
        self.name = name
        self.value = value

        super.init()
    }
}

然后,您可以拥有一个表视图控制器,该控制器使用此模型类型的实例填充objects数组。这里的细节与观察结果基本无关(我们将在下面介绍),但我将其包括在内只是为了提供一个完整的例子:

class ViewController: UITableViewController {

    var objects: [CustomObject]!

    override func viewDidLoad() {
        super.viewDidLoad()

        // self sizing cells

        tableView.estimatedRowHeight = 60
        tableView.rowHeight = UITableViewAutomaticDimension

        // populate model with random data

        let formatter = NumberFormatter()
        formatter.numberStyle = .spellOut

        objects = (0 ..< 1000).map {
            CustomObject(name: formatter.string(for: $0)!, value: 0.5)
        }
    }
}

// MARK: - UITableViewDataSource

extension ViewController {
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return objects?.count ?? 0
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
        cell.object = objects[indexPath.row]
        return cell
    }
}

完成后,您现在可以拥有单元格的基类(a)如果滑块更改,则更新模型对象; (b)观察dynamic属性的变化,在本例中,当在模型对象中观察到value变化时更新标签:

class CustomCell: UITableViewCell {
    @IBOutlet weak var nameLabel: UILabel!
    @IBOutlet weak var valueLabel: UILabel!
    @IBOutlet weak var valueSlider: UISlider!

    static private let formatter: NumberFormatter = {
        let _formatter = NumberFormatter()
        _formatter.maximumFractionDigits = 2
        _formatter.minimumFractionDigits = 2
        _formatter.minimumIntegerDigits = 1
        return _formatter
    }()

    private var token: NSKeyValueObservation?

    weak var object: CustomObject? {
        didSet {
            let value = object?.value ?? 0

            nameLabel.text = object?.name
            valueLabel.text = CustomCell.formatter.string(for: value)
            valueSlider.value = value

            token = object?.observe(\.value) { [weak self] object, change in
                self?.valueLabel.text = CustomCell.formatter.string(for: object.value)
            }
        }
    }

    @IBAction func didChangeSlider(_ slider: UISlider) {
        object?.value = slider.value
    }
}

产量:

enter image description here

有关更多信息,请参阅Using Swift with Cocoa and Objective-C: Adopting Cocoa Patterns的“Key-Value Observing”部分。


0
投票

您好@sean问题是在UITableview单元格类中您已经制作了diSet方法,因此您不需要为cell.lable和滑块传递值只需尝试下面的代码

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    if let cell = tableView.dequeueReusableCell(withIdentifier: myTableViewCell.ID) as? myTableViewCell {
        //pass the object to which you wanna add observer to cell
        cell.object = items[indexPath.row]
        return cell
    } else {
        return UITableViewCell()
    }

}
© www.soinside.com 2019 - 2024. All rights reserved.