Skip to content

Instantly share code, notes, and snippets.

@Ikloo
Created November 5, 2018 19:48
Show Gist options
  • Save Ikloo/039fce762b547e684f76795b248e2922 to your computer and use it in GitHub Desktop.
Save Ikloo/039fce762b547e684f76795b248e2922 to your computer and use it in GitHub Desktop.
Gradientable protocol for UIView
// Created by Kirill Budevich.
// Copyright © 2018 Kirill Budevich. All rights reserved.
//
import UIKit
public protocol Gradientable: class {
/// Drawed CAGradientLayer from sublayers, nil if not added
var gradientLayer: CAGradientLayer? { get }
/// Draw gradient with style.
/// Insert as sublayer for some styles.
/// - parameter color: Gradient.Color struct with colors and locations
/// - parameter style: Gradient.Style enum with different methods to draw.
/// - parameter direction: Gradient.Direction enum with different start and end gradient's points.
func setGradient(color: Gradient.Color, style: Gradient.Style, direction: Gradient.Direction)
/// Remove gradient layer if exist.
func removeGradientLayer()
}
public enum Gradient {
public enum Style {
/// Fill for full frame like as background color
case fill
/// Draw border. - parameter lineWidth: line width for border
case border(CGFloat)
/// Draw mask. - parameter opacity: opacity value for mask
case mask(Float)
/// Draw text. - parameter closure with color: return drawed gradiented color
case text((UIColor) -> Void)
/// Draw image. - parameter closure with image: return drawed gradiented image
case image((UIImage) -> Void)
}
public struct Color {
public var colors: [UIColor]
public var locations: [NSNumber]
public init(colors: [UIColor], locations: [NSNumber] = [0, 1]) {
self.colors = colors
self.locations = locations
}
}
public enum Direction {
case leftRight, rightLeft, topBottom, bottomTop
case topLeftBottomRight, bottomRightTopLeft, topRightBottomLeft, bottomLeftTopRight
var points: (CGPoint, CGPoint) {
switch self {
case .leftRight: return (CGPoint(x: 0, y: 0.5), CGPoint(x: 1, y: 0.5))
case .rightLeft: return (CGPoint(x: 1, y: 0.5), CGPoint(x: 0, y: 0.5))
case .topBottom: return (CGPoint(x: 0.5, y: 0), CGPoint(x: 0.5, y: 1))
case .bottomTop: return (CGPoint(x: 0.5, y: 1), CGPoint(x: 0.5, y: 0))
case .topLeftBottomRight: return (CGPoint(x: 0, y: 0), CGPoint(x: 1, y: 1))
case .bottomRightTopLeft: return (CGPoint(x: 1, y: 1), CGPoint(x: 0, y: 0))
case .topRightBottomLeft: return (CGPoint(x: 1, y: 0), CGPoint(x: 0, y: 1))
case .bottomLeftTopRight: return (CGPoint(x: 0, y: 1), CGPoint(x: 1, y: 0))
}
}
}
}
private enum Keys {
static let kGradientLayer = "kGradientLayer"
}
extension UIView: Gradientable {
public var gradientLayer: CAGradientLayer? {
set {
guard let grLayer = newValue else { return }
grLayer.name = Keys.kGradientLayer
}
get {
return layer.sublayers?.first(where: { $0.name == Keys.kGradientLayer }) as? CAGradientLayer
}
}
public func setGradient(color: Gradient.Color, style: Gradient.Style, direction: Gradient.Direction) {
let gradient = CAGradientLayer()
gradient.name = Keys.kGradientLayer
gradient.colors = color.colors.map({ $0.cgColor })
gradient.locations = color.locations
gradient.startPoint = direction.points.0
gradient.endPoint = direction.points.1
gradient.backgroundColor = UIColor.clear.cgColor
gradient.mask = nil
gradient.opacity = 1
gradient.frame = bounds
switch style {
case .border(let lineWidth):
drawBorderGradient(gradient, lineWidth: lineWidth)
drawFillGradient(gradient)
case .fill:
drawFillGradient(gradient)
case .image(let setImageClosure):
drawImageGradient(gradient, setClosure: setImageClosure)
case .mask(let opacity):
drawMaskGradient(gradient, opacity: opacity)
case .text(let setColorClosure):
drawTextGradient(gradient, setClosure: setColorClosure)
}
}
public func removeGradientLayer() {
guard let index = layer.sublayers?.index(where: { $0.name == Keys.kGradientLayer }) else { return }
layer.sublayers?.remove(at: index)
}
private func drawFillGradient(_ gradient: CAGradientLayer) {
if let availableGradient = gradientLayer {
layer.replaceSublayer(availableGradient, with: gradient)
} else {
layer.insertSublayer(gradient, at: 0)
}
}
private func drawBorderGradient(_ gradient: CAGradientLayer, lineWidth: CGFloat) {
let mask = CAShapeLayer()
mask.lineWidth = lineWidth
mask.path = UIBezierPath(roundedRect: bounds, cornerRadius: layer.cornerRadius).cgPath
mask.strokeColor = UIColor.black.cgColor
mask.fillColor = UIColor.clear.cgColor
gradient.mask = mask
}
private func drawTextGradient(_ gradient: CAGradientLayer, setClosure: (UIColor) -> Void) {
if let label = self as? UILabel {
gradient.frame = label.textRect(forBounds: bounds, limitedToNumberOfLines: label.numberOfLines)
} else if let button = self as? UIButton {
gradient.frame = button.titleRect(forContentRect: bounds)
} else {
gradient.frame = .zero
}
let renderer = UIGraphicsImageRenderer(bounds: bounds)
let gradientImage = renderer.image { (context) in
gradient.render(in: context.cgContext)
}
let gradientColor = UIColor(patternImage: gradientImage)
setClosure(gradientColor)
}
private func drawMaskGradient(_ gradient: CAGradientLayer, opacity: Float) {
gradient.opacity = opacity
if let availableGradient = gradientLayer {
layer.replaceSublayer(availableGradient, with: gradient)
} else {
let index = (layer.sublayers?.count ?? 1) - 1
layer.insertSublayer(gradient, at: UInt32(index))
}
}
private func drawImageGradient(_ gradient: CAGradientLayer, setClosure: (UIImage) -> Void) {
let renderer = UIGraphicsImageRenderer(bounds: bounds)
let gradientImage = renderer.image { (context) in
gradient.render(in: context.cgContext)
}
setClosure(gradientImage)
}
}
public extension UIColor {
public convenience init(gradientColor: Gradient.Color, frame: CGRect, direction: Gradient.Direction = .leftRight) {
let gradientLayer = CAGradientLayer()
gradientLayer.colors = gradientColor.colors.map({ $0.cgColor })
gradientLayer.locations = gradientColor.locations
gradientLayer.startPoint = direction.points.0
gradientLayer.endPoint = direction.points.1
gradientLayer.frame = frame
let renderer = UIGraphicsImageRenderer(size: frame.size)
let gradientImage = renderer.image { (context) in
gradientLayer.render(in: context.cgContext)
}
self.init(patternImage: gradientImage)
}
}
@bilaldurnagol
Copy link

How to Unit Test those gradient methods?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment