swift2 倾斜渐变层

eoigrqb6  于 2022-11-06  发布在  Swift
关注(0)|答案(5)|浏览(153)

我有一个自定义的UIView类,可以在Swift 2中渲染渐变。我正在努力制作一个倾斜的渐变,以便它从左上角到右下角绘制。有人能帮我一点吗?

import UIKit

class GradientView: UIView {

    let gradientLayer = CAGradientLayer()

    override func awakeFromNib() {
        // 1
        self.backgroundColor = ColorPalette.White

        // 2
        gradientLayer.frame = self.bounds

        // 3
        let color1 = ColorPalette.GrdTop.CGColor as CGColorRef
        let color2 = ColorPalette.GrdBottom.CGColor as CGColorRef
        gradientLayer.colors = [color1, color2]

        // 4
        gradientLayer.locations = [0.0, 1.0]

        // 5
        self.layer.addSublayer(gradientLayer)
    }

}

我怀疑这应该是其他的东西,但无论我输入什么都没有改变。

gradientLayer.locations = [0.0, 1.0]
inn6fuwd

inn6fuwd1#

您不想使用locations来指定渐变的方向,而应该使用startPointendPoint
locations数组用于指定渐变应该在startPointendPoint之间的哪个位置发生。例如,如果您希望颜色只在从起点到终点的中间10%范围内发生,您可以用途:

locations = [0.45, 0.55]

locations数组并不指定方向,而startPointendPoint指定方向。因此,对于从左上角到右下角的对角线渐变,可以将startPoint设置为CGPoint(x: 0, y: 0),将endPoint设置为CGPoint(x: 1, y: 1)
例如:

@IBDesignable
class GradientView: UIView {

    override class var layerClass: AnyClass { return CAGradientLayer.self }

    private var gradientLayer: CAGradientLayer { return layer as! CAGradientLayer }

    @IBInspectable var color1: UIColor = .white { didSet { updateColors() } }
    @IBInspectable var color2: UIColor = .blue  { didSet { updateColors() } }

    override init(frame: CGRect = .zero) {
        super.init(frame: frame)
        configureGradient()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        configureGradient()
    }

    private func configureGradient() {
        gradientLayer.startPoint = CGPoint(x: 0, y: 0)
        gradientLayer.endPoint = CGPoint(x: 1, y: 1)
        updateColors()
    }

    private func updateColors() {
        gradientLayer.colors = [color1.cgColor, color2.cgColor]
    }

}

例如:

注意,与当前问题无关:

  • 如果要添加渐变作为子层,则需要在layoutSubviews中更新该子层的frame,这样,当视图的bounds发生变化时,gradientLayerframe也会发生变化。但是,更好的方法是,覆盖视图的layerClass,它不仅会为您示例化CAGradientLayer,而且还可以随着视图大小的变化对渐变进行动态调整,特别是可以更优雅地处理动画更改。
  • 同样,我设置了color1color2,这样它们将触发渐变的更新,以便颜色的任何变化都将立即反映在视图中。
  • 我创建了这个@IBDesignable,所以如果我把它放到它自己的框架中,然后在IB中添加GradientView,我会看到在IB中呈现的效果。

有关Swift 2的实现,请参见previous revision of this answer

ds97pgxw

ds97pgxw2#

任何Angular 的渐层起点和终点

Swift 4.2、Xcode 10.0的版本
给定任意Angular ,我的代码将设置渐变层的起点和终点。
如果输入的Angular 大于 360°,则将使用除以 360 后的余数。

  • 输入 415° 将产生与输入 55° 相同的结果

如果输入小于 的Angular ,则将顺时针旋转方向反转

  • 输入 -15° 将产生与输入 345° 相同的结果

代码:

public extension CAGradientLayer {

    /// Sets the start and end points on a gradient layer for a given angle.
    ///
    /// - Important:
    /// *0°* is a horizontal gradient from left to right.
    ///
    /// With a positive input, the rotational direction is clockwise.
    ///
    ///    * An input of *400°* will have the same output as an input of *40°*
    ///
    /// With a negative input, the rotational direction is clockwise.
    ///
    ///    * An input of *-15°* will have the same output as *345°*
    ///
    /// - Parameters:
    ///     - angle: The angle of the gradient.
    ///                  
    public func calculatePoints(for angle: CGFloat) {

        var ang = (-angle).truncatingRemainder(dividingBy: 360)

        if ang < 0 { ang = 360 + ang }

        let n: CGFloat = 0.5

        switch ang {

        case 0...45, 315...360:
            let a = CGPoint(x: 0, y: n * tanx(ang) + n)
            let b = CGPoint(x: 1, y: n * tanx(-ang) + n)
            startPoint = a
            endPoint = b

        case 45...135:
            let a = CGPoint(x: n * tanx(ang - 90) + n, y: 1)
            let b = CGPoint(x: n * tanx(-ang - 90) + n, y: 0)
            startPoint = a
            endPoint = b

        case 135...225:
            let a = CGPoint(x: 1, y: n * tanx(-ang) + n)
            let b = CGPoint(x: 0, y: n * tanx(ang) + n)
           startPoint = a
            endPoint = b

        case 225...315:
            let a = CGPoint(x: n * tanx(-ang - 90) + n, y: 0)
            let b = CGPoint(x: n * tanx(ang - 90) + n, y: 1)
            startPoint = a
            endPoint = b

        default:
            let a = CGPoint(x: 0, y: n)
            let b = CGPoint(x: 1, y: n)
            startPoint = a
            endPoint = b

        }
    }

    /// Private function to aid with the math when calculating the gradient angle
    private func tanx(_ 𝜽: CGFloat) -> CGFloat {
        return tan(𝜽 * CGFloat.pi / 180)
    }

    // Overloads

    /// Sets the start and end points on a gradient layer for a given angle.
    public func calculatePoints(for angle: Int) {
        calculatePoints(for: CGFloat(angle))
    }

    /// Sets the start and end points on a gradient layer for a given angle.
    public func calculatePoints(for angle: Float) {
        calculatePoints(for: CGFloat(angle))
    }

    /// Sets the start and end points on a gradient layer for a given angle.
    public func calculatePoints(for angle: Double) {
        calculatePoints(for: CGFloat(angle))
    }

}

用法:

let gradientLayer = CAGradientLayer()

// Setup gradient layer...

// Gradient Direction: →
gradient.calculatePoints(for: 0)

// Gradient Direction: ↗︎
gradient.calculatePoints(for: -45)

// Gradient Direction: ←
gradient.calculatePoints(for: 180)

// Gradient Direction: ↓
gradient.calculatePoints(for: 450)

数学解释

所以我最近花了很多时间试图自己来回答这个问题。这里有一些Angular 的例子,只是为了帮助理解和形象化顺时针旋转的方向。

如果你想知道我是如何得出这个结论的,我做了一个表格,把我从 360° 的过程形象化。

d8tt03nd

d8tt03nd3#

看起来你忘记在你的CAGradientLayer()上设置startPoint了。下面的代码是你提供的代码,加上我的补充。

import UIKit

class GradientView: UIView {

    let gradientLayer = CAGradientLayer()

    override func awakeFromNib() {
        // 1
        self.backgroundColor = ColorPalette.White

        // 2
        gradientLayer.frame = self.bounds

        // 3
        let color1 = ColorPalette.GrdTop.CGColor as CGColorRef
        let color2 = ColorPalette.GrdBottom.CGColor as CGColorRef
        gradientLayer.colors = [color1, color2]

        //**This code should do the trick...**//
        gradientLayer.startPoint = CGPointMake(0.0, 0.5)

        // 4
        gradientLayer.locations = [0.0, 1.0]

        // 5
        self.layer.addSublayer(gradientLayer)
    }
}
s71maibg

s71maibg4#

我不知道是什么原因让你的不工作,但我确实有一个GradientView,我使用它可以是水平或垂直的,并与ui构建器的东西。请随时运行它,并根据您的需要进行改进:

import UIKit

@IBDesignable  class  GradientView: UIView {
    var gradient:CAGradientLayer
    @IBInspectable var startColor:UIColor = UIColor.whiteColor() {
        didSet {
            self.updateGradient()
        }
    }

    @IBInspectable var color1:UIColor? = nil {
        didSet {
            self.updateGradient()
        }
    }

    @IBInspectable var stop1:Double = (1.0 / 3.0) {
        didSet {
            self.updateGradient()
        }
    }

    @IBInspectable var color2:UIColor? = nil {
        didSet {
            self.updateGradient()
        }
    }

    @IBInspectable var stop2:Double = (2.0 / 3.0) {
        didSet {
            self.updateGradient()
        }
    }

    @IBInspectable var endColor:UIColor = UIColor.blackColor() {
        didSet {
            self.updateGradient()
        }
    }

    @IBInspectable var isHorizontal:Bool {
        get {
            return self.gradient.endPoint.y == self.gradient.startPoint.y
        }
        set {
            self.gradient.endPoint = newValue ? CGPoint(x: 1, y: 0) : CGPoint(x: 0, y: 1)
        }
    }

    override init(frame: CGRect) {
        gradient = CAGradientLayer()
        super.init(frame: frame)
        self.configGradient()
    }

    required init?(coder aDecoder: NSCoder) {
        gradient = CAGradientLayer()
        super.init(coder: aDecoder)
        self.configGradient()
    }

    func configGradient() {
        self.backgroundColor = UIColor.clearColor()
        self.layer.insertSublayer(self.gradient, atIndex: 0)
        self.gradient.masksToBounds = true
        self.gradient.frame = self.bounds
        self.gradient.startPoint = CGPoint(x: 0, y: 0)
        self.gradient.endPoint = CGPoint(x: 1, y: 0)
        self.updateGradient()
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        self.gradient.frame = self.bounds
    }

    func updateGradient() {
        var colors:[CGColorRef] = []
        var locations:[NSNumber] = []
        colors.append(self.startColor.CGColor)
        locations.append(0.0.nsNumber)

        if let color = self.color1 {
            colors.append(color.CGColor)
            locations.append(self.stop1)}

        if let color = self.color2 {
            colors.append(color.CGColor)
            locations.append(self.stop2)
        }

        colors.append(self.endColor.CGColor)
        locations.append(1.0.nsNumber)

        self.gradient.colors = colors
        self.gradient.locations = locations

        self.layer.setNeedsDisplay()
    }
}
5lhxktic

5lhxktic5#

可以使用一些基本的三角函数来实现倾斜渐变,可以通过对UIView进行子类化来实现,正如我在blog post on the subject中所描述的。
首先定义一些变量:-

// The end point of the gradient when drawn in the layer’s coordinate space. Animatable.
var endPoint: CGPoint

// The start point of the gradient when drawn in the layer’s coordinate space. Animatable.
var startPoint: CGPoint

// the gradient angle, in degrees anticlockwise from 0 (east/right)
@IBInspectable var angle: CGFloat = 270

下面的核心函数获取单位空间中的起点和终点。

// create vector pointing in direction of angle
private func gradientPointsForAngle(_ angle: CGFloat) -> (CGPoint, CGPoint) {
    // get vector start and end points
    let end = pointForAngle(angle)
    let start = oppositePoint(end)
    // convert to gradient space
    let p0 = transformToGradientSpace(start)
    let p1 = transformToGradientSpace(end)
    return (p0, p1)
}

这只是取用户指定的Angular ,并使用它来创建指向该方向的矢量。Angular 指定矢量从0度开始的旋转,按照惯例,在Core Animation中指向东方,并逆时针增加(逆时针)。
相关代码的其余部分如下所示,它涉及的是这样一个事实,即结果点位于单位圆上。然而,我们需要的点位于单位正方形中:将该矢量外推到单位平方。

private func pointForAngle(_ angle: CGFloat) -> CGPoint {
    // convert degrees to radians
    let radians = angle * .pi / 180.0
    var x = cos(radians)
    var y = sin(radians)
    // (x,y) is in terms unit circle. Extrapolate to unit square to get full vector length
    if (fabs(x) > fabs(y)) {
        // extrapolate x to unit length
        x = x > 0 ? 1 : -1
        y = x * tan(radians)
    } else {
        // extrapolate y to unit length
        y = y > 0 ? 1 : -1
        x = y / tan(radians)
    }
    return CGPoint(x: x, y: y)
}

private func oppositePoint(_ point: CGPoint) -> CGPoint {
    return CGPoint(x: -point.x, y: -point.y)
}

private func transformToGradientSpace(_ point: CGPoint) -> CGPoint {
    // input point is in signed unit space: (-1,-1) to (1,1)
    // convert to gradient space: (0,0) to (1,1), with flipped Y axis
    return CGPoint(x: (point.x + 1) * 0.5, y: 1.0 - (point.y + 1) * 0.5)
}

最后,必须从更新函数中调用所有内容:

private func updateGradient() {
    if let gradient = self.gradient {
        let (start, end) = gradientPointsForAngle(self.angle)
        gradient.startPoint = start
        gradient.endPoint = end
        gradient.frame = self.bounds
    }
}

有关完整的实现,请参阅我的blog post

相关问题