iOS 12, Xcode 10: UIView setNeedsDisplay(_:) 好像坏了
iOS 12, Xcode 10: UIView setNeedsDisplay(_:) seems to be broken
更新到 Xcode10 后,我意识到在我的应用程序中我的自定义 UIView(class 派生自 UIView)的 draw(_ rect: CGRect)
例程被错误地调用 rect
.事实上,它总是被称为 rect
作为底层 UIView 的完整框架,而不是 setNeedsDisplay(_ rect: CGRect)
指定的 rect
。
这是一个可以 运行 作为游乐场的代码片段,它至少在我的设置中显示了上述在简约设置中的错误行为:
import Foundation
import UIKit
import PlaygroundSupport
class CustomView: UIView {
override func draw(_ rect: CGRect) {
print("rect = \(rect)")
}
}
let customView = CustomView(frame: CGRect(origin: CGPoint.zero, size: CGSize(width: 200.0, height: 200.0)))
PlaygroundPage.current.liveView = customView
print("test")
customView.setNeedsDisplay(CGRect(origin: CGPoint.zero, size: CGSize(width: 100.0, height: 100.0)))
我得到的输出是
rect = (0.0, 0.0, 200.0, 200.0)
test
rect = (0.0, 0.0, 200.0, 200.0)
rect 的第一个打印输出是视图的标准完全重绘,但打印 "test" 后的第二个输出出现问题。由于之前调用 customView.setNeedsDisplay
而重绘的输出应该是较小的指定矩形 (0.0, 0.0, 100.0, 100.0)
.
所以我的明显问题是:
- 你能重现这个行为吗?
- 我是不是遗漏了什么明显的东西?
- 这是一个错误吗?
我在 Xcode 9、10 和 10.1 中对此进行了测试。
行为在 iOS 11 和 iOS 12 / 12.1
之间肯定发生了变化
文档或头文件中没有迹象表明这是有意的。
在我看来像是一个错误。
这实际上是为了 iOS 12 的新动态后备存储功能而设计的。
什么是后备存储
后备存储用于存储绘制的视图,需要为此分配内存。该内存量取决于视图的大小,因为它本质上是颜色和像素之间的映射。
如果您要绘制灰度图像,但已为宽色域分配了内存,那么这将导致大量空分配内存(灰度比 RGBA 占用空间小)。为了解决这个问题,动态后备存储功能通过绘制视图的全部内容来工作,然后计算出它需要多少内存,而不是从一开始就假设所有内容都需要广泛的颜色支持。
这样做的连锁反应是您无法重新绘制视图的较小子部分,因为这可能会改变这家商店。
如何绕过它
这是一个很棒的新功能,但如果您确实需要解决它,您可以在您的视图中禁用动态后备存储。您这样做的方法是显式设置视图 layer
.
的 contentsFormat
属性
灰度、RGBA 8 位和 RGBA 16 位(广色)有 3 个选项供您选择
所以只要打电话:
layer.contentsFormat = .RGBA16Float
并且您的 setNeedsDisplay(_ rect: CGRect)
将再次按预期开始工作
您可以在此处阅读 属性:https://developer.apple.com/documentation/quartzcore/calayer/1792104-contentsformat
还有来自 WWDC 18 的精彩演讲解释了新的动态后备存储并(非常安静地)提到了这项技术
https://developer.apple.com/videos/play/wwdc2018/219/?time=1451
更新到 Xcode10 后,我意识到在我的应用程序中我的自定义 UIView(class 派生自 UIView)的 draw(_ rect: CGRect)
例程被错误地调用 rect
.事实上,它总是被称为 rect
作为底层 UIView 的完整框架,而不是 setNeedsDisplay(_ rect: CGRect)
指定的 rect
。
这是一个可以 运行 作为游乐场的代码片段,它至少在我的设置中显示了上述在简约设置中的错误行为:
import Foundation
import UIKit
import PlaygroundSupport
class CustomView: UIView {
override func draw(_ rect: CGRect) {
print("rect = \(rect)")
}
}
let customView = CustomView(frame: CGRect(origin: CGPoint.zero, size: CGSize(width: 200.0, height: 200.0)))
PlaygroundPage.current.liveView = customView
print("test")
customView.setNeedsDisplay(CGRect(origin: CGPoint.zero, size: CGSize(width: 100.0, height: 100.0)))
我得到的输出是
rect = (0.0, 0.0, 200.0, 200.0)
test
rect = (0.0, 0.0, 200.0, 200.0)
rect 的第一个打印输出是视图的标准完全重绘,但打印 "test" 后的第二个输出出现问题。由于之前调用 customView.setNeedsDisplay
而重绘的输出应该是较小的指定矩形 (0.0, 0.0, 100.0, 100.0)
.
所以我的明显问题是:
- 你能重现这个行为吗?
- 我是不是遗漏了什么明显的东西?
- 这是一个错误吗?
我在 Xcode 9、10 和 10.1 中对此进行了测试。
行为在 iOS 11 和 iOS 12 / 12.1
之间肯定发生了变化文档或头文件中没有迹象表明这是有意的。
在我看来像是一个错误。
这实际上是为了 iOS 12 的新动态后备存储功能而设计的。
什么是后备存储
后备存储用于存储绘制的视图,需要为此分配内存。该内存量取决于视图的大小,因为它本质上是颜色和像素之间的映射。
如果您要绘制灰度图像,但已为宽色域分配了内存,那么这将导致大量空分配内存(灰度比 RGBA 占用空间小)。为了解决这个问题,动态后备存储功能通过绘制视图的全部内容来工作,然后计算出它需要多少内存,而不是从一开始就假设所有内容都需要广泛的颜色支持。
这样做的连锁反应是您无法重新绘制视图的较小子部分,因为这可能会改变这家商店。
如何绕过它
这是一个很棒的新功能,但如果您确实需要解决它,您可以在您的视图中禁用动态后备存储。您这样做的方法是显式设置视图 layer
.
contentsFormat
属性
灰度、RGBA 8 位和 RGBA 16 位(广色)有 3 个选项供您选择
所以只要打电话:
layer.contentsFormat = .RGBA16Float
并且您的 setNeedsDisplay(_ rect: CGRect)
将再次按预期开始工作
您可以在此处阅读 属性:https://developer.apple.com/documentation/quartzcore/calayer/1792104-contentsformat
还有来自 WWDC 18 的精彩演讲解释了新的动态后备存储并(非常安静地)提到了这项技术
https://developer.apple.com/videos/play/wwdc2018/219/?time=1451