2016-05-29 4 views
4

Muss ich einzelne Elemente eines Arrays abonnieren/abbestellen?Wie beobachtet man einzelne Array-Element-Änderungen (Update) mit Swift und KVO?

Ich möchte jede Tabellenansichtszelle einzeln aktualisieren, um Änderungen im Hintergrundarray zu reflektieren. Mit Änderungen meine ich keine Append/Remove-Operationen, sondern die Aktualisierung der Eigenschaften der Objekte des Arrays. Ich hoffe, ich konnte erklären, was ich erreichen möchte. Danke

Antwort

8

KVO zu verwenden, erklärt das Modellobjekt mit dynamic Eigenschaften:

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

Dann lassen Sie die Zelle Prozess der KVO. Zuerst würde ich ein Protokoll haben, mit dem die Zelle der Tabellen-Ansicht informieren kann, dass sie neu geladen werden muss:

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

Und der Tisch-View-Controller zu diesem CustomCellDelegate Protokoll anpassen kann und die Zelle neu zu laden, wenn informiert braucht es zu:

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

So, und dann definieren Zelle zum Einrichten und Umgang mit KVO. In Swift 4 und iOS 11, können Sie die Verschlussbasis observe Verfahren mit den neuen stark typisierte Tasten verwenden:

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

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

} 

Und jetzt kann die cellForRowAtIndexPath gesetzt nur die delegate und object Eigenschaften:

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 
} 

meiner Meinung nach ist dies KVO Mechanismus ist besser als der Swift Beobachter-Mechanismus, da t Das Modellobjekt muss nichts über den Beobachter wissen (und sollte es auch nicht). Es muss nur angegeben werden, dass es dynamischen Versand unterstützt, und das ist es.

Für Swift 2 Wiedergabe der oben genannten, siehe previous revision of this answer.

+0

Danke Rob. Ich mochte deine Herangehensweise sehr. – mra214

+0

What's & observerContext in diesem Fall und woher kommt es? – Eden

+1

@Eden - Der 'observeValue (forKeyPath: of: change: context:)' benötigt einen "Kontext", um zu wissen, ob diese Benachrichtigung beobachtet wird oder etwas, das seine 'Super'-Klasse beobachtet. Daher geben wir einen eindeutigen "Kontext" an, der beim Erstellen des Beobachters verwendet und bei der Verarbeitung der Benachrichtigung überprüft wird. Die Standardmethode dafür ist, eine Variable zu definieren, in diesem Fall 'observerContext', und ihre Speicheradresse als diese eindeutige Kennung zu verwenden. Es ist ein übliches Muster, aber ich entschuldige mich, wenn es nicht offensichtlich war. Ich habe es oben hinzugefügt (sowie die einfachere blockbasierte Swift 4, iOS 11 Version). – Rob

1

Sie können einen Delegaten verwenden, um die Kommunikation zwischen den Daten der einzelnen Zelle (s, Elemente Ihres Arrays) und dem Besitzer dieser Zellen (d. H. Besitzer des Arrays) zu behandeln. Änderungen im "backing array" können an das Array propagiert werden, in dem Delegate-Callbacks mit entsprechenden Informationen verwendet werden.

Z. B .:

/* 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 
    } 
} 

Beispiel Nutzung:

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

Sie Setter Beobachter gespeicherten Variablen in dem Träger Array nutzen könnten.

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

wenn Sie den Wert in array ändern die willSet und didSet ausgeführt wird, und Sie können wollen tun das Update eine Funktion auf der Zelle nennen.

Verwandte Themen