Periodically update text field inside NSTableCellView using timer

I have a tableView made from several custom NSTableCellView. Some of the views should show a timer (how much time has passed) along with the NSProgressIndicator.

I created a timer (with a large central display) and every 100 ms I update the textView with setNeedsDisplay (or .needsDisplay = true in swift).

Code that works fine in a regular view using NSTextField, but does not work when the text field is part of NSTableCellView. The timer is triggered, but the field is not redrawn.

It also does not work if I reload the entire table every 100 ms from within the viewController. Rebooting the entire table was not a good solution anyway, because the selection is lost every 100 ms, and the user can no longer edit (regular) cells.

So, how should I reload one particular textField in several cells of the entire table view every second?

@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? 

}

+5
source share
2 answers

In general, itโ€™s a very bad idea to reload a cell to change one element in it, not to mention its state, because, as you said, it has a lot of problems with states, animation +, this also creates a scroll problem and your re-rendering . That being said, this is by far the easiest solution. Let's try to go a little at the conceptual level of coding, as I can clearly see that you are able to code it yourself in the right direction

Controllers vs. Views

While I am on thin ice here, because there will always be people who have a different opinion about this, here is what I consider the correct use of views and controllers:

  • Controllers in iOS are used to control and customize views. The controllers should not have network logic, etc., but it should interact with your model layer. He should act only as a decision maker on how to present the desired result to the user.
  • Contrary to this, Views are what you need to control. There should not be any logic that works with your application model. Submissions should be written so that they are as reusable as possible, unless they have good reason for them (a very specific component, etc.). They should have methods to change the properties of this view. You should not directly access the outputs in the cell (since they can change, names, etc., but the functionality will probably remain the same - if not, then there is no difference)
  • Views should contain drawing logic (it also includes fe. If you have a date format, it should be there).

Decision

So, if we establish what should be done, here is what I consider the best solution:

  • Create one timer in your controller and let it work indefinitely
  • Create a repository from which you will dynamically add and remove cells that interest you as you scroll and change your data.
  • At each tick tick, go through the entire repository and update the cells that interest you, using the correct data, using the methods on the cell (setProgress :), which will update both progress and Lb

This solution should work even without using a GCD. The problem is that if you have too many update requests, it can basically shut up the interface and display it only occasionally or maybe never. To do this, you need to update the interface in the main thread, but it is called in async mode, for example:

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

If you have one text field, just try updating it by calling the setNeedsDisplay method.

However, with the table view, there is no longer a single text view. It seems that each of your cells has its own, and you should not directly access the views in the table view (since they can scroll the screen off-screen, return to displaying data for other indexes, etc.).

What you need to do is update your model, and then either call reloadData in the entire table view or call reloadRowsAtIndexPaths:withRowAnimation: (If you want to reload the modified indexPaths.)

By the way, this has nothing to do with Core Animation. You must remove this tag from your post.

+1
source

Source: https://habr.com/ru/post/1215093/


All Articles