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 委托?