Created
November 5, 2018 19:48
-
-
Save Ikloo/039fce762b547e684f76795b248e2922 to your computer and use it in GitHub Desktop.
Gradientable protocol for UIView
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
How to Unit Test those gradient methods?