Cómo actualizar UITableViewCells usando NSTimer y NSNotificationCentre en Swift

NOTA: pidiendo respuestas en Swift, por favor.

Lo que trato de hacer:

  • Mantenga las celdas de vista de tabla actualizadas cada 1 segundo y visualice una count regresiva en time real.

Cómo lo hago actualmente:

  • Tengo una vista de tabla con celdas que contienen una label.

  • Cuando se generan las celdas, llaman a una function que calcula el time entre hoy y una date objective almacenada, y muestra el time de count atrás restante en la label.

  • Para que las celdas actualicen la label cada segundo, uso un NSTimer que recarga la vista de tabla cada segundo.

  • PROBLEMA: La count regresiva funciona, pero cuando trato de hacer una acción Swipe to Delete, porque NSTimer la tableview, restablece la celda deslizada para que ya no se deslice y, por supuesto, esto no es aceptable para los usuarios finales.

Lo que estoy intentando / Soluciones potenciales

  • Escuché que una mejor manera de hacerlo es usar NSNotificationCenter que es desencadenado por NSTimer y agregar oyentes a cada celda. Cada 1 segundo se envía una "difusión" desde NSNotificationCenter , la célula recibirá la "difusión" y actualizará la label.

  • Intenté agregar un oyente a cada celda en la parte del código donde genera una nueva celda:

     // Generate the cells override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { var cell = tableView.dequeueReusableCellWithIdentifier("Tablecell", forIndexPath: indexPath) as UITableViewCell let countdownEntry = fetchedResultController.objectAtIndexPath(indexPath) as CountdownEntry // MARK: addObserver to "listen" for broadcasts NSNotificationCenter.defaultCenter().addObserver(self, selector: "updateCountdownLabel", name: mySpecialNotificationKey, object: nil) func updateCountdownLabel() { println("broadcast received in cell") if let actualLabelCountdown = labelCountdown { // calculate time between stonetworking date to today's date var countdownValue = CountdownEngine.timeBetweenDatesRealTime(countdownEntry.date) actualLabelCountdown.text = countdownValue } } 
  • pero el selector del observador solo parece ser capaz de dirigir las funciones en el nivel del controller de vista, no en la celda. (No puedo get "updateCountdownLabel" para disparar desde dentro de la celda. Cuando creo una function del mismo nombre en el nivel de controller de vista, se puede llamar a la function)

Preguntas

  • Entonces mi pregunta es: ¿es este el enfoque correcto?
  • En caso afirmativo, ¿cómo puedo hacer que el oyente dispare una function en el nivel de celda?
  • Si no, entonces, ¿cuál es la mejor manera de lograr esta count regresiva en time real sin interrumpir las acciones de Swipe on Cell?

¡Gracias de antemano por tu ayuda!

Podría ser una solución que no recargue células en absoluto. Solo haga que las celdas escuchen una notificación de actualización y cambien su label en consecuencia. Asumo que la subclass UITableViewCell y le doy a la celda una propiedad de date de stonetworkingDate . Configurarás esa propiedad al preparar la celda.

El timer acaba de disparar la notificación.

Recuerde dealloc el logging de la celda del centro de notifications en dealloc

Aquí hay un rápido y sucio ejemplo.

El controller de vista que contiene su TableView:

 class ViewController: UIViewController, UITableViewDataSource { @IBOutlet weak var tableView: UITableView! var timer: NSTimer! //MARK: UI Updates func fireCellsUpdate() { let notification = NSNotification(name: "CustomCellUpdate", object: nil) NSNotificationCenter.defaultCenter().postNotification(notification) } //MARK: UITableView Data Source func numberOfSectionsInTableView(tableView: UITableView) -> Int { return 1 } func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 10 } func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cellIdentifier = "CustomCell" let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as! CustomTableViewCell cell.timeInterval = 20 return cell } //MARK: View Lifecycle override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. self.timer = NSTimer(timeInterval: 1.0, target: self, selector: Selector("fireCellsUpdate"), userInfo: nil, repeats: true) NSRunLoop.currentRunLoop().addTimer(self.timer, forMode: NSRunLoopCommonModes) } deinit { self.timer?.invalidate() self.timer = nil } } 

La subclass de celda personalizada:

 class CustomTableViewCell: UITableViewCell { @IBOutlet weak var label: UILabel! var timeInterval: NSTimeInterval = 0 { didSet { self.label.text = "\(timeInterval)" } } //MARK: UI Updates func updateUI() { if self.timeInterval > 0 { --self.timeInterval } } //MARK: Lifecycle override func awakeFromNib() { super.awakeFromNib() // Initialization code let notificationCenter = NSNotificationCenter.defaultCenter() notificationCenter.addObserver(self, selector: Selector("updateUI"), name: "CustomCellUpdate", object: nil) } deinit { NSNotificationCenter.defaultCenter().removeObserver(self) } } 

Estoy bastante seguro de que este ejemplo no se adhiere a la lógica de su aplicación. Solo muestra cómo las cosas brillan juntas.

Como sugirió Ian MacDonald, debes evitar volver a cargar la celda cuando el timer se active, si estás deslizando. También suelte la NSNotification, ya que It and timer esencialmente están haciendo lo mismo