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

Pau*_*son 1 cocoa animation core-animation

我正在努力把我自己的想法放在Cocoa上NSPathControl.

在此输入图像描述

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

// 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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

class PathComponentView: NSView {

    override func drawRect(dirtyRect: NSRect) {
        super.drawRect(dirtyRect)
    }
}
Run Code Online (Sandbox Code Playgroud)

虽然基本相同NSView,当我在动画中使用这个类时,动画不再按照它应该做的那样 - 我为子视图设置的帧几乎被忽略了,整个效果很难看.如果我注释掉drawRect:我的子类的方法,事情就会恢复正常.任何人都可以解释出了什么问题吗?为什么存在drawRect:干扰动画?

我在GitHub页面上放了一个演示项目.

Rob*_*ier 5

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

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

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

你打这个电话:

subview.layer?.backgroundColor = color.CGColor
Run Code Online (Sandbox Code Playgroud)

这就是"直接与底层图层对象进行交互." 你不被允许这样做.如果没有drawRect,AppKit会跳过尝试重绘视图,只是使用Core Animation来动画那里的动画,这会给你你所期望的(你在那里很幸运;它没有得到承诺,这将有效).

但当它看到你实际实现时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
Run Code Online (Sandbox Code Playgroud)

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