color ios swift calayer cagradientlayer

ios - color - Gradiente diagonal CAGradientLayer



ios swift gradient (3)

Yo uso el siguiente CAGradientLayer:

let layer = CAGradientLayer() layer.colors = [ UIColor.redColor().CGColor, UIColor.greenColor().CGColor, UIColor.blueColor().CGColor ] layer.startPoint = CGPointMake(0, 1) layer.endPoint = CGPointMake(1, 0) layer.locations = [0.0, 0.6, 1.0]

Pero cuando configuro la propiedad de límites para la capa, solo se extiende un degradado cuadrado. Necesito un resultado como en la imagen de la aplicación Sketch 3 (ver arriba).

¿Cómo puedo conseguir esto?


Aquí está la matemática para arreglar el punto final

let width = bounds.width let height = bounds.height let dx = endPoint.x - startPoint.x let dy = endPoint.y - startPoint.y if width == 0 || height == 0 || width == height || dx == 0 || dy == 0 { return } let ux = dx * width / height let uy = dy * height / width let coef = (dx * ux + dy * uy) / (ux * ux + uy * uy) endPoint = CGPoint(x: startPoint.x + coef * ux, y: startPoint.y + coef * uy)


Código completo del método layoutSubviews es

override func layoutSubviews() { super.layoutSubviews() let gradientOffset = self.bounds.height / self.bounds.width / 2 self.gradientLayer.startPoint = CGPointMake(0, 0.5 + gradientOffset) self.gradientLayer.endPoint = CGPointMake(1, 0.5 - gradientOffset) self.gradientLayer.frame = self.bounds }


Actualización: use context.drawLinearGradient () en lugar de CAGradientLayer de una manera similar a la siguiente . Dibujará gradientes que son consistentes con Sketch / Photoshop.

Si absolutamente debe usar CAGradientLayer, aquí está la matemática que necesitará usar ...

Tomó un tiempo descifrarlo, pero luego de una observación cuidadosa, descubrí que la implementación de gradientes de Apple en CAGradientLayer es bastante extraña:

  1. Primero convierte la vista a un cuadrado.
  2. Luego aplica el gradiente utilizando los puntos de inicio / fin.
  3. El gradiente medio formará un ángulo de 90 grados en esta resolución.
  4. Finalmente, aplasta la vista hasta el tamaño original.

Esto significa que el gradiente medio ya no formará un ángulo de 90 grados en el nuevo tamaño. Esto contradice el comportamiento de prácticamente todas las demás aplicaciones de pintura: Sketch, Photoshop, etc.

Si desea implementar puntos de inicio / finalización como funciona en Sketch, deberá traducir los puntos de inicio / finalización para tener en cuenta el hecho de que Apple va a aplastar la vista.

Pasos a realizar (Diagramas)

Código

import UIKit /// Last updated 4/3/17. /// See https://.com/a/43176174 for more information. public enum LinearGradientFixer { public static func fixPoints(start: CGPoint, end: CGPoint, bounds: CGSize) -> (CGPoint, CGPoint) { // Naming convention: // - a: point a // - ab: line segment from a to b // - abLine: line that passes through a and b // - lineAB: line that passes through A and B // - lineSegmentAB: line segment that passes from A to B if start.x == end.x || start.y == end.y { // Apple''s implementation of horizontal and vertical gradients works just fine return (start, end) } // 1. Convert to absolute coordinates let startEnd = LineSegment(start, end) let ab = startEnd.multiplied(multipliers: (x: bounds.width, y: bounds.height)) let a = ab.p1 let b = ab.p2 // 2. Calculate perpendicular bisector let cd = ab.perpendicularBisector // 3. Scale to square coordinates let multipliers = calculateMultipliers(bounds: bounds) let lineSegmentCD = cd.multiplied(multipliers: multipliers) // 4. Create scaled perpendicular bisector let lineSegmentEF = lineSegmentCD.perpendicularBisector // 5. Unscale back to rectangle let ef = lineSegmentEF.divided(divisors: multipliers) // 6. Extend line let efLine = ef.line // 7. Extend two lines from a and b parallel to cd let aParallelLine = Line(m: cd.slope, p: a) let bParallelLine = Line(m: cd.slope, p: b) // 8. Find the intersection of these lines let g = efLine.intersection(with: aParallelLine) let h = efLine.intersection(with: bParallelLine) if let g = g, let h = h { // 9. Convert to relative coordinates let gh = LineSegment(g, h) let result = gh.divided(divisors: (x: bounds.width, y: bounds.height)) return (result.p1, result.p2) } return (start, end) } private static func unitTest() { let w = 320.0 let h = 60.0 let bounds = CGSize(width: w, height: h) let a = CGPoint(x: 138.5, y: 11.5) let b = CGPoint(x: 151.5, y: 53.5) let ab = LineSegment(a, b) let startEnd = ab.divided(divisors: (x: bounds.width, y: bounds.height)) let start = startEnd.p1 let end = startEnd.p2 let points = fixPoints(start: start, end: end, bounds: bounds) let pointsSegment = LineSegment(points.0, points.1) let result = pointsSegment.multiplied(multipliers: (x: bounds.width, y: bounds.height)) print(result.p1) // expected: (90.6119039567129, 26.3225059181603) print(result.p2) // expected: (199.388096043287, 38.6774940818397) } } private func calculateMultipliers(bounds: CGSize) -> (x: CGFloat, y: CGFloat) { if bounds.height <= bounds.width { return (x: 1, y: bounds.width/bounds.height) } else { return (x: bounds.height/bounds.width, y: 1) } } private struct LineSegment { let p1: CGPoint let p2: CGPoint init(_ p1: CGPoint, _ p2: CGPoint) { self.p1 = p1 self.p2 = p2 } init(p1: CGPoint, m: CGFloat, distance: CGFloat) { self.p1 = p1 let line = Line(m: m, p: p1) let measuringPoint = line.point(x: p1.x + 1) let measuringDeltaH = LineSegment(p1, measuringPoint).distance let deltaX = distance/measuringDeltaH self.p2 = line.point(x: p1.x + deltaX) } var length: CGFloat { let dx = p2.x - p1.x let dy = p2.y - p1.y return sqrt(dx * dx + dy * dy) } var distance: CGFloat { return p1.x <= p2.x ? length : -length } var midpoint: CGPoint { return CGPoint(x: (p1.x + p2.x)/2, y: (p1.y + p2.y)/2) } var slope: CGFloat { return (p2.y-p1.y)/(p2.x-p1.x) } var perpendicularSlope: CGFloat { return -1/slope } var line: Line { return Line(p1, p2) } var perpendicularBisector: LineSegment { let p1 = LineSegment(p1: midpoint, m: perpendicularSlope, distance: -distance/2).p2 let p2 = LineSegment(p1: midpoint, m: perpendicularSlope, distance: distance/2).p2 return LineSegment(p1, p2) } func multiplied(multipliers: (x: CGFloat, y: CGFloat)) -> LineSegment { return LineSegment( CGPoint(x: p1.x * multipliers.x, y: p1.y * multipliers.y), CGPoint(x: p2.x * multipliers.x, y: p2.y * multipliers.y)) } func divided(divisors: (x: CGFloat, y: CGFloat)) -> LineSegment { return multiplied(multipliers: (x: 1/divisors.x, y: 1/divisors.y)) } } private struct Line { let m: CGFloat let b: CGFloat /// y = mx+b init(m: CGFloat, b: CGFloat) { self.m = m self.b = b } /// y-y1 = m(x-x1) init(m: CGFloat, p: CGPoint) { // y = m(x-x1) + y1 // y = mx-mx1 + y1 // y = mx + (y1 - mx1) // b = y1 - mx1 self.m = m self.b = p.y - m*p.x } init(_ p1: CGPoint, _ p2: CGPoint) { self.init(m: LineSegment(p1, p2).slope, p: p1) } func y(x: CGFloat) -> CGFloat { return m*x + b } func point(x: CGFloat) -> CGPoint { return CGPoint(x: x, y: y(x: x)) } func intersection(with line: Line) -> CGPoint? { // Line 1: y = mx + b // Line 2: y = nx + c // mx+b = nx+c // mx-nx = c-b // x(m-n) = c-b // x = (c-b)/(m-n) let n = line.m let c = line.b if m-n == 0 { // lines are parallel return nil } let x = (c-b)/(m-n) return point(x: x) } }

Prueba que funciona independientemente del tamaño del rectángulo

Intenté esto con un size=320x60 vista size=320x60 , gradient=[red@0,[email protected],blue@1] , startPoint = (0,1) y endPoint = (1,0) .

Bosquejo 3:

Captura de pantalla de iOS real generada usando el código anterior:

Tenga en cuenta que el ángulo de la línea verde se ve con una precisión del 100%. La diferencia radica en cómo se mezclan el rojo y el azul. No puedo decir si es porque estoy calculando los puntos de inicio / finalización incorrectamente, o si es solo una diferencia en la forma en que Apple combina los gradientes en comparación con la forma en que Sketch combina los gradientes.