import Foundation import UIKit import Display import ComponentFlow public final class EdgeEffectView: UIView { public enum Edge { case top case bottom } private let contentView: UIView private let contentMaskView: UIImageView private var blurView: VariableBlurView? public override init(frame: CGRect) { self.contentView = UIView() self.contentMaskView = UIImageView() self.contentView.mask = self.contentMaskView super.init(frame: frame) self.addSubview(self.contentView) } required public init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } public func update(content: UIColor, blur: Bool = false, alpha: CGFloat = 0.65, rect: CGRect, edge: Edge, edgeSize: CGFloat, transition: ComponentTransition) { transition.setBackgroundColor(view: self.contentView, color: content) switch edge { case .top: self.contentMaskView.transform = CGAffineTransformMakeScale(1.0, -1.0) case .bottom: self.contentMaskView.transform = .identity } let bounds = CGRect(origin: CGPoint(), size: rect.size) transition.setFrame(view: self.contentView, frame: bounds) transition.setFrame(view: self.contentMaskView, frame: bounds) if self.contentMaskView.image?.size.height != edgeSize { let baseGradientAlpha: CGFloat = alpha let numSteps = 8 let firstStep = 1 let firstLocation = 0.0 let colors: [UIColor] = (0 ..< numSteps).map { i in if i < firstStep { return UIColor(white: 1.0, alpha: 1.0) } else { let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1) let value: CGFloat = bezierPoint(0.42, 0.0, 0.58, 1.0, step) return UIColor(white: 1.0, alpha: baseGradientAlpha * value) } } let locations: [CGFloat] = (0 ..< numSteps).map { i in if i < firstStep { return 0.0 } else { let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1) return (firstLocation + (1.0 - firstLocation) * step) } } if edgeSize > 0.0 { self.contentMaskView.image = generateGradientImage( size: CGSize(width: 8.0, height: edgeSize), colors: colors, locations: locations )?.stretchableImage(withLeftCapWidth: 0, topCapHeight: Int(edgeSize)) } else { self.contentMaskView.image = nil } } if blur { let gradientMaskLayer = SimpleGradientLayer() let baseGradientAlpha: CGFloat = 1.0 let numSteps = 8 let firstStep = 1 let firstLocation = 0.8 gradientMaskLayer.colors = (0 ..< numSteps).map { i in if i < firstStep { return UIColor(white: 1.0, alpha: 1.0).cgColor } else { let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1) let value: CGFloat = 1.0 - bezierPoint(0.42, 0.0, 0.58, 1.0, step) return UIColor(white: 1.0, alpha: baseGradientAlpha * value).cgColor } } gradientMaskLayer.locations = (0 ..< numSteps).map { i -> NSNumber in if i < firstStep { return 0.0 as NSNumber } else { let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1) return (firstLocation + (1.0 - firstLocation) * step) as NSNumber } } let blurView: VariableBlurView if let current = self.blurView { blurView = current } else { blurView = VariableBlurView(gradientMask: self.contentMaskView.image ?? UIImage(), maxBlurRadius: 8.0) blurView.layer.mask = gradientMaskLayer self.insertSubview(blurView, at: 0) self.blurView = blurView } transition.setFrame(view: blurView, frame: bounds) if let maskLayer = blurView.layer.mask { transition.setFrame(layer: maskLayer, frame: bounds) maskLayer.transform = CATransform3DMakeScale(1.0, -1.0, 1.0) } blurView.transform = self.contentMaskView.transform } else if let blurView = self.blurView { self.blurView = nil blurView.removeFromSuperview() } } } public final class VariableBlurView: UIVisualEffectView { public let maxBlurRadius: CGFloat public var gradientMask: UIImage { didSet { if self.gradientMask !== oldValue { self.resetEffect() } } } public init(gradientMask: UIImage, maxBlurRadius: CGFloat = 20.0) { self.gradientMask = gradientMask self.maxBlurRadius = maxBlurRadius super.init(effect: UIBlurEffect(style: .regular)) self.resetEffect() if self.subviews.indices.contains(1) { let tintOverlayView = subviews[1] tintOverlayView.alpha = 0 } } required public init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override public func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { if self.traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) { self.resetEffect() } } private func resetEffect() { let filterClassStringEncoded = "Q0FGaWx0ZXI=" let filterClassString: String = { if let data = Data(base64Encoded: filterClassStringEncoded), let string = String(data: data, encoding: .utf8) { return string } return "" }() let filterWithTypeStringEncoded = "ZmlsdGVyV2l0aFR5cGU6" let filterWithTypeString: String = { if let data = Data(base64Encoded: filterWithTypeStringEncoded), let string = String(data: data, encoding: .utf8) { return string } return "" }() let filterWithTypeSelector = Selector(filterWithTypeString) guard let filterClass = NSClassFromString(filterClassString) as AnyObject as? NSObjectProtocol else { return } guard filterClass.responds(to: filterWithTypeSelector) else { return } let variableBlur = filterClass.perform(filterWithTypeSelector, with: "variableBlur").takeUnretainedValue() guard let variableBlur = variableBlur as? NSObject else { return } guard let gradientImageRef = self.gradientMask.cgImage else { return } variableBlur.setValue(self.maxBlurRadius, forKey: "inputRadius") variableBlur.setValue(gradientImageRef, forKey: "inputMaskImage") variableBlur.setValue(true, forKey: "inputNormalizeEdges") let backdropLayer = self.subviews.first?.layer backdropLayer?.filters = [variableBlur] backdropLayer?.setValue(UIScreenScale, forKey: "scale") } }