使用计时器定期更新NSTableCellView内的文本字段

时间:2015-03-10 21:17:28

标签: swift timer grand-central-dispatch nstableview

我有一个由几个自定义NSTableCellView制作的tableView。有些视图必须与NSProgressIndicator一起显示一个计时器(已经过了多少时间)。

我创建了一个计时器(带有中央显示器),每100毫秒我用setNeedsDisplay更新textView(或者swift中的.needsDisplay = true)。

我在具有NSTextField的常规视图中工作的代码很好,但是一旦文本字段是NSTableCellView的一部分,它就无法正常工作。计时器触发,但不重绘该字段。

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

那么我应该如何每秒在整个tableview的几个单元格中重新加载一个特定的textField呢?

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

}

2 个答案:

答案 0 :(得分:6)

一般来说,重新加载单元格只是为了更改其中的一个元素,更不用说它的状态是非常糟糕的,因为如你所说,它会给状态带来很多问题,动画+它也会产生滚动问题和你的重新渲染。话虽如此,这肯定是最简单的解决方案。让我们尝试在编码的概念层面上做更多的事情,因为我可以清楚地看到你有能力用适当的方向自己编码

控制器与视图

虽然我在这里让自己处于困境,因为总会有人对此有不同的看法,我认为这是对视图和控制器的正确使用:

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

<强>解决方案

所以现在如果我们确定应该遵循的内容,我认为这是最好的解决方案:

  • 在控制器中创建一个计时器,让它无限期运行
  • 创建一个存储空间,当您滚动和更改数据时,您将动态添加和删除您感兴趣的单元格
  • 在每个计时器刻度上,运行整个存储,并使用单元格(setProgress :)上的方法使用适当的数据更新您感兴趣的单元格,这将更新进度和Lb

即使不使用GCD,此解决方案也可以正常工作。问题是如果你有太多的更新请求,它基本上会阻塞UI并且只在有时或者从不渲染它。为此,您应该在主线程上更新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
        })
    })

答案 1 :(得分:1)

如果您有一个文本字段,通过调用它的setNeedsDisplay方法来更新它是一件简单的事情。

但是,使用表格视图,不再有单个文本视图。你的每个单元都可能有自己的单元,你不应该直接在表视图中处理视图(因为它们可以在屏幕外滚动,循环显示其他索引路径的数据等)。

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

BTW,这与Core Animation无关。您应该从帖子中删除该标记。