如何使用 KVO 根据底层数组元素的变化来更新 tableViewCells?
How to use KVO to update tableViewCells based on underlying array element changes?
我有一个代表底层数组的 table 视图。这些单元格有一个标签和一个滑块,它应该显示数组的 percentage
属性 的值。
我想在百分比 属性 发生变化时使用键值观察来更新标签。 (我知道 KVO 在这个例子中有点矫枉过正,但最终滑动一个滑块会影响其他单元格,包括滑块的位置和底层数组将从应用程序中的多个位置随时设置,所以 KVO 是可行的方法。 )
我得到了很多帮助 ,但我无法启动它并更新标签。我在这里包括我所有的代码。不知道我哪里错了。
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)
}
嗨@sean 问题在 UITableview 单元格中 class 你已经制作了 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()
}
}
要在 Swift 4 中执行 KVO,您必须将 属性 声明为 dynamic
并对该对象调用 observe(_:options:changeHandler:)
,保存结果 NSKeyValueObservation
令牌。当该令牌超出范围(或被另一个令牌替换)时,原始观察者将自动被删除。
在您的情况下,您让观察者调用委托,然后委托重新加载单元格。但是您似乎从来没有设置 delegate
属性,所以我怀疑没有调用该方法。
但这一切似乎都有些脆弱。我倾向于直接在观察者 changeHandler
中更新标签。我还认为您可以更直接地更新单元格(我将 "value changed" IBAction 放在单元格中,而不是 table 视图中),并消除 [=18 的相当尴尬的使用=] 以确定模型数组中的哪一行更新了滑块(如果插入或删除行,这可能会出现问题)。
所以考虑这个对象:
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()
}
}
然后您可以拥有一个 table 视图控制器,它使用此模型类型的实例填充 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: [=11=])!, 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
}
}
完成后,您现在可以为您的单元格设置基础 class (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
}
}
产生:
有关详细信息,请参阅 Using Swift with Cocoa and Objective-C: Adopting Cocoa Patterns 的 "Key-Value Observing" 部分。
我有一个代表底层数组的 table 视图。这些单元格有一个标签和一个滑块,它应该显示数组的 percentage
属性 的值。
我想在百分比 属性 发生变化时使用键值观察来更新标签。 (我知道 KVO 在这个例子中有点矫枉过正,但最终滑动一个滑块会影响其他单元格,包括滑块的位置和底层数组将从应用程序中的多个位置随时设置,所以 KVO 是可行的方法。 )
我得到了很多帮助
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)
}
嗨@sean 问题在 UITableview 单元格中 class 你已经制作了 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()
}
}
要在 Swift 4 中执行 KVO,您必须将 属性 声明为 dynamic
并对该对象调用 observe(_:options:changeHandler:)
,保存结果 NSKeyValueObservation
令牌。当该令牌超出范围(或被另一个令牌替换)时,原始观察者将自动被删除。
在您的情况下,您让观察者调用委托,然后委托重新加载单元格。但是您似乎从来没有设置 delegate
属性,所以我怀疑没有调用该方法。
但这一切似乎都有些脆弱。我倾向于直接在观察者 changeHandler
中更新标签。我还认为您可以更直接地更新单元格(我将 "value changed" IBAction 放在单元格中,而不是 table 视图中),并消除 [=18 的相当尴尬的使用=] 以确定模型数组中的哪一行更新了滑块(如果插入或删除行,这可能会出现问题)。
所以考虑这个对象:
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()
}
}
然后您可以拥有一个 table 视图控制器,它使用此模型类型的实例填充 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: [=11=])!, 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
}
}
完成后,您现在可以为您的单元格设置基础 class (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
}
}
产生:
有关详细信息,请参阅 Using Swift with Cocoa and Objective-C: Adopting Cocoa Patterns 的 "Key-Value Observing" 部分。