CALayer 委托仅在使用 Swift 时偶尔调用

Posted

技术标签:

【中文标题】CALayer 委托仅在使用 Swift 时偶尔调用【英文标题】:CALayer delegate is only called occasionally, when using Swift 【发布时间】:2014-11-21 05:05:44 【问题描述】:

我是 ios 和 Swift 的新手,所以我首先将 Apple 的 Accelerometer 示例代码移植到 Swift。

这一切都很简单。由于 Accelerometer API 已被弃用,我改用 Core Motion,它工作得很好。我也切换到了故事板。

我的问题是我的图层委托很少被调用。它会持续几分钟并且永远不会被调用,然后它会每秒被调用 40 次,然后又回到不被调用的状态。如果我进行上下文切换,将调用委托,并显示其中一个子层,但有 32 个子层,我还没有看到它们都被绘制出来。绘制的内容似乎很好 - 问题只是在我调用 setNeedsDisplay() 时让委托实际被调用,并让所有子层都被绘制。

我已检查以确保每个子图层具有正确的边界和框架尺寸,并且我已检查以确保在获取每个加速度计点后调用 setNeedsDisplay()。

如果我连接仪器,我会看到帧速率通常为零,但偶尔会更高。

我的猜测是运行循环没有循环。运行循环中实际上没有任何内容,我不知道该放在哪里。在 ViewDidLoad 委托中,我为加速度计设置了一个更新速率,并调用了一个函数来更新视图中的子层。其他一切都是事件驱动的,所以我不知道如何处理运行循环。

我尝试创建 CALayers,并将它们添加为子层。我还尝试将 GraphViewSegment 类设为 UIView,因此它有自己的图层。

用 Objective C 编写的版本完美可靠。

此应用程序的工作方式是加速度值显示在屏幕左侧,然后滚动到右侧。为了提高效率,新的加速度值被写入一个小的子层,其中包含 32 个时间值的图表。当它已满时,整个子层只是一次向右移动一个像素,而一个新的(或回收的)片段将在屏幕左侧占据它的位置。

以下是将未更改的段向右移动一个像素的代码:

    for s: GraphViewSegment in self.segments 
         var position = s.layer.position
        position.x += 1.0;
        s.layer.position = position;
        //s.layer.hidden = false
        s.layer.setNeedsDisplay()
    

我不认为 setNeedsDisplay 在这里是绝对必要的,因为当左边的线段获得新的线段时,它会被调用。

添加新图层的方法如下:

public func addSegment() -> GraphViewSegment 
    // Create a new segment and add it to the segments array.
    var segment = GraphViewSegment(coder: self.coder)
    // We add it at the front of the array because -recycleSegment expects the oldest segment
    // to be at the end of the array. As long as we always insert the youngest segment at the front
    // this will be true.
    self.segments.insert(segment, atIndex: 0)
 // this is now a weak reference

    // Ensure that newly added segment layers are placed after the text view's layer so that the text view
    // always renders above the segment layer.
    self.layer.insertSublayer(segment.layer, below: self.text.layer)
    // Position it properly (see the comment for kSegmentInitialPosition)
    segment.layer.position = kSegmentInitialPosition;
    //println("New segment added")
    self.layer.setNeedsDisplay()
    segment.layer.setNeedsDisplay()
    return segment;


在这一点上,我很困惑。我试过到处调用 setNeedsDisplay,包括拥有的 UIView。我试过制作子层 UIViews,我试过让它们不是 UIViews。不管我做什么,行为总是一样的。

一切都在 viewDidLoad 中设置:

override func viewDidLoad() 
    super.viewDidLoad()

    pause.possibleTitles?.setByAddingObjectsFromArray([kLocalizedPause, kLocalizedResume])
    isPaused = false
    useAdaptive = false
    self.changeFilter(LowpassFilter)

    var accelerometerQueue = NSOperationQueue()
    motionManager.accelerometerUpdateInterval = 1.0 / kUpdateFrequency 
    motionManager.startAccelerometerUpdatesToQueue(accelerometerQueue,
        withHandler:
            (accelerometerData: CMAccelerometerData!, error: NSError!) -> Void in
                self.accelerometer(accelerometerData))

    unfiltered.isAccessibilityElement = true
    unfiltered.accessibilityLabel = "unfiltered graph"
    filtered.isAccessibilityElement = true
    filtered.accessibilityLabel = "filtered graph"


func accelerometer (accelerometerData: CMAccelerometerData!) 
    if (!isPaused) 
        let acceleration: CMAcceleration = accelerometerData.acceleration
        filter.addAcceleration(acceleration)
        unfiltered!.addPoint(acceleration.x, y: acceleration.y, z: acceleration.z)
        filtered!.addPoint(filter.x, y: filter.y, z: filter.z)
        //unfiltered.setNeedsDisplay()
    

有什么想法吗?

我非常喜欢 Swift 作为一门语言——它吸收了 Java 和 C# 中最好的部分,并添加了一些不错的语法糖。但这让我很闲!我确定这是我忽略的一些小事,但我不知道是什么。

【问题讨论】:

【参考方案1】:

由于您为加速度计更新处理程序创建了一个新的NSOperationQueue,处理程序调用的所有内容也在一个单独的队列中运行,与主运行循环隔离。我建议要么在主线程 NSOperationQueue.mainQueue() 上运行该处理程序,要么通过主队列上的块将任何可以更新 UI 的东西移回主线程:

NSOperationQueue.mainQueue().addOperationWithBlock 
    // do UI stuff here

【讨论】:

我花了几天的时间来解决这个问题——但我错误地认为我的处理程序是在 mainThread 中执行的。你救了我剩下的头发。我现在获得了稳定的 60 fps。

以上是关于CALayer 委托仅在使用 Swift 时偶尔调用的主要内容,如果未能解决你的问题,请参考以下文章

有没有办法让 CALayer 的动作字典永久优先于其 UIView 委托?

为啥我得到一个仅在程序运行时偶尔出现的 NPE?

CALayer委托方法drawLayer没有被调用

如何仅在三个侧面设置 UITextField 边框/CALayer 边框 ios

缩放CALayer会使视图模糊

Swift 中的自定义 UI 元素:子类化 CALayer 并覆盖 drawInContext 导致像素化绘图