-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/gradient layer accessors #3
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,76 +11,125 @@ import Foundation | |
import UIKit | ||
|
||
extension CAGradientLayer { | ||
class func startPointFor(_ angle: Double) -> CGPoint { | ||
if let defaultDirection = GradientDirection(rawValue: angle) { | ||
switch defaultDirection { | ||
case .topToBottom: | ||
return CGPoint(x: 0.5, y: 0.0) | ||
case .bottomToTop: | ||
return CGPoint(x: 0.5, y: 1.0) | ||
case .leftToRight: | ||
return CGPoint(x: 0.0, y: 0.5) | ||
default: | ||
return CGPoint(x: 1.0, y: 0.5) | ||
//MARK: Attributes accessors | ||
|
||
/// Collection of UIColors used in the gradient. | ||
public var uiColors: [UIColor] { | ||
get { | ||
guard let anyColors = colors else { return [] } | ||
return anyColors.map { color in | ||
CFGetTypeID(color as CFTypeRef) == CGColor.typeID ? | ||
UIColor(cgColor: color as! CGColor) : | ||
UIColor.clear | ||
} | ||
} | ||
return pointWithAngle(angle) | ||
set { | ||
colors = newValue.map { $0.cgColor } | ||
} | ||
} | ||
|
||
class func endPointFor(_ angle: Double) -> CGPoint { | ||
if let defaultDirection = GradientDirection(rawValue: angle) { | ||
switch defaultDirection { | ||
case .topToBottom: | ||
return CGPoint(x: 0.5, y: 1.0) | ||
case .bottomToTop: | ||
return CGPoint(x: 0.5, y: 0.0) | ||
case .leftToRight: | ||
return CGPoint(x: 1.0, y: 0.5) | ||
default: | ||
return CGPoint(x: 0.0, y: 0.5) | ||
|
||
/// Color stop locations in percentages. | ||
public var percentLocations: [Int] { | ||
get { | ||
guard let decimalLocations = locations else { return [] } | ||
return decimalLocations.map { Int(exactly: $0.floatValue * 100) ?? 0 } | ||
} | ||
set{ | ||
locations = newValue.map { NSNumber(value: Float($0) / 100.0) } | ||
} | ||
} | ||
|
||
/// Predefined gradient direction if specified or if the current angle | ||
/// matches any of the GradientDirection cases. | ||
public var direction: GradientDirection? { | ||
get { | ||
if | ||
let direction = GradientDirection.allCases.first(where: { direction in | ||
abs(direction.startPoint.x - startPoint.x) <= .ulpOfOne && | ||
abs(direction.startPoint.y - startPoint.y) <= .ulpOfOne && | ||
abs(direction.endPoint.x - endPoint.x) <= .ulpOfOne && | ||
abs(direction.endPoint.y - endPoint.y) <= .ulpOfOne | ||
}) | ||
{ | ||
return direction | ||
} | ||
return nil | ||
} | ||
set { | ||
guard let newDirection = newValue else { return } | ||
startPoint = newDirection.startPoint | ||
endPoint = newDirection.endPoint | ||
} | ||
} | ||
|
||
/// The gradient angle in degrees, measured clockwise and starting at the left. | ||
/// 0 -> left, 90 -> up, etc | ||
public var angle: CGFloat { | ||
get { | ||
let product = endPoint.y - startPoint.y | ||
let determinant = endPoint.x - startPoint.x | ||
var degrees = CGFloat(atan2(product, determinant)) * 180 / CGFloat.pi | ||
if degrees < 0 { degrees = 360 + degrees } | ||
|
||
return degrees.truncatingRemainder(dividingBy: 360) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe add |
||
} | ||
set { | ||
startPoint = CAGradientLayer.startPointFor(newValue) | ||
endPoint = CAGradientLayer.endPointFor(newValue) | ||
} | ||
} | ||
|
||
//MARK: Angle and points helpers | ||
|
||
class func startPointFor(_ angle: CGFloat) -> CGPoint { | ||
return pointWithAngle(angle) | ||
} | ||
|
||
class func endPointFor(_ angle: CGFloat) -> CGPoint { | ||
return pointWithAngle(angle, isStartPoint: false) | ||
} | ||
|
||
/// **pointWithAngle**: Helper for CAGradientLayer's start and endPoint given an angle in degrees | ||
/// - Parameter **angle** The desired angle in degrees and measured anti-clockwise. | ||
/// **pointWithAngle**: Helper for CAGradientLayer's start and endPoint | ||
/// given an angle in degrees | ||
/// - Parameter **angle** The desired angle in degrees, measured clockwise | ||
/// and starting at the left. | ||
/// - Parameter **isStartPoint** A boolean indicating which point you need. | ||
/// - Returns: The initial or ending CGPoint for a CAGradientLayer within the Unit Cordinate System. | ||
/// - Returns: The initial or ending CGPoint for a CAGradientLayer | ||
/// within the Unit Cordinate System. | ||
private class func pointWithAngle( | ||
_ angle: Double, | ||
_ angle: CGFloat, | ||
isStartPoint: Bool = true | ||
) -> CGPoint { | ||
// negative angles not allowed | ||
var positiveAngle = angle < 0 ? angle * -1.0 : angle | ||
var y1: Double, y2: Double, x1: Double, x2: Double | ||
var ang = (-angle).truncatingRemainder(dividingBy: 360) | ||
if ang < 0 { ang = 360 + ang } | ||
let n: CGFloat = 0.5 | ||
|
||
if // ranges when we know Y values | ||
(positiveAngle >= 45 && positiveAngle <= 135) || | ||
(positiveAngle >= 225 && positiveAngle <= 315) | ||
{ | ||
y1 = positiveAngle < 180 ? 0.0 : 1.0 | ||
y2 = 1.0 - y1 //opposite to start Y | ||
x1 = positiveAngle >= 45 && positiveAngle <= 135 ? | ||
1.5 - positiveAngle / 90 : | ||
abs(2.5 - positiveAngle / 90) | ||
x2 = 1.0-x1 //opposite to start X | ||
} else { // ranges when we know X values | ||
x1 = positiveAngle < 45 || positiveAngle >= 315 ? 1.0 : 0.0 | ||
x2 = 1.0 - x1 | ||
if positiveAngle > 135 && positiveAngle < 225 { | ||
y2 = abs(2.5 - positiveAngle / 90) | ||
y1 = 1.0 - y2 | ||
} else { // Range 0-45 315-360 | ||
//Turn this ranges into one single 90 degrees range | ||
positiveAngle = positiveAngle >= 0 && positiveAngle <= 45 ? | ||
45.0 - positiveAngle : | ||
360 - positiveAngle + 45 | ||
y1 = positiveAngle / 90 | ||
y2 = 1.0 - y1 | ||
} | ||
switch ang { | ||
case 0...45, 315...360: | ||
return isStartPoint ? | ||
CGPoint(x: 0, y: n * tanx(ang) + n) : | ||
CGPoint(x: 1, y: n * tanx(-ang) + n) | ||
case 45...135: | ||
return isStartPoint ? | ||
CGPoint(x: n * tanx(ang - 90) + n, y: 1) : | ||
CGPoint(x: n * tanx(-ang - 90) + n, y: 0) | ||
case 135...225: | ||
return isStartPoint ? | ||
CGPoint(x: 1, y: n * tanx(-ang) + n) : | ||
CGPoint(x: 0, y: n * tanx(ang) + n) | ||
case 225...315: | ||
return isStartPoint ? | ||
CGPoint(x: n * tanx(-ang - 90) + n, y: 0) : | ||
CGPoint(x: n * tanx(ang - 90) + n, y: 1) | ||
default: | ||
return isStartPoint ? | ||
CGPoint(x: 0, y: n) : | ||
CGPoint(x: 1, y: n) | ||
} | ||
Comment on lines
+107
to
128
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm going to decide to believe you 😂 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will post the source fro you :) |
||
return isStartPoint ? CGPoint(x: x1, y: y1) : CGPoint(x: x2, y: y2) | ||
} | ||
|
||
private class func tanx(_ 𝜽: CGFloat) -> CGFloat { | ||
return tan(𝜽 * CGFloat.pi / 180) | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
self not needed