Skip to content

Instantly share code, notes, and snippets.

@jakehawken
Last active August 24, 2023 21:10
Show Gist options
  • Save jakehawken/93ec9a107a72f1b58ee8dfd19ff3d3a3 to your computer and use it in GitHub Desktop.
Save jakehawken/93ec9a107a72f1b58ee8dfd19ff3d3a3 to your computer and use it in GitHub Desktop.
Shadows or rounded corners. To that I say ¿Porque no los dos?
extension UIView {
/// Adds the subview to a container with shadows on it and returns the container.
/// Primary use case is for a subview that clips to its bounds, and therefore can't
/// have a shadow on its own `layer`. The shadow will match the corner radius of
/// the contained view.
static func shadowContainerFor(
_ subview: UIView,
shadowColor: UIColor? = .darkGray,
shadowRadius: CGFloat = 2,
shadowOffset: CGSize = CGSize(width: 0, height: 2),
shadowOpacity: Float = 1
) -> UIView {
ShadowBox(
view: subview,
shadowColor: shadowColor,
shadowRadius: shadowRadius,
shadowOffset: shadowOffset,
shadowOpacity: shadowOpacity
)
}
/// Adds the view to a container with shadows on it and returns the container.
/// Primary use case is for a view that clips to its bounds, and therefore can't
/// have a shadow on its own `layer`. The shadow will match the corner radius of
/// the contained view.
func inShadowBox(
shadowColor: UIColor? = .darkGray,
shadowRadius: CGFloat = 2,
shadowOffset: CGSize = CGSize(width: 0, height: 2),
shadowOpacity: Float = 1
) -> UIView {
ShadowBox(
view: self,
shadowColor: shadowColor,
shadowRadius: shadowRadius,
shadowOffset: shadowOffset,
shadowOpacity: shadowOpacity
)
}
private class ShadowBox: UIView {
private let mainView: UIView
required init(
view: UIView,
shadowColor: UIColor?,
shadowRadius: CGFloat,
shadowOffset: CGSize,
shadowOpacity: Float
) {
self.containedView = view
super.init(frame: .zero)
addSubviewAndPinToAllSides(containedView)
self.addShadow(
shadowColor: shadowColor,
shadowRadius: shadowRadius,
shadowOffset: shadowOffset,
shadowOpacity: shadowOpacity,
shadowPath: UIBezierPath(
roundedRect: self.bounds,
cornerRadius: containedView.cornerRadius * shadowRadius
).cgPath
)
}
override func layoutSubviews() {
super.layoutSubviews()
layer.shadowPath = UIBezierPath(
roundedRect: self.bounds,
cornerRadius: containedView.cornerRadius * layer.shadowRadius
).cgPath
}
required init?(coder: NSCoder) {
fatalError(
"init(coder:) prohibited. Please use required init "
+= "init(view:shadowColor:shadowRadius:shadowOffset:shadowOpacity:) instead."
)
}
}
private func addSubviewAndPinToAllSides(_ subview: UIView) {
subview.translatesAutoresizingMaskIntoConstraints = false
addSubview(subview)
NSLayoutConstraint.activate([
subview.topAnchor.constraint(equalTo: topAnchor),
subview.leadingAnchor.constraint(equalTo: leadingAnchor),
subview.trailingAnchor.constraint(equalTo: trailingAnchor),
subview.bottomAnchor.constraint(equalTo: bottomAnchor),
])
setNeedsLayout()
}
private func addShadow(
shadowColor: UIColor? = .darkGray,
shadowRadius: CGFloat = 2,
shadowOffset: CGSize = CGSize(width: 0, height: 2),
shadowOpacity: Float = 1,
shadowPath: CGPath? = nil
) {
if let shadowPath {
layer.shadowPath = shadowPath
}
layer.shadowColor = shadowColor?.cgColor
layer.shadowRadius = shadowRadius
layer.shadowOffset = shadowOffset
layer.shadowOpacity = shadowOpacity
clipsToBounds = false
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment