Last active
December 5, 2023 07:36
-
-
Save dezinezync/724d93dc8d95fe06a80881f39802c2aa to your computer and use it in GitHub Desktop.
DZShadowsView
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
// | |
// DZShadowsView.swift | |
// | |
// | |
// Created by Nikhil Nigade on 16/12/21. | |
// | |
#if !os(watchOS) | |
#if os(iOS) | |
import UIKit | |
#elseif os(macOS) | |
import AppKit | |
#endif | |
import EasyPeasy | |
#if os(iOS) | |
public typealias RealColor = UIColor | |
#else | |
public typealias RealColor = NSColor | |
#endif | |
public final class DZShadowsView: RealView { | |
#if os(iOS) | |
public let contentView: RealView = UIView() | |
#else | |
public let contentView: RealView = FlippedView() | |
#endif | |
public struct Shadow: Hashable { | |
var radius: Double | |
var offset: CGSize | |
var color: RealColor | |
var opacity: Float | |
/// Create a shadow from params | |
/// - Parameters: | |
/// - radius: radius of the shadow. See `sketchShadow` for more info. | |
/// - offset: offset for the shadow | |
/// - color: color of the shadow | |
/// - opacity: opacity of the shadow (don't set on the color) | |
/// - sketchShadow: true if the shadow is being created from Sketch's layer panel. When true, the radius is halved. | |
public init(radius: Double, offset: CGSize, color: RealColor, opacity: Float, sketchShadow: Bool = false) { | |
self.radius = radius / (sketchShadow ? 2.0 : 1.0) | |
self.offset = offset | |
self.color = color | |
self.opacity = opacity | |
} | |
public func hash(into hasher: inout Hasher) { | |
hasher.combine(radius) | |
hasher.combine(offset.width) | |
hasher.combine(offset.height) | |
hasher.combine(opacity) | |
hasher.combine(color.description) | |
} | |
} | |
/// The shadows to setup on this view. | |
public var shadows: [Shadow] = [] { | |
didSet { | |
if oldValue != shadows { | |
setupShadows() | |
} | |
} | |
} | |
public override init(frame: CGRect) { | |
super.init(frame: frame) | |
commonInit() | |
} | |
public required init?(coder: NSCoder) { | |
super.init(coder: coder) | |
commonInit() | |
} | |
private func commonInit() { | |
#if os(iOS) | |
layer.masksToBounds = false | |
contentView.layer.masksToBounds = true | |
contentView.backgroundColor = .systemBackground | |
#elseif os(macOS) | |
contentView.wantsLayer = true | |
layer?.masksToBounds = false | |
contentView.layer?.masksToBounds = true | |
contentView.backgroundColor = .windowBackgroundColor | |
#endif | |
contentView.translatesAutoresizingMaskIntoConstraints = false | |
super.addSubview(contentView) | |
contentView.easy.layout(Edges()) | |
} | |
public override func addSubview(_ aView: RealView) { | |
contentView.addSubview(aView) | |
} | |
public func setNeedsUpdateShadows() { | |
setupShadows() | |
#if os(iOS) | |
setNeedsLayout() | |
setNeedsDisplay() | |
#elseif os(macOS) | |
layoutSubtreeIfNeeded() | |
setNeedsDisplay(frame) | |
#endif | |
} | |
private var layers: [CALayer] = [] | |
private func setupShadows() { | |
for layer in layers { | |
layer.removeFromSuperlayer() | |
} | |
for shadow in shadows { | |
let sublayer = CALayer() | |
sublayer.frame = bounds | |
#if os(iOS) | |
sublayer.cornerRadius = contentView.layer.cornerRadius | |
sublayer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: contentView.layer.cornerRadius).cgPath | |
#elseif os(macOS) | |
sublayer.cornerRadius = contentView.layer?.cornerRadius ?? 5 | |
sublayer.shadowPath = NSBezierPath(roundedRect: bounds, xRadius: contentView.layer!.cornerRadius, yRadius: contentView.layer!.cornerRadius).cgPath | |
#endif | |
sublayer.shadowOpacity = shadow.opacity | |
sublayer.shadowColor = shadow.color.cgColor | |
sublayer.shadowOffset = shadow.offset | |
sublayer.shadowRadius = shadow.radius | |
#if os(iOS) | |
layer.addSublayer(sublayer) | |
#elseif os(macOS) | |
layer?.addSublayer(sublayer) | |
#endif | |
layers.append(sublayer) | |
} | |
} | |
#if os(iOS) | |
public override func layoutSubviews() { | |
backgroundColor = .clear | |
for sublayer in layer.sublayers ?? [] { | |
sublayer.frame = bounds | |
sublayer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: contentView.layer.cornerRadius).cgPath | |
} | |
bringSubviewToFront(contentView) | |
} | |
#elseif os(macOS) | |
public override func layout() { | |
super.layout() | |
backgroundColor = .clear | |
contentView.layer!.removeFromSuperlayer() | |
for sublayer in layer?.sublayers ?? [] { | |
sublayer.frame = bounds | |
sublayer.shadowPath = NSBezierPath(roundedRect: bounds, xRadius: contentView.layer!.cornerRadius, yRadius: contentView.layer!.cornerRadius).cgPath | |
} | |
// brings subview to front | |
layer?.addSublayer(contentView.layer!) | |
} | |
#endif | |
} | |
#if canImport(Cocoa) | |
public extension NSBezierPath { | |
var cgPath: CGPath { | |
let path = CGMutablePath() | |
var points = [CGPoint](repeating: .zero, count: 3) | |
for i in 0 ..< self.elementCount { | |
let type = self.element(at: i, associatedPoints: &points) | |
switch type { | |
case .moveTo: | |
path.move(to: points[0]) | |
case .lineTo: | |
path.addLine(to: points[0]) | |
case .curveTo: | |
path.addCurve(to: points[2], control1: points[0], control2: points[1]) | |
case .closePath: | |
path.closeSubpath() | |
@unknown default: | |
break | |
} | |
} | |
return path | |
} | |
} | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment