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
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)
Remarque: Ajout d'un chemin de Bézier pour un cercle, car cela fonctionnerait sans le masque pour le carré.
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
}
}
}
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)
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.
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 °.
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)
@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)