Как заполнить CAShapeLayer угловым градиентом

Как мне заполнить CAShapeLayer() с градиентом и под углом 45 градусов?

Например, на рисунке 1 приведенный ниже код рисует квадрат и заполняет слой синим цветом (UIColor.blueColor().CGColor).

Но как мне заполнить его градиентом под углом 45 градусов от синего к красному, как в изображении 2 (т.е. UIColor.blueColor().CGColor - UIColor.redColor().CGColor)?

Код:

let path = UIBezierPath()
path.moveToPoint(CGPoint(x: 0, y: 0))
path.addLineToPoint(CGPoint(x: 0, y: 100))
path.addLineToPoint(CGPoint(x: 100, y: 100))
path.addLineToPoint(CGPoint(x: 100, y: 0))
path.closePath()

let shape = CAShapeLayer()
shape.path = path.CGPath
shape.fillColor = UIColor.blueColor().CGColor

Solid-color square next to square with gradient

Ответы

Ответ 1

Почему бы не использовать CAGradientLayer которого есть свойства startPoint и endPoint.

Ты можешь сделать:

import UIKit
import PlaygroundSupport

let frame = CGRect(x: 0, y: 0, width: 100, height: 100)
let view = UIView(frame: frame)

PlaygroundPage.current.liveView = view

let path = UIBezierPath(ovalIn: frame)

let shape = CAShapeLayer()
shape.frame = frame
shape.path = path.cgPath
shape.fillColor = UIColor.blue.cgColor

let gradient = CAGradientLayer()
gradient.frame = frame
gradient.colors = [UIColor.blue.cgColor,
                   UIColor.red.cgColor]
gradient.startPoint = CGPoint(x: 0, y: 1)
gradient.endPoint = CGPoint(x: 1, y: 0)
gradient.mask = shape

view.layer.addSublayer(gradient)

enter image description here

Примечание. Добавлен путь безье для круга, потому что он будет работать без маски для квадрата.

Ответ 2

я думаю что это

shape.startPoint = CGPoint(x: 1.0, y: 0.0)
shape.endPoint = CGPoint(x: 0.0, y: 1.0)

который является первым цветом в правом нижнем углу второго цвета в верхнем левом углу. Если вы хотите, чтобы первый цвет находился в правом верхнем и втором цветах в левом нижнем углу, вы должны иметь

shape.startPoint = CGPoint(x: 1.0, y: 1.0)
shape.endPoint = CGPoint(x: 0.0, y: 0.0)

Первый цвет в верхнем левом углу, второй цвет в правом нижнем углу

shape.startPoint = NSMakePoint(x: 0.0, y: 1.0)
shape.endPoint = NSMakePoint(x: 1.0, y: 0.0)

первый цвет в левом нижнем углу, второй цвет в правом верхнем углу

shape.startPoint = CGPoint(x: 0.0, y: 0.0)
shape.endPoint = CGPoint(x: 1.0, y: 1.0)

Ответ 3

Ответ @Alistra работает, если у вас есть форма в верхнем левом углу экрана. Если вы попытаетесь переместить местоположение вашей фигуры, вы заметите, что форма обрезается (если она вообще появляется вообще в зависимости от ваших значений x и y)

Чтобы исправить это, используйте два разных кадра как для слоя градиента, так и для слоя формы. Установите координаты x,y вашего слоя формы на 0,0. Затем задайте координаты x,y вашего градиентного слоя так, чтобы вы разместили его на экране.

    let gradientFrame = CGRect(x: 100,
                               y: 150,
                               width: 150,
                               height: 150)
    let circleFrame = CGRect(x: 0,
                             y: 0,
                             width: 150,
                             height: 150)
    let circle = CAShapeLayer()
    circle.frame = circleFrame
    circle.path = UIBezierPath(ovalIn: circleFrame).cgPath

    let gradient = CAGradientLayer()
    gradient.frame = gradientFrame
    gradient.startPoint = CGPoint(x: 0, y: 0)
    gradient.endPoint = CGPoint(x: 1, y: 1)
    gradient.colors = [UIColor.blue.cgColor,
                       UIColor.red.cgColor]
    gradient.mask = circle
    view.layer.addSublayer(gradient)

Ответ 4

Легко применить градиент для CALayer

Swift 4.2, Xcode 10.0

Хотя вышеприведенные решения действительно работают только с такими тривиальными углами, как 45 °, мой код может установить градиент для любого заданного угла.

public extension CALayer {

    public func applyGradient(of colors: UIColor..., atAngle angle: CGFloat) -> CAGradientLayer {
        let gradient = CAGradientLayer()
        gradient.frame = frame
        gradient.colors = colors
        gradient.calculatePoints(for: angle)
        gradient.mask = self
        return gradient
    }

}


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

        let tanx: (CGFloat) -> CGFloat = { tan($0 * CGFloat.pi / 180) }

        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

        }
    }

}

Использование:

let layer = CAShapeLayer()

// Setup layer...

// Gradient Direction: →
let gradientLayer1 = layer.applyGradient(of: UIColor.yellow, UIColor.red, at: 0)

// Gradient Direction: ↗︎
let gradientLayer2 = layer.applyGradient(of: UIColor.purple, UIColor.yellow, UIColor.green, at: -45)

// Gradient Direction: ←
let gradientLayer3 = layer.applyGradient(of: UIColor.yellow, UIColor.blue, UIColor.green, at: 180)

// Gradient Direction: ↓
let gradientLayer4 = layer.applyGradient(of: UIColor.red, UIColor.blue, at: 450)

Математическое объяснение

Так что я совсем недавно провел много времени, пытаясь ответить на этот вопрос сам. Вот несколько примеров углов, чтобы помочь понять и визуализировать направление вращения по часовой стрелке.

Example Angles

Если вам интересно, как я это понял, я составил таблицу, чтобы наглядно представить, что я делаю, от 0 ° до 360 °.

Table