2016-05-06 74 views
1

我有一个简单的自定义CALayer在我的UIView上创建叠加渐变效果。下面是代码:每次出现视图时,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

是什么原因造成这种故障?我怎样才能每次加载第一个?

回答

1

你有几个问题。

首先,你应该想一个梯度作为阵列停止,其中停止有两个部分:一个颜色和位置。您必须拥有相同数量的颜色和位置,因为每个站点都有其中一个。你可以看到这一点,如果,例如,你检查CGGradientCreateWithColorComponents文档关于components说法:

这个数组中的项目数量应该是count产品,并在颜色空间分量的数量。

这是一个产品(乘法的结果),因为您有count停止位,并且您需要一组完整的颜色分量用于每个停止。您提供的颜色组件不足,你的GradientLayer可以有任何数量的位置(你给它两个),但只有一种颜色。您正在获取该颜色的组件并将其作为组件数组传递至CGGradientCreateWithColorComponents,但该数组太短。斯威夫特并没有发现这个错误 - 注意你的colorComponents的类型是UnsafePointer<CGFloat>Unsafe部分告诉你,你处于危险的境地。 (你可以通过在Xcode中点击选项来查看colorComponents的类型。)

由于你没有为components提供足够大的数组,因此iOS使用的是组件之后发生的随机值一种颜色。这些可能会因运行而改变,往往不是你想要的。其实,你甚至不应该使用CGGradientCreateWithColorComponents。你应该使用CGGradientCreateWithColors,它采用CGColor的数组,因此它不仅更简单,而且更安全,因为它少了一个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使状态栏更高,它会调用它。 (您可以通过选择硬件测试,在模拟器>切换在通话状态栏。)

您需要一次创建渐变图层,将它们存储在一个属性。如果它们已经被创建,你需要更新它们的帧并且不创建新的图层:

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

} 
+0

你能解释一下'init(layer other:AnyObject)'需要什么吗? – Aaron

+1

从文档:“此初始化器用于创建图层的阴影副本,例如,用于'presentationLayer'方法。 [...]如果您正在实现自定义图层子类,则可以重写此方法并使用它将实例变量的值复制到新对象中。“ –

0

你的问题是你已经写在viewWillLayoutSubviews代码函数它被多次调用时的观点负载只需添加一个检查,以运行一次或更好,但添加在viewdidlayoutsubviews进行检查,一旦运行