2016-05-06 19 views
1

UIViewにオーバーレイするグラデーションエフェクトを作成するための簡単なカスタム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

この誤動作の原因になっているもの:ここではいくつかの例は、(最初​​のものは、私が欲しいものです)ですか?毎回最初のものだけを読み込むにはどうすればいいですか?

答えて

1

たら、それを実行するためのviewdidlayoutsubviewsにチェックを追加し、まだ一回以上、それを実行するためのチェックを追加したときにそのが複数回呼び出される関数です問題。色や所在地:の配列が停止は2つの部分を持っているを停止するよう

まず第一に、あなたはグラデーションを考える必要があります。すべてのストップには1つの色と場所があるため、同じ数の色と場所が必要です。

この配列内の項目数をcountの製品と色空間の構成要素の数である必要があります。たとえば、あなたがcomponents引数に関するCGGradientCreateWithColorComponentsドキュメントを確認し、場合は、これを見ることができます。

countの停止があり、停止するたびに完全な色のセットが必要なので、乗算の結果です。

あなたは十分なカラーコンポーネントを提供していません。 GradientLayerにはいくつでも場所がありますが(2つ与えていますが)色は1つだけです。その1つの色のコンポーネントを取得し、それをコンポーネント配列としてCGGradientCreateWithColorComponentsに渡しますが、配列が短すぎます。 Swiftはこのエラーを認識しません。あなたのcolorComponentsのタイプがUnsafePointer<CGFloat>であることに注意してください。 Unsafe部分はあなたが危険な領域にいることを伝えます。

componentsのために十分な大きさの配列を提供していないため、iOSはあなたのコンポーネントの後にランダムな値をメモリ内に使用しています1色それらは逐次実行から実行に変わるかもしれませんし、しばしばそれらが欲しいものではありません。

実際、CGGradientCreateWithColorComponentsは使用しないでください。 CGGradientCreateWithColorsを使用する必要があります。これは、CGColorの配列をとるため、使用するのが簡単であるだけでなく、UnsafePointerが浮かんでいないので安全です。

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

} 

次の問題:ここで

GradientLayerがどのように見えるかです。システムがviewWillLayoutSubviewsを呼び出すたびに、より多くのグラデーションレイヤーを追加しています。その関数を複数回呼び出すことができます!たとえば、アプリがインターフェイスローテーションをサポートしている場合、またはコールが着信してiOSがステータスバーを大きくした場合に呼び出されます。 (Hardware> Toggle In-Call Status Barを選択してシミュレータでテストできます)。

グラデーションレイヤを一度作成してプロパティに保存する必要があります。すでに作成されている場合は、フレームを更新して新しいレイヤーを作成する必要はありません。

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(レイヤーother:AnyObject)'が必要な理由を教えてください。 – Aaron

+1

ドキュメントから:「この初期化子は、たとえば、presentationLayerメソッドのために、レイヤーのシャドウコピーを作成するために使用されます。 [...]カスタムレイヤーのサブクラスを実装する場合、このメソッドをオーバーライドしてインスタンス変数の値を新しいオブジェクトにコピーすることができます。 –

0

あなたの問題はあなたがviewWillLayoutSubviewsに書かれているコードは、ビューの負荷がちょうどあなたは、いくつかを持っている

関連する問題