// // UIView+CALayer.swift // VDS // // Created by Matt Bruce on 6/15/23. // import Foundation import UIKit import VDSTokens //-------------------------------------------------- // MARK: - Debug Borders //-------------------------------------------------- extension UIView { internal func removeDebugBorder() { layer.remove(layerName: "debug") } internal func addDebugBorder(color: UIColor = .red) { //ensure you remove existing removeDebugBorder() //add bounds border let borderLayer = CALayer() borderLayer.name = "debugAreaLayer" borderLayer.frame = bounds borderLayer.bounds = bounds borderLayer.borderWidth = VDSFormControls.borderWidth borderLayer.borderColor = color.cgColor layer.addSublayer(borderLayer) //add touchborder if applicable if type(of: self) is AppleGuidelinesTouchable.Type { let faultToleranceX: CGFloat = max((45 - bounds.size.width) / 2.0, 0) let faultToleranceY: CGFloat = max((45 - bounds.size.height) / 2.0, 0) let touchableAreaPath = UIBezierPath(rect: bounds.insetBy(dx: -faultToleranceX, dy: -faultToleranceY)) let touchLayer = CAShapeLayer() touchLayer.path = touchableAreaPath.cgPath touchLayer.strokeColor = color.cgColor touchLayer.fillColor = UIColor.clear.cgColor touchLayer.lineWidth = VDSFormControls.borderWidth touchLayer.opacity = 1.0 touchLayer.name = "debugTouchableAreaLayer" touchLayer.zPosition = 100 touchLayer.frame = bounds touchLayer.bounds = bounds layer.addSublayer(touchLayer) } } public var hasDebugBorder: Bool { guard let layers = layer.sublayers else { return false } return layers.compactMap{$0.name}.filter{$0.hasPrefix("debug")}.count > 0 } public func debugBorder(show shouldShow: Bool = true, color: UIColor = .red) { if shouldShow { addDebugBorder(color: color) } else { removeDebugBorder() } if let view = self as? ViewProtocol { view.updateView() } } } //-------------------------------------------------- // MARK: - CALayer //-------------------------------------------------- extension CALayer { /// Removes a sublayer of a specific name. /// - Parameter layerName: Layer with the name you want to remove. public func remove(layerName: String) { guard let sublayers = sublayers else { return } sublayers.forEach({ layer in if layer.name?.hasPrefix(layerName) ?? false { layer.removeFromSuperlayer() } }) } } //-------------------------------------------------- // MARK: - Borders //-------------------------------------------------- extension UIView { /// Adds a border to a specific sides with the parameters. /// - Parameters: /// - side: Side in which to add the border. /// - width: Width of the border. /// - color: Color of the border. /// - offset: offset of the border. public func addBorder(side: UIRectEdge, width: CGFloat, color: UIColor, offset: CGFloat = 0) { let layerName = borderLayerName(for: side) layer.remove(layerName: layerName) let borderLayer = CALayer() borderLayer.backgroundColor = color.cgColor borderLayer.name = layerName switch side { case .left: borderLayer.frame = CGRect(x: 0, y: 0, width: width, height: frame.height) case .right: borderLayer.frame = CGRect(x: frame.width - width - offset, y: 0, width: width, height: frame.height) case .top: borderLayer.frame = CGRect(x: 0, y: 0, width: frame.width, height: width) case .bottom: borderLayer.frame = CGRect(x: 0, y: frame.height - width - offset, width: frame.width, height: width) default: break } layer.addSublayer(borderLayer) } /// Removes all of the borders addeding using the addBorder method. public func removeBorders() { layer.borderWidth = 0 layer.borderColor = nil layer.remove(layerName: borderLayerName(for: .top)) layer.remove(layerName: borderLayerName(for: .left)) layer.remove(layerName: borderLayerName(for: .right)) layer.remove(layerName: borderLayerName(for: .bottom)) } private func borderLayerName(for side: UIRectEdge) -> String { switch side { case .left: return "leftBorderLayer" case .right: return "rightBorderLayer" case .top: return "topBorderLayer" case .bottom: return "bottomBorderLayer" default: return "" } } } extension UIView { fileprivate var bezierPathIdentifier: String { return "bezierPathBorderLayer" } fileprivate var bezierPathBorder: CAShapeLayer? { return (self.layer.sublayers?.filter({ (layer) -> Bool in return layer.name == self.bezierPathIdentifier && (layer as? CAShapeLayer) != nil }) as? [CAShapeLayer])?.first } public func bezierPathBorder(_ color: UIColor = .white, width: CGFloat = 1) { var border = self.bezierPathBorder let path = UIBezierPath(roundedRect: self.bounds, cornerRadius:self.layer.cornerRadius) let mask = CAShapeLayer() mask.path = path.cgPath self.layer.mask = mask if (border == nil) { border = CAShapeLayer() border!.name = self.bezierPathIdentifier self.layer.addSublayer(border!) } border!.frame = self.bounds let pathUsingCorrectInsetIfAny = UIBezierPath(roundedRect: border!.bounds, cornerRadius:self.layer.cornerRadius) border!.path = pathUsingCorrectInsetIfAny.cgPath border!.fillColor = UIColor.clear.cgColor border!.strokeColor = color.cgColor border!.lineWidth = width * 2 } public func removeBezierPathBorder() { self.layer.mask = nil self.bezierPathBorder?.removeFromSuperlayer() } }