UIView图层的子图层在每次出现视图时显示不同/随机

我有一个简单的自定义CALayer在我的UIView上创build一个叠加渐变效果。 这里是代码:

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]) } } 

我在这里初始化和设置这些层:

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

视图似乎大部分时间显示正确,但(看起来)随机我会得到不同的渲染,你会看到下面。 这里有一些例子(第一个是我想要的):

在这里输入图像说明 在这里输入图像说明 在这里输入图像说明

什么导致这个故障? 我怎样才能每次加载第一个?

你有几个问题。

首先,您应该将渐变看作是一个停止arrays,其中一个停止有两个部分:一个颜色和一个位置。 您必须拥有相同数量的颜色和位置,因为每个站点都有其中一个。 例如,如果您检查了有关components参数的CGGradientCreateWithColorComponents文档,则可以看到这一点:

此数组中的项目数量应该是count和色彩空间中组件数量的乘积。

这是一个产品(乘法的结果),因为你有停止count ,你需要一个完整的每个停止的颜色组件。

你没有提供足够的颜色组件。 你的GradientLayer可以有任何数量的位置(你给它两个),但只有一种颜色。 你得到的是一种颜色的组件,并将其作为组件数组传递给CGGradientCreateWithColorComponents ,但数组太短。 Swift没有发现这个错误 – 注意你的colorComponentstypes是UnsafePointer<CGFloat>Unsafe部分告诉你,你在危险的领土。 (您可以通过在Xcode中单击它来查看colorComponents的types。)

由于你没有为components提供足够大的数组,所以iOS在你的一种颜色的组件之后使用随机值在内存中。 这些可能会从跑步变成跑步,往往不是你想要的。

事实上,你甚至不应该使用CGGradientCreateWithColorComponents 。 你应该使用CGGradientCreateWithColors ,它接受一个CGColor数组,所以它不仅更简单,而且更安全,因为它less了一个UnsafePointer

以下是GradientLayer样子:

 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会使状态栏变高,就会调用它。 (您可以通过selectHardware> Toggle In-Call Status Bar来在模拟器中进行testing。)

您需要创build一次渐变图层,将它们存储在属性中。 如果他们已经被创build,你需要更新他们的框架,而不是创build新的图层:

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

你的问题是你写在viewWillLayoutSubviews函数中的代码被多次调用时,视图加载时只需添加一个检查运行一次,或者更好,但添加一个检查viewdidlayoutsubviews运行一次