2016-05-06 70 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進行檢查,一旦運行