每次显示视图时,UIView层的子层显示都不同/随机 [英] UIView layer's sublayers display differently/randomly each time view appears
问题描述
我有一个简单的自定义CALayer,可在UIView上创建覆盖渐变效果。下面是代码:
I have a simple custom CALayer to create an overlaying gradient effect on my UIView. Here is the code:
class GradientLayer: CALayer {
var locations: [CGFloat]?
var origin: CGPoint?
var radius: CGFloat?
var color: CGColor?
convenience init(view: UIView, locations: [CGFloat]?, origin: CGPoint?, radius: CGFloat?, color: UIColor?) {
self.init()
self.locations = locations
self.origin = origin
self.radius = radius
self.color = color?.CGColor
self.frame = view.bounds
}
override func drawInContext(ctx: CGContext) {
super.drawInContext(ctx)
guard let locations = self.locations else { return }
guard let origin = self.origin else { return }
guard let radius = self.radius else { return }
let colorSpace = CGColorGetColorSpace(color)
let colorComponents = CGColorGetComponents(color)
let gradient = CGGradientCreateWithColorComponents(colorSpace, colorComponents, locations, locations.count)
CGContextDrawRadialGradient(ctx, gradient, origin, CGFloat(0), origin, radius, [.DrawsAfterEndLocation])
}
}
I首字母缩写ze并在此处设置以下图层:
I initialize and set these layers here:
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
let gradient1 = GradientLayer(view: view, locations: [0.0,1.0], origin: CGPoint(x: view.frame.midX, y: view.frame.midY), radius: 100.0, color: UIColor(white: 1.0, alpha: 0.2))
let gradient2 = GradientLayer(view: view, locations: [0.0,1.0], origin: CGPoint(x: view.frame.midX-20, y: view.frame.midY+20), radius: 160.0, color: UIColor(white: 1.0, alpha: 0.2))
let gradient3 = GradientLayer(view: view, locations: [0.0,1.0], origin: CGPoint(x: view.frame.midX+30, y: view.frame.midY-30), radius: 300.0, color: UIColor(white: 1.0, alpha: 0.2))
gradient1.setNeedsDisplay()
gradient2.setNeedsDisplay()
gradient3.setNeedsDisplay()
view.layer.addSublayer(gradient1)
view.layer.addSublayer(gradient2)
view.layer.addSublayer(gradient3)
}
该视图在大多数情况下似乎都能正常显示,但是(似乎)随机地,我会得到不同的效果图,如下图所示。以下是一些示例(第一个示例是我想要的):
The view seems to display properly most of the time, but (seemingly) randomly I'll get different renderings as you'll see below. Here are some examples (the first one is what I want):
是什么原因导致此故障?我怎么每次都只加载第一个?
What is causing this malfunction? How do I only load the first one every time?
推荐答案
您有几个问题。
首先,您应该将渐变视为 stops 的数组,其中 stop 有两个部分:颜色和位置。您必须具有相同数量的颜色和位置,因为每个停靠点都有一个。例如,如果您查看有关 components
参数的 CGGradientCreateWithColorComponents
文档,则可以看到以下内容:
First off, you should think of a gradient as an array of stops, where a stop has two parts: a color and a location. You must have an equal number of colors and locations, because every stop has one of each. You can see this if, for example, you check the CGGradientCreateWithColorComponents
documentation regarding the components
argument:
此数组中的项目数应为
count
乘以颜色空间。
这是乘积(乘积的结果),因为您有个计数
停止,每个停止都需要一整套颜色分量。
It's a product (the result of a multiplication) because you have count
stops and you need a complete set of color components for each stop.
您没有提供足够的颜色分量。您的 GradientLayer
可以有任意多个位置(并且您给了两个),但是只有一种颜色。您将获得一种颜色的组件,并将其作为组件数组传递给 CGGradientCreateWithColorComponents
,但是数组太短。 Swift不会捕获此错误-请注意, colorComponents
的类型为 UnsafePointer< CGFloat>
。 不安全
部分告诉您您处于危险区域。 (您可以通过在Xcode中单击选项来查看 colorComponents
的类型。)
You're not providing enough color components. Your GradientLayer
could have any number of locations (and you're giving it two) but has only one color. You're getting that one color's components and passing that as the components array to CGGradientCreateWithColorComponents
, but the array is too short. Swift doesn't catch this error—notice that the type of your colorComponents
is UnsafePointer<CGFloat>
. The Unsafe
part tells you that you're in dangerous territory. (You can see the type of colorComponents
by option-clicking it in Xcode.)
因为您不是为提供个组件
的足够大的数组,iOS正在使用一种颜色的组件之后在内存中碰巧的任何随机值。这些可能会随运行而变化,并且通常不是您想要的。
Since you're not providing a large enough array for components
, iOS is using whatever random values happen to be in memory after the components of your one color. Those may change from run to run and are often not what you want them to be.
实际上,您甚至不应该使用 CGGradientCreateWithColorComponents
。您应该使用 CGGradientCreateWithColors
,它需要一个 CGColor
数组,因此它不仅使用更简单,而且更安全,因为它是一个更少的 UnsafePointer
浮动。
In fact, you shouldn't even use CGGradientCreateWithColorComponents
. You should use CGGradientCreateWithColors
, which takes an array of CGColor
so it's not only simpler to use, but safer because it's one less UnsafePointer
floating around.
这是 GradientLayer
应该的内容看起来像:
Here's what GradientLayer
should look like:
class RadialGradientLayer: CALayer {
struct Stop {
var location: CGFloat
var color: UIColor
}
var stops: [Stop] { didSet { self.setNeedsDisplay() } }
var origin: CGPoint { didSet { self.setNeedsDisplay() } }
var radius: CGFloat { didSet { self.setNeedsDisplay() } }
init(stops: [Stop], origin: CGPoint, radius: CGFloat) {
self.stops = stops
self.origin = origin
self.radius = radius
super.init()
needsDisplayOnBoundsChange = true
}
override init(layer other: AnyObject) {
guard let other = other as? RadialGradientLayer else { fatalError() }
stops = other.stops
origin = other.origin
radius = other.radius
super.init(layer: other)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func drawInContext(ctx: CGContext) {
let locations = stops.map { $0.location }
let colors = stops.map { $0.color.CGColor }
locations.withUnsafeBufferPointer { pointer in
let gradient = CGGradientCreateWithColors(nil, colors, pointer.baseAddress)
CGContextDrawRadialGradient(ctx, gradient, origin, 0, origin, radius, [.DrawsAfterEndLocation])
}
}
}
下一个问题。每当系统调用 viewWillLayoutSubviews
时,您将添加更多的渐变图层。它可以多次调用该函数!例如,如果您的应用支持界面旋转,或者有电话打入,而iOS使状态栏变高,它将调用它。 (您可以在模拟器中通过选择硬件>切换通话中的状态栏进行测试。)
Next problem. You're adding more gradient layers every time the system calls viewWillLayoutSubviews
. It can call that function multiple times! For example, it will call it if your app supports interface rotation, or if a call comes in and iOS makes the status bar taller. (You can test that in the simulator by choosing Hardware > Toggle In-Call Status Bar.)
您需要创建一次渐变图层,并将其存储在属性中。如果已经创建了它们,则需要更新它们的框架而不是创建新的图层:
You need to create the gradient layers once, storing them in a property. If they have already been created, you need to update their frames and not create new layers:
class ViewController: UIViewController {
private var gradientLayers = [RadialGradientLayer]()
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
if gradientLayers.isEmpty {
createGradientLayers()
}
for layer in gradientLayers {
layer.frame = view.bounds
}
}
private func createGradientLayers() {
let bounds = view.bounds
let mid = CGPointMake(bounds.midX, bounds.midY)
typealias Stop = RadialGradientLayer.Stop
for (point, radius, color) in [
(mid, 100, UIColor(white:1, alpha:0.2)),
(CGPointMake(mid.x - 20, mid.y + 20), 160, UIColor(white:1, alpha:0.2)),
(CGPointMake(mid.x + 30, mid.y - 30), 300, UIColor(white:1, alpha:0.2))
] as [(CGPoint, CGFloat, UIColor)] {
let stops: [RadialGradientLayer.Stop] = [
Stop(location: 0, color: color),
Stop(location: 1, color: color.colorWithAlphaComponent(0))]
let layer = RadialGradientLayer(stops: stops, origin: point, radius: radius)
view.layer.addSublayer(layer)
gradientLayers.append(layer)
}
}
}
这篇关于每次显示视图时,UIView层的子层显示都不同/随机的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!