每次显示视图时,UIView层的子层显示都不同/随机 [英] UIView layer's sublayers display differently/randomly each time view appears

查看:97
本文介绍了每次显示视图时,UIView层的子层显示都不同/随机的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个简单的自定义CALayer,可在UIView上创建覆盖渐变效果。下面是代码:

I have a simple custom CALayer to create an overlaying gradient effect on my UIView. Here is the code:

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

I首字母缩写ze并在此处设置以下图层:

I initialize and set these layers here:

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

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

The view seems to display properly most of the time, but (seemingly) randomly I'll get different renderings as you'll see below. Here are some examples (the first one is what I want):



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

What is causing this malfunction? How do I only load the first one every time?

推荐答案

您有几个问题。

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

First off, you should think of a gradient as an array of stops, where a stop has two parts: a color and a location. You must have an equal number of colors and locations, because every stop has one of each. You can see this if, for example, you check the CGGradientCreateWithColorComponents documentation regarding the components argument:


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

这是乘积(乘积的结果),因为您有个计数停止,每个停止都需要一整套颜色分量。

It's a product (the result of a multiplication) because you have count stops and you need a complete set of color components for each stop.

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

You're not providing enough color components. Your GradientLayer could have any number of locations (and you're giving it two) but has only one color. You're getting that one color's components and passing that as the components array to CGGradientCreateWithColorComponents, but the array is too short. Swift doesn't catch this error—notice that the type of your colorComponents is UnsafePointer<CGFloat>. The Unsafe part tells you that you're in dangerous territory. (You can see the type of colorComponents by option-clicking it in Xcode.)

因为您不是为提供个组件的足够大的数组,iOS正在使用一种颜色的组件之后在内存中碰巧的任何随机值。这些可能会随运行而变化,并且通常不是您想要的。

Since you're not providing a large enough array for components, iOS is using whatever random values happen to be in memory after the components of your one color. Those may change from run to run and are often not what you want them to be.

实际上,您甚至不应该使用 CGGradientCreateWithColorComponents 。您应该使用 CGGradientCreateWithColors ,它需要一个 CGColor 数组,因此它不仅使用更简单,而且更安全,因为它是一个更少的 UnsafePointer 浮动。

In fact, you shouldn't even use CGGradientCreateWithColorComponents. You should use CGGradientCreateWithColors, which takes an array of CGColor so it's not only simpler to use, but safer because it's one less UnsafePointer floating around.

这是 GradientLayer 应该的内容看起来像:

Here's what GradientLayer should look like:

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使状态栏变高,它将调用它。 (您可以在模拟器中通过选择硬件>切换通话中的状态栏进行测试。)

Next problem. You're adding more gradient layers every time the system calls viewWillLayoutSubviews. It can call that function multiple times! For example, it will call it if your app supports interface rotation, or if a call comes in and iOS makes the status bar taller. (You can test that in the simulator by choosing Hardware > Toggle In-Call Status Bar.)

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

You need to create the gradient layers once, storing them in a property. If they have already been created, you need to update their frames and not create new layers:

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

}

这篇关于每次显示视图时,UIView层的子层显示都不同/随机的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆