web-dev-qa-db-fra.com

Comment remplir un dégradé angulaire d'une couche CAShapeLayer

Comment remplir un CAShapeLayer() avec un dégradé et un angle de 45 degrés?

Par exemple, dans Image 1, le code ci-dessous dessine un carré et remplit le calque bleu (UIColor.blueColor().CGColor).

Mais comment le remplir avec un dégradé sur un angle de 45 degrés du bleu au rouge, comme dans Image 2 (c'est-à-dire UIColor.blueColor().CGColor à UIColor.redColor().CGColor)?

Code:

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

14
user4806509

Pourquoi ne pas utiliser CAGradientLayer qui a les propriétés startPoint et endPoint.

Tu peux faire:

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

Remarque: Ajout d'un chemin de Bézier pour un cercle, car cela fonctionnerait sans le masque pour le carré.

27
Alistra

Appliquer facilement le dégradé à CALayer

Swift 4.2, Xcode 10.0

Bien que les solutions ci-dessus ne fonctionnent vraiment qu'avec des angles triviaux tels que 45 °, mon code est capable de définir un dégradé à n’importe quel angle donné.

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

        }
    }

}

Usage:

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)

Explication mathématique

En fait, j'ai récemment passé beaucoup de temps à essayer de répondre à cette question moi-même. Voici quelques exemples d’angles pour vous aider à comprendre et à visualiser le sens de rotation dans le sens des aiguilles d’une montre.

 Example Angles

Si vous êtes intéressé par la façon dont je l'ai compris, j'ai créé un tableau pour visualiser essentiellement ce que je fais de 0 ° à 360 °

 Table

3
Noah Wilder

Je pense que c'est

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

, qui est la première couleur en bas à droite de la deuxième couleur en haut à gauche. Si vous voulez la première couleur en haut à droite et la deuxième couleur en bas à gauche, alors vous devriez avoir

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

Première couleur en haut à gauche, deuxième couleur en bas à droite

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

première couleur en bas à gauche, deuxième couleur en haut à droite

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

@Alistra answer fonctionne si votre forme se trouve dans le coin supérieur gauche de l'écran. Si vous essayez de déplacer l’emplacement de votre forme, vous remarquerez que celle-ci est coupée (si elle apparaît même en fonction de vos valeurs x et y)

Pour résoudre ce problème, utilisez deux cadres différents pour le calque de dégradé et le calque de forme. Définissez les coordonnées x,y de votre couche de forme sur 0,0. Ensuite, définissez les coordonnées x,y de votre calque de dégradé sur l'emplacement souhaité à l'écran.

    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)
0
WBuck