2015-03-10 10 views
5

Ho una tabellaView composta da diversi NSTableCellView personalizzati. Alcune delle viste devono mostrare un timer (quanto tempo è trascorso) insieme a NSProgressIndicator.aggiorna periodicamente il campo di testo all'interno di NSTableCellView con un timer

Ho creato un timer (con display centrale grand) e ogni 100 ms aggiorno il testoView con setNeedsDisplay (o .needsDisplay = true in swift).

Il codice che ho funziona correttamente in una vista normale con un NSTextField, ma non funziona una volta che il campo di testo è parte di un NSTableCellView. Il timer scatta ma il campo non viene ridisegnato.

Non funziona anche se ricarico l'intera tabella ogni 100 ms all'interno di viewController. Ricaricare l'intera tabella non è stata comunque una buona soluzione perché la selezione viene persa ogni 100 ms e l'utente non può più modificare le celle (regolari).

Quindi, come dovrei ricaricare un determinato campo di testo in poche celle di un'intera tableview ogni secondo?

@IBDesignable class ProgressCellView : NSTableCellView 
{ 
//MARK: properties 
@IBInspectable var clock : Bool = false //should this type of cell show a clock? (is set to true in interface builder) 

private lazy var formatter = TimeIntervalFormatter() //move to view controller?: 1 timer for the entire table => but then selection is lost 
private var timer : dispatch_source_t! 
private var isRunning : Bool = false 

//MARK: init 
override func awakeFromNib() 
{ 
    self.timeIndex?.formatter = formatter 
    if self.clock 
    { 
     self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue()) 
     dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, 0), 100 * NSEC_PER_MSEC, 50 * NSEC_PER_MSEC) //50 ms leeway, is good enough for the human eye 
     dispatch_source_set_event_handler(timer) { 
      self.timeIndex?.needsDisplay = true 
      //self.needsDisplay = true 
      //self.superview?.needsDisplay = true 
     } 
    } 

} 

//only run the clock when the view is visible 
override func viewWillMoveToSuperview(newSuperview: NSView?) 
{ 
    super.viewWillMoveToSuperview(newSuperview) 
    if self.clock && !isRunning { dispatch_resume(self.timer); isRunning = true } 
} 

override func removeFromSuperview() 
{ 
    super.removeFromSuperview() 
    if self.clock && isRunning { dispatch_suspend(self.timer); isRunning = false } 
} 

//MARK: properties 
override var objectValue: AnyObject? { 
    didSet { 
     let entry = self.objectValue as! MyEntry 
     self.progressBar?.doubleValue = entry.progress?.fractionCompleted ?? 0 

     self.timeIndex?.objectValue = entry.dateStarted   
    } 
} 

//MARK: user interface 
@IBOutlet var timeIndex  : NSTextField? 
@IBOutlet var progressBar : NSProgressIndicator? 

}

+0

Hai mai capito questo? – Matt

+0

Kinda. Ogni cella ha un timer (anche se preferirei avere 1 timer per l'intera tabella). Nel gestore del timer aggiorno il valore objectValue del campo di testo, invece di impostare needsDisplay su true. Un formattatore personalizzato mostra la quantità corretta di tempo trascorso. Se si dispone di un numero elevato di celle che devono essere aggiornate, ciò probabilmente causerà problemi a causa dell'elevato numero di timer (uno per ogni cella). Non è previsto nel mio caso, però. YMMV. Un timer sincronizzerebbe anche gli aggiornamenti, che dovrebbero apparire più belli. – user965972

risposta

6

In generale, è una pessima idea ricaricare la cella solo per cambiarne un elemento, per non parlare del suo stato, perché come hai detto, presenta molti problemi con gli stati, animazione + crea anche problemi con lo scrolling e il tuo re-rendering. Detto questo, è sicuramente la soluzione più semplice. Consente di cercare di andare poco più del livello concettuale della codifica, in quanto posso vedere chiaramente che siete in grado di codifica che da soli con direzione corretta

Controller vs. Visualizzazioni

Mentre mi sto lasciando su un ghiaccio sottile qui perché ci saranno sempre persone che hanno opinioni diverse su questo, ecco quello che penso sia un uso corretto di viste e controller:

  • I controller in iOS servono per controllare e configurare le viste. Nei controller, non dovrebbe esserci logica di rete, ecc., Ma dovrebbe comunicare con il tuo livello di modello. Dovrebbe servire solo come decisore su come presentare l'output desiderato dall'utente
  • Contrariamente a ciò, le viste sono ciò che deve essere controllato. Non ci dovrebbe essere alcuna logica che funzioni con il tuo modello di applicazione. Le viste dovrebbero essere scritte in modo da essere il più possibile riutilizzabili, a meno che non ci sia davvero una buona ragione per non farlo (componente molto specifico, ecc.). Questi dovrebbero avere metodi, che ti permettono di cambiare proprietà su quella vista. Non si dovrebbe accedere direttamente alle prese sulla cella (poiché queste possono cambiare, nomi, ecc., ma la funzionalità probabilmente rimarrà la stessa - in caso contrario, non vi è alcuna differenza)
  • Le viste dovrebbero contenere la logica di disegno (include anche fe. se si dispone di formattatore di date, dovrebbe essere lì).

Soluzione

Così ora se abbiamo stabilito che cosa dovremmo seguire, ecco quello che penso sarebbe la soluzione migliore:

  • Creare un timer nel controllore e farlo funzionare a tempo indeterminato
  • Crea una memoria da cui aggiungi e rimuovi dinamicamente le celle a cui sei interessato mentre scorri e mentre i tuoi dati cambiano
  • Su ogni tick timer, attraversano tutta stoccaggio e aggiornare le cellule che si sono interessati a dati appropriati utilizzando metodi sulla cella (setProgress :) che aggiornerà sia progresso e Lb

Questa soluzione dovrebbe funzionare anche senza uso di GCD. Il problema è che se hai troppe richieste di aggiornamento, può in pratica intasare l'interfaccia utente e renderla solo a volte, o forse mai. Per questo, devi aggiornare l'interfaccia utente sul thread principale, ma chiama in modalità asincrona, in questo modo:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), {() -> Void in 
     dispatch_async(dispatch_get_main_queue(), {() -> Void in 
      // Do update here 
     }) 
    }) 
+0

"livello concettuale di codifica". Mi piacciono questi tipi di risposte. Vorrei mettere in pausa il timer se non ci sono celle di interesse, però. – user965972

+0

Sì, hai assolutamente ragione. Anche se lo farei come un'ottimizzazione dopo aver saputo che questo funziona, un passo alla volta. –

+0

Cosa succede se i valori che devono essere mostrati/modificati sono associati ad altri oggetti? – Matt

1

Se si dispone di un unico campo di testo, è una questione semplice per farlo aggiornare invocando è il metodo setNeedsDisplay.

Tuttavia, con una vista tabella, non c'è più una singola vista di testo. Ognuna delle tue celle presumibilmente ha il suo, e non dovresti indirizzare direttamente le viste nella vista tabella (dato che possono scorrere fuori schermo, riciclare per visualizzare dati per altri indexPath, ecc.)

Cosa è necessario aggiornare il modello e chiamare il reloadData sull'intera vista tabella o chiamare reloadRowsAtIndexPaths:withRowAnimation: (se si desidera ricaricare solo i IndexPath modificati).

BTW, questo non ha nulla a che fare con Core Animation. Dovresti rimuovere quel tag dal tuo post.

+0

I dati sottostanti non sono stati modificati. La data di inizio fa parte del modello di dati e la cella mostra solo quanto tempo è trascorso. Anche ricaricare l'intera tabella non è un'opzione, in quanto la selezione viene persa. Ricaricare celle specifiche significa cercare l'origine dei dati per alcune celle interessate, mentre le stesse cellule sanno già se aggiornarsi o meno. – user965972

Problemi correlati