每次出现视图时,UIView图层的子图层都会以不同/随机显示

时间:2016-05-06 22:36:06

标签: ios swift uiview calayer cagradientlayer

我有一个简单的自定义CALayer来为我的UIView创建一个重叠的渐变效果。这是代码:

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

视图似乎在大多数情况下都能正常显示,但(看似)随机地我会得到不同的效果图,如下所示。以下是一些例子(第一个是我想要的):

enter image description here enter image description here enter image description here

导致此故障的原因是什么?我如何每次只加载第一个?

2 个答案:

答案 0 :(得分:1)

你有几个问题。

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

  

此数组中的项目数应为count的乘积和颜色空间中的组件数。

它是一个产品(乘法的结果),因为您有count个停靠点,并且每个停靠点都需要一套完整的颜色组件。

您没有提供足够的色彩组件。您的GradientLayer可以包含任意数量的位置(并且您只给它两个),但只有一种颜色。您正在获取该颜色的组件并将其作为组件数组传递给CGGradientCreateWithColorComponents,但该数组太短。 Swift没有发现此错误 - 请注意colorComponents的类型为UnsafePointer<CGFloat>Unsafe部分告诉您,您处于危险区域。 (您可以通过选项在Xcode中单击它来查看colorComponents的类型。)

由于您没有为components提供足够大的数组,iOS正在使用您的一种颜色的组件之后的内存中的任何随机值。这些可能会在不同的运行中发生变化,而且往往不是您想要的那样。

事实上,你甚至不应该使用CGGradientCreateWithColorComponents。您应该使用CGGradientCreateWithColors,其中包含CGColor数组,因此它不仅使用起来更简单,而且更安全,因为它只会少UnsafePointer个浮动。< / p>

这里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使状态栏更高,则会调用它。 (您可以通过选择硬件&gt;切换呼叫状态栏在模拟器中测试它。)

您需要创建一次渐变图层,并将它们存储在属性中。如果已经创建了它们,则需要更新它们的帧而不是创建新层:

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

}

答案 1 :(得分:0)

您的问题是您在viewWillLayoutSubviews函数中编写的代码,当视图加载时,它会多次调用,只需添加一次检查即可运行一次或更好,但在viewdidlayoutsubviews中添加一次检查以运行一次