如何防止可可在每个完全不可见的子视图上调用drawRect [英] How do I prevent Cocoa from calling drawRect on every completely invisible sub-view

查看:67
本文介绍了如何防止可可在每个完全不可见的子视图上调用drawRect的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个可以正常运行,非常快速的单窗口应用程序,它是用Objective-C编写的,最初是使用XCode 4在MacOS 10.7上开发的.它具有一个滚动视图,该视图显示了一个带有多个子视图的大型内容视图的各个部分.每个子视图仅显示一部分,只有最初可见的子视图才在应用程序显示初始窗口时调用其 -drawRect 方法.在应用程序启动时(不是从xib文件中)以编程方式创建正在滚动的内容视图及其所有子视图和子子视图.

I have a completely working, very fast, single window app, written in Objective-C and originally developed on MacOS 10.7 using XCode 4. It has a single scroll view showing portions of one large content view with many sub-views. Only a portion of each sub-view shows, and only that sub-view that is initially visible gets its -drawRect method called when the app shows the initial window. The content view being scrolled and all of its sub-views and sub-sub-views are created programmatically when the app launches (not from the xib file).

每个子视图通常具有各种控件和一个特殊的子类子视图,这些子视图显示计算密集型图形,该图形在其 -drawRect 方法首次被使用时会预先计算并缓存叫.因此,滚动浏览所有这些内容将分配计算和缓存,直到用户需要查看它时为止.这一切都很好,并且已经做了很多年了.直到子子视图的一部分在滚动过程中在层次结构中变得可见之前,才不会调用任何子子视图的 -drawRect 方法.

Each sub-view usually has a variety of controls and one special sub-classed sub-sub-view that displays a computationally intensive drawing that gets precomputed and cached the first time its -drawRect method is called. So scrolling through all this distributes the computation and caching until just when the user needs to see it. This all works well and has done so for years. None of the sub-sub-views' -drawRect methods is ever called until the sub-sub-view has a portion of itself that becomes visible within the hierarchy during scrolling.

问题在于,我现在已将该应用程序移植到XCode 9.3和MacOS 10.13.4,并发现在桌面上启动时首次显示带有滚动视图的单个窗口之后,每个子视图具有其 -drawRect 方法,可可运行时会调用该方法,即使 none 子视图在空间上甚至在视图中不可见窗口中滚动视图内部的层次结构.这将导致每个子子视图都在用户可以与刚刚启动的应用程序进行交互之前进行预先计算和缓存.这需要很多秒钟,这是用户界面无法接受的.

The problem is that I've now ported the app to XCode 9.3 and MacOS 10.13.4, and have discovered that after the single window with the scrolling view in it is shown for the first time at launch on the desktop, every sub-sub-view is having its -drawRect method called by Cocoa's runtime, even though none of the sub-sub-views are even spatially close to being visible in the view hierarchy inside the scrolling view in the window. This causes every one of the sub-sub-views to pre-compute and cache, all before the user can interact with the just-launched app. This takes many seconds, which is unacceptable user-interface-wise.

我不知道我未更改的代码可能会做什么,以导致要求所有不可见的子子视图进行绘制,因此,看来这种新情况可能是由于某些后来的Apple框架行为更改所致.我尝试查询 -drawRect 内子视图的 -isHiddenOrHasHiddenAncestor 属性,但是它总是返回false,这可能是因为超级视图在逻辑上是可见的但被滚动视图剪裁.

I have no idea what my unchanged code might be doing to cause all the invisible sub-sub-views to be asked to draw themselves, and so it seems this new state of affairs could be due to some later Apple framework behavior changes. I've tried querying the -isHiddenOrHasHiddenAncestor property of the sub-sub-view inside -drawRect, but it is always returning false, probably because the super-views are logically visible but being clipped by the scrolling view.

如何调试这种情况,这似乎与视图系统应该自动执行的工作完全相反?当视图的框架完全无法显示任何内容时,系统在什么情况下调用视图的 -drawRect ?苹果框架方面发生了什么变化?

How does one debug this situation, which seems completely contrary to what the view system is supposed to be doing automatically? Under what circumstances does the system call a view's -drawRect when the view's frame is completely unable to show anything? Did something change on Apple's framework side?

推荐答案

这可能是由于响应滚动"所致,它是10.9中引入的,并且仅针对与10.8 SDK或更高版本链接的应用程序启用.最好的综合文档可能在 10.9 AppKit中发布说明.一定还要仔细阅读文档中的透支"部分,这(如果我是对的话)是要求绘制不可见视图的具体原因.

This may be due to "responsive scrolling", which was introduced in 10.9 and is only enabled for apps linked against the 10.8 SDK or later. The best consolidated documentation is probably in the 10.9 AppKit release notes. Be sure to read the section on "overdraw" a bit down the document, too, which (if I'm right) is the specific cause of your invisible views being asked to draw.

防止这种情况的快速但肮脏的方法是让其中一个涉及的视图的类覆盖 + isCompatibleWithResponsiveScrolling 以返回false.

The quick-but-dirty way to prevent this is to have the class of one of the involved views override +isCompatibleWithResponsiveScrolling to return false.

但是,最好在后台线程上异步进行计算.在计算完成之前,您的视图将绘制一些便宜的占位符(或什么也没有).完成后,您将其标记为需要显示.(请确保将所有UI工作从后台线程分流回主线程.)

But, it may be better to do the computation asynchronously on a background thread. Your view would draw some cheap placeholder (or nothing) until the computation is completed. At completion, you'd mark it as needing display. (Be sure to shunt all UI work from the background thread back to the main thread.)

您还可以推迟子视图(或子子视图)的创建,并根据需要使用 -prepareContentInRect:的重写.或使用该方法的替代来禁用过度绘制,如发行说明中所述.

You could also defer the creation of your subviews (or sub-subviews) and use an override of -prepareContentInRect: to create them as necessary. Or use an override of that method to disable overdraw as described in the release notes.

这篇关于如何防止可可在每个完全不可见的子视图上调用drawRect的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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