// Based on https://github.com/aheze/VariableBlurView/blob/main/Sources/VariableBlurView.swift import UIKit public protocol ViewBlurring: UIView { var blurRadius: CGFloat? { get set } } extension UIView: ViewBlurring { private var blurRadiusKey: String { "inputRadius" } private var blurFilterName: String { "blurFilter" } private var blurKeyPath: String { "filters.\(blurFilterName).\(blurRadiusKey)" } /// Adds a blur filter to the view’s `CALayer`, applying the provided radius. Removes the filter when set to `nil`. /// Can be animated. public var blurRadius: CGFloat? { get { blurFilter?.value(forKey: blurRadiusKey) as? CGFloat } set { let startValue = blurRadius ?? 0 let endValue = newValue ?? 0 guard newValue != blurRadius else { return } guard let newValue else { // TODO: Maybe remove filter after any animations have completed removeBlurFilter() return } if blurFilter == nil { addBlurFilter() } addBlurActionIfNeeded(from: startValue, to: endValue) layer.setValue(newValue, forKeyPath: blurKeyPath) } } } extension UIView { public func addBlurActionIfNeeded(from startValue: CGFloat, to endValue: CGFloat) { guard let action = action(for: layer, forKey: "backgroundColor") as? NSObject, let animation = action.copy() as? CABasicAnimation else { return } animation.keyPath = blurKeyPath animation.fromValue = startValue animation.toValue = endValue layer.add(animation, forKey: "blurAnimation") } } extension UIView { private var blurFilter: NSObject? { layer.filters?.first as? NSObject } private func addBlurFilter() { guard let gaussianBlurFilter = Self.createBlurFilter() else { return } gaussianBlurFilter.setValue(blurFilterName, forKey: "name") gaussianBlurFilter.setValue(0, forKey: blurRadiusKey) layer.filters = [gaussianBlurFilter] } private func removeBlurFilter() { layer.filters = [] } } extension UIView { private static func decodedString(forKey key: String) -> String? { Data(base64Encoded: key).flatMap { String(data: $0, encoding: .utf8) } } private static func createBlurFilter() -> NSObject? { // Shenanigans guard let selectorName = decodedString(forKey: "ZmlsdGVyV2l0aFR5cGU6") else { return nil } let filterWithTypeSelector = Selector(selectorName) guard let className = decodedString(forKey: "Q0FGaWx0ZXI="), let filterClass = NSClassFromString(className) as AnyObject as? NSObjectProtocol, filterClass.responds(to: filterWithTypeSelector) else { return nil } guard let filterType = decodedString(forKey: "Z2F1c3NpYW5CbHVy"), let gaussianBlurFilter = filterClass .perform(filterWithTypeSelector, with: filterType) .takeUnretainedValue() as? NSObject else { return nil } return gaussianBlurFilter } }