为什么空drawRect会干扰简单的动画?

时间:2016-01-27 13:40:20

标签: cocoa animation core-animation

我试图将自己的看法放在Cocoa的NSPathControl上。

enter image description here

为了找出处理鼠标悬停在控件中的组件上时获得的扩展合约动画的最佳方法,我将一个非常简单的示例应用程序放在一起。最初事情进展顺利 - 鼠标进入或离开包含路径组件的视图,您可以使用以下代码获得简单的动画:

// This code belongs to PathView (not PathComponentView)

override func mouseEntered(theEvent: NSEvent) {

    NSAnimationContext.runAnimationGroup({ (ctx) -> Void in
        self.animateToProportions([CGFloat](arrayLiteral: 0.05, 0.45, 0.45, 0.05))
    }, completionHandler: { () -> Void in
            //
    })
}

// Animating subviews

func animateToProportions(proportions: [CGFloat]) {

    let height = self.bounds.height
    let width = self.bounds.width

    var xOffset: CGFloat = 0
    for (i, proportion) in enumerate(proportions) {
        let subview = self.subviews[i] as! NSView
        let newFrame = CGRect(x: xOffset, y: 0, width: width * proportion, height: height)
        subview.animator().frame = newFrame
        xOffset = subview.frame.maxX
    }
}

作为控件开发的下一步,我开始使用自己的NSView子类,而不是使用NSView实例作为我的路径组件:

class PathComponentView: NSView {

    override func drawRect(dirtyRect: NSRect) {
        super.drawRect(dirtyRect)
    }
}

虽然与NSView基本相同,但是当我在动画中使用此类时,动画不再符合它的预期 - 我为子视图设置的帧几乎被忽略了,整体效果很难看。如果我注释掉我的子类的drawRect:方法,事情就会恢复正常。任何人都可以解释出了什么问题吗?为什么drawRect:存在干扰动画?

我已在我的GitHub页面上放置demo project

1 个答案:

答案 0 :(得分:5)

你在这里违反了一些规则,当你违反规则时,行为就变得不明确了。

在图层支持的视图中,您不能直接修改图层。请参阅wantsLayer的文档(您指定它是如何支持图层的):

  

在图层支持的视图中,视图完成的任何绘图都会缓存到底层图层对象。然后,可以以比显式重绘视图内容更高效的方式操纵此缓存内容。 AppKit自动创建底层图层对象(使用makeBackingLayer方法)并处理视图内容的缓存。 如果wantsUpdateLayer方法返回NO,则不应直接与底层图层对象进行交互。相反,使用此类的方法对视图及其图层进行任何更改。如果wantsUpdateLayer返回YES,则可以接受(并且适当)修改视图的updateLayer方法中的图层。

你打这个电话:

subview.layer?.backgroundColor = color.CGColor

这是“直接与底层图层对象进行交互。”你不被允许这样做。当没有drawRect时,AppKit会跳过尝试重绘视图并只使用Core Animation来动画那里的动画,这会给你你所期望的东西(你在那里很幸运;它没有得到承诺,这将有效)。 / p>

但是当它看到你实际上实现了drawRect(它知道你做了,但它不知道里面是什么),那么它必须调用它来执行自定义绘图。您有责任确保drawRect填充矩形的每个像素。您不能依赖后备层为您执行此操作(这只是一个缓存)。你什么都没画,所以你看到的是默认背景灰色与缓存颜色混合。一旦我们走上这条道路,就没有承诺所有像素都会正确更新。

如果要操作图层,则需要“图层托管视图”而不是“图层支持的视图”。您可以通过将AppKit的缓存层替换为您自己的自定义图层来实现。

let subview = showMeTheProblem ? PathComponentView() : NSView()
addSubview(subview)
subview.layer = CALayer() // Use our layer, not AppKit's caching layer.
subview.layer?.backgroundColor = color.CGColor

虽然在这种情况下可能无关紧要,但请记住,图层支持的视图和图层托管视图具有不同的性能特征。在图层支持的视图中,AppKit将您的drawRect结果缓存到其缓存层,然后相当自动地应用动画(这允许在CALayer之前编写的现有AppKit代码获得一些不错的选择 - 性能改进而不会有太大变化)。在图层托管视图中,系统更像iOS。你没有得到任何神奇的缓存。只有一个图层,您可以在其上使用Core Animation。