Swift 使用计时器定期更新NSTableCellView中的textfield

Swift 使用计时器定期更新NSTableCellView中的textfield,swift,timer,grand-central-dispatch,nstableview,Swift,Timer,Grand Central Dispatch,Nstableview,我有一个由几个定制的NSTableCellView组成的tableView。有些视图必须与NSPROGRESS指示器一起显示计时器(经过了多少时间) 我创建了一个计时器(带有大中央显示屏),每隔100毫秒,我就会用setNeedsDisplay(或.needsDisplay=true)更新textView 我的代码在带有NSTextField的常规视图中运行良好,但一旦textfield成为NSTableCellView的一部分,它就不起作用了。计时器触发,但字段未重新绘制 如果我从viewCo

我有一个由几个定制的NSTableCellView组成的tableView。有些视图必须与NSPROGRESS指示器一起显示计时器(经过了多少时间)

我创建了一个计时器(带有大中央显示屏),每隔100毫秒,我就会用setNeedsDisplay(或.needsDisplay=true)更新textView

我的代码在带有NSTextField的常规视图中运行良好,但一旦textfield成为NSTableCellView的一部分,它就不起作用了。计时器触发,但字段未重新绘制

如果我从viewController中每隔100毫秒重新加载整个表,它也不起作用。在任何情况下,重新加载整个表都不是一个好的解决方案,因为每100毫秒就会丢失一次选择,用户无法再编辑(常规)单元格

那么,我应该如何每秒在整个tableview的几个单元格中重新加载一个特定的文本字段呢

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

}

如果您只有一个文本字段,那么通过调用它的setNeedsDisplay方法来更新它就很简单了

但是,对于表视图,不再有单个文本视图。您的每个单元格可能都有自己的单元格,并且您不应该直接在表视图中寻址视图(因为它们可以滚动到屏幕外,可以循环使用以显示其他IndExpath的数据,等等)

您应该做的是更新模型,然后在整个表视图上调用reloadData,或者调用
reloadRowsAtIndexPaths:withRowAnimation:
(如果您只想重新加载已更改的indexPaths)


顺便说一句,这与核心动画无关。您应该从帖子中删除该标记。

一般来说,只为了更改单元格中的一个元素而重新加载单元格是一个非常糟糕的主意,更不用说它的状态了,因为正如您所说的,它会在状态、动画方面出现很多问题。它还会在滚动和重新渲染方面产生问题。尽管如此,这肯定是最简单的解决办法。让我们试着在编码的概念层次上做更多的探讨,因为我可以清楚地看到,您能够自己按照正确的方向编码

控制器与视图的对比

虽然我在这里让自己如履薄冰,因为总会有人对此持有不同意见,但以下是我认为正确使用视图和控制器的方法:

  • iOS中的控制器用于控制和配置视图。在控制器中,不应该有网络逻辑等,但它应该与模型层通信。它只能作为一个决策者,决定如何呈现用户所需的输出
  • 与此相反,应该控制的是观点。应用程序模型不应该有任何逻辑。视图的编写应该使它们尽可能可重用,除非有很好的理由不这样做(非常具体的组件等)。这些应该具有允许您更改该视图属性的方法。您不应该直接访问单元上的插座(因为这些插座可能会更改、名称等,但功能可能会保持不变-如果没有,则没有区别)
  • 视图应该包含绘图逻辑(它还包括fe。如果您有日期格式化程序,它应该在那里)
解决方案

因此,如果我们确定了应该遵循的原则,我认为以下是最佳解决方案:

  • 在控制器中创建一个计时器,并让它无限期运行
  • 创建一个存储,在滚动和数据更改时,您可以从中动态添加和删除感兴趣的单元格
  • 在每个计时器滴答声中,运行整个存储器,并使用cell上的方法(setProgress:)使用适当的数据更新感兴趣的单元格,该方法将同时更新进度和Lb
即使不使用GCD,此解决方案也应有效。问题是,如果有太多的请求需要更新,它基本上会阻塞用户界面,并且只在某些时候呈现,或者永远不会呈现。为此,您应该在主线程上更新UI,但在异步模式下调用,如下所示:

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

基础数据未更改。开始日期是数据模型的一部分,单元格只显示经过了多少时间。另外,重新加载整个表也不是一个选项,因为这样会丢失选择。重新加载特定单元格意味着在数据源中搜索一些受影响的单元格,而单元格本身已经知道是否应该更新自己。你有没有想过?有点。每个单元格都有一个计时器(尽管我希望整个表都有一个计时器)。在计时器处理程序中,我更新文本字段的objectValue,而不是将needsDisplay设置为true。自定义格式设置程序显示正确的传递时间。如果有大量单元格需要更新,这可能会由于大量计时器(每个单元格一个)而导致问题。不过,在我的情况下,这是不可能的。YMMV。一个定时器也可以同步更新,这样看起来会更好。我喜欢这些类型的答案。如果没有感兴趣的单元格,我会暂停计时器。是的,你说得绝对正确。虽然我会在你知道这是可行的之后,一步一步地进行优化。如果需要显示/更改的值绑定到其他对象怎么办?Matt,你的单元格保留了对具有相关信息的对象的引用。TableviewController(TVC)跟踪需要定期更新的单元格。当更新时间到来时,TVC向每个受影响的单元发送重新加载消息。在单元格的重新加载功能中,您不会对单元格本身调用“setNeedsDisplay”。相反,单元格通过引用的对象获取数据,并使用该对象设置标签或其他单元格项的值。是的,在这种情况下,tableCellView承担一些控制器的责任(在MVC模型中)