2016-05-29 3 views
4

Нужно ли подписаться/отказаться от подписки на отдельные элементы массива?Как наблюдать изменения отдельных элементов массива (обновление) с помощью Swift и KVO?

Я хочу обновить каждую ячейку представления таблицы индивидуально, чтобы отразить изменения в массиве поддержки. Под изменениями я имею в виду не добавлять/удалять операции, а обновлять свойства объектов массива. Надеюсь, я смог объяснить, чего я хочу достичь. Thanks

ответ

8

Чтобы использовать KVO, объявить объект модели с dynamic свойствами:

class Foo: NSObject { 
    @objc dynamic var bar: String // in Swift 3, `@objc` is not necessary; in Swift 4 we must make this explicit 

    init(bar: String) { 
     self.bar = bar 
     super.init() 
    } 
} 

Затем, пусть процесс соты KVO. Во-первых, я должен был бы протокол, по которому клетка может сообщить вид таблицы, что она должна быть перезагружена:

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

И контроллер вид таблицы может соответствовать этому CustomCellDelegate протокола и перезагрузить ячейку, когда сообщили, что нужно до:

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

Итак, а затем определите ячейку для настройки и управления KVO. В Swift 4 и прошивкой 11, вы можете использовать закрывающий на основе observe метод с новыми сильно типизированных клавиш:

class CustomCell: UITableViewCell { 

    weak var delegate: CustomCellDelegate? 

    private var token: NSKeyValueObservation? 

    var object: Foo? { 
     willSet { 
      token?.invalidate() 
     } 
     didSet { 
      textLabel?.text = object?.bar 
      token = object?.observe(\.bar) { [weak self] object, change in 
       if let cell = self { 
        cell.delegate?.didUpdateObject(for: cell) 
       } 
      } 
     } 
    } 
} 

В Swift 3:

class CustomCell: UITableViewCell { 

    private var observerContext = 0 

    weak var delegate: CustomCellDelegate? 

    var object: Foo? { 
     willSet { 
      object?.removeObserver(self, forKeyPath: #keyPath(Foo.bar), context: &observerContext) 
     } 
     didSet { 
      textLabel?.text = object?.bar 
      object?.addObserver(self, forKeyPath: #keyPath(Foo.bar), context: &observerContext) 
     } 
    } 

    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 
     guard context == &observerContext else { 
      super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) 
      return 
     } 

     delegate?.didUpdateObject(for: self) 
    } 

    deinit { 
     object?.removeObserver(self, forKeyPath: #keyPath(Foo.bar), context: &observerContext) 
    } 

} 

А теперь cellForRowAtIndexPath можно просто установить delegate и object свойства:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! CustomCell 

    cell.delegate = self 
    cell.object = objects![indexPath.row] 

    return cell 
} 

на мой взгляд, этот механизм KVO лучше, чем Swift механизм наблюдателя, потому что т он не должен знать (и не должен) ничего о наблюдателе. Просто нужно указать, что он поддерживает динамическую отправку, и все.

Для ознакомления с Свифт 2, см. previous revision of this answer.

+0

Спасибо, Роб. Мне очень понравился ваш подход. – mra214

+0

Что такое & observerContext в этом случае и откуда оно взялось? – Eden

+1

@Eden - 'observValue (forKeyPath: of: change: context:)' нуждается в некотором «контексте», чтобы узнать, наблюдает ли это уведомление или что-то, что его класс 'super' наблюдает. Поэтому мы указываем уникальный «контекст», используемый при создании наблюдателя, и проверяем при обработке уведомления. Стандартный способ сделать это - определить некоторую переменную, в данном случае 'observerContext', и использовать ее адрес памяти в качестве этого уникального идентификатора. Это обычная картина, но я извиняюсь, если это не очевидно. Я добавил его выше (а также предлагаю упрощенную версию SWIFT 4, iOS 11). – Rob

1

Вы можете использовать делегат для обработки связи между данными отдельной ячейки: (например, элементами вашего массива) и владельцем этих ячеек (то есть владельцем массива). Изменения в «массиве поддержки» могут быть переданы в массив, который используется с помощью обратных вызовов делегатов с соответствующей информацией.

т.д .:

/* delegate protocol blueprinting delegate callback methods */ 
protocol MyDelegate: class { 
    func arrayEntryUpdated(element: Foo) 
} 

/* lets assume your table view cells data source are Foo objects */ 
class Foo { 
    var id: Int = -1 
    private var bar : String = "" { 
     didSet { 
      /* notify the delegate each time the bar 
       property of a Foo object is set */ 
      delegate?.arrayEntryUpdated(self) 
     } 
    } 

    weak var delegate: MyDelegate? 
} 

/* you can then hande individual updates of the Foo objects via 
    delegate callbacks to the owner of the Foo array */ 
class MyArrayOwningClass: MyDelegate { 
    var fooArr : [Foo] 

    init(fooArr: [Foo]) { 
     self.fooArr = fooArr 
     self.fooArr.enumerate().forEach { $1.id = $0; $1.delegate = self } 
    } 

    // MyDelegate 
    func arrayEntryUpdated(element: Foo) { 
     print("Foo element of id #\(element.id) updated.") 
      // ... in your case, handle individual cell updating 
    } 
} 

Пример использования:

let fooArr = [Foo(), Foo(), Foo()] // "data source" 
let owner = MyArrayOwningClass(fooArr: fooArr) 

// update an element of the data source 
fooArr[1].bar = "bar" // via delegate: "Foo element of id #1 updated." 
1

Вы можете использовать сеттер наблюдатель от сохраненных переменных в массиве основы.

var s = "myString" { 
    willSet { 
     // do the update on the cell here with newValue 
    } 
    didSet { 
     // do something with oldValue 
    } 
} 
var array: [String] = [] 
array.append(s) 

при изменении значения в array willSet и didSet выполняются, и вы можете вызвать функцию клетки делают обновление, которое требуется.

Смежные вопросы