为什么一个空的drawRect干扰一个简单的动画? [英] Why is an empty drawRect interfering with a simple animation?

查看:325
本文介绍了为什么一个空的drawRect干扰一个简单的动画?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图把我自己的对Cocoa的 NSPathControl





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

  //这个代码属于PathView(不是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
//
})
}

//动画子视图

func animateToProportions(proportion:[CGFloat]){

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

var xOffset:CGFloat = 0
for(i,proportion)in enumerate(proportion){
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虽然本质上与 NSView 相同,但是它们可以使用




/ code>,当我在动画中使用这个类时,动画不再做它应该是什么 - 我为子视图设置的帧几乎被忽略,整个效果是丑陋的。如果我注释掉我的子类的 drawRect:方法返回正常。任何人都可以解释发生了什么问题?为什么存在 drawRect:干扰动画?



我已经把演示项目在我的GitHub页面。

解决方案<



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


在基于图层的视图中,由视图完成的任何绘图都将缓存到基础图层对象。然后可以以比显式地重新绘制视图内容更高性能的方式来操纵该高速缓存的内容。 AppKit自动创建底层对象(使用makeBackingLayer方法),并处理视图内容的缓存。 如果wantsUpdateLayer方法返回NO,您不应该直接与底层对象交互。 如果wantsUpdateLayer返回YES,那么在视图的updateLayer方法中修改图层是可以接受的(也是合适的)。


您进行此调用:

  subview.layer ?.backgroundColor = color.CGColor 

这是直接与底层对象交互。你不能这样做。当没有 drawRect 时,AppKit跳过尝试重绘视图,只是使用核心动画来动画什么,这给了你的期望(你只是在那里幸运;



但是当它看到你实际上实现了 drawRect (它知道你做了,但它不知道里面有什么),那么它必须调用它来执行自定义绘图。这是你的责任,确保 drawRect 填充矩形的每个像素。你不能依靠背层来为你做这些(这只是一个缓存)。你什么都不画,所以你看到的是默认的背景灰色与缓存的颜色混合。



如果你想操作图层,你需要一个layer-托管视图而不是基于层的视图。你可以用你自己的自定义图层替换AppKit的缓存图层。

  let subview = showMeTheProblem? PathComponentView():NSView()
addSubview(subview)
subview.layer = CALayer()//使用我们的图层,而不是AppKit的缓存图层。
subview.layer?.backgroundColor = color.CGColor

在这种情况下,请记住,基于层的视图和层主机视图具有不同的性能特性。在一个支持layer的视图中,AppKit正在缓存你的 drawRect 结果到它的缓存层,然后相当自动地应用动画(这允许现有的AppKit代码, code> CALayer 以获得一些不错的选择性能改进,而不改变太多)。在层主机视图中,系统更像是iOS。你没有得到任何神奇的缓存。只有一个层,你可以使用核心动画。


I'm trying to put together my own take on Cocoa's NSPathControl.

In a bid to figure out the best way to handle the expand-contract animation that you get when you mouse over a component in the control, I put together a very simple sample app. Initially things were going well - mouse into or out of the view containing the path components and you'd get a simple animation using the following code:

// 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
    }
}

As the next step in the control's development, instead of using NSView instances as my path components I started using my own NSView subclass:

class PathComponentView: NSView {

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

Although essentially identical to NSView, when I use this class in my animation, the animation no longer does what it's supposed to - the frames I set for the subviews are pretty much ignored, and the whole effect is ugly. If I comment out the drawRect: method of my subclass things return to normal. Can anyone explain what's going wrong? Why is the presence of drawRect: interfering with the animation?

I've put a demo project on my GitHub page.

解决方案

You've broken some rules here, and when you break the rules, behavior becomes undefined.

In a layer backed view, you must not modify the layer directly. See the documentation for wantsLayer (which is how you specify that it's layer-backed):

In a layer-backed view, any drawing done by the view is cached to the underlying layer object. This cached content can then be manipulated in ways that are more performant than redrawing the view contents explicitly. AppKit automatically creates the underlying layer object (using the makeBackingLayer method) and handles the caching of the view’s content. If the wantsUpdateLayer method returns NO, you should not interact with the underlying layer object directly. Instead, use the methods of this class to make any changes to the view and its layer. If wantsUpdateLayer returns YES, it is acceptable (and appropriate) to modify the layer in the view’s updateLayer method.

You make this call:

subview.layer?.backgroundColor = color.CGColor

That's "interact[ing] with the underlying layer object directly." You're not allowed to do that. When there's no drawRect, AppKit skips trying to redraw the view and just uses Core Animation to animate what's there, which gives you what you expect (you're just getting lucky there; it's not promised this will work).

But when it sees you actually implemented drawRect (it knows you did, but it doesn't know what's inside), then it has to call it to perform custom drawing. It's your responsibility to make sure that drawRect fills in every pixel of the rectangle. You can't rely on the backing layer to do that for you (that's just a cache). You draw nothing, so what you're seeing is the default background gray intermixed with cached color. There's no promise that all of the pixels will be updated correctly once we've gone down this road.

If you want to manipulate the layer, you want a "layer-hosting view" not a "layer-backed view." You can do that by replacing AppKit's cache layer with your own custom layer.

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

While it probably doesn't matter in this case, keep in mind that a layer-backed view and a layer-hosting view have different performance characteristics. In a layer-backed view, AppKit is caching your drawRect results into its caching layer, and then applying animations on that fairly automatically (this allows existing AppKit code that was written before CALayer to get some nice opt-in performance improvements without changing much). In a layer-hosting view, the system is more like iOS. You don't get any magical caching. There's just a layer, and you can use Core Animation on it.

这篇关于为什么一个空的drawRect干扰一个简单的动画?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆