import Foundation import UIKit import Display import ComponentFlow import GlassBackgroundComponent private final class RestingBackgroundView: UIVisualEffectView { var isDark: Bool? static func colorMatrix(isDark: Bool) -> [Float32] { if isDark { return [1.082, -0.113, -0.011, 0.0, 0.135, -0.034, 1.003, -0.011, 0.0, 0.135, -0.034, -0.113, 1.105, 0.0, 0.135, 0.0, 0.0, 0.0, 1.0, 0.0] } else { return [1.185, -0.05, -0.005, 0.0, -0.2, -0.015, 1.15, -0.005, 0.0, -0.2, -0.015, -0.05, 1.195, 0.0, -0.2, 0.0, 0.0, 0.0, 1.0, 0.0] } } init() { let effect = UIBlurEffect(style: .light) super.init(effect: effect) for subview in self.subviews { if subview.description.contains("VisualEffectSubview") { subview.isHidden = true } } self.clipsToBounds = true } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func update(isDark: Bool) { if self.isDark == isDark { return } self.isDark = isDark if let sublayer = self.layer.sublayers?[0], let _ = sublayer.filters { sublayer.backgroundColor = nil sublayer.isOpaque = false if let classValue = NSClassFromString("CAFilter") as AnyObject as? NSObjectProtocol { let makeSelector = NSSelectorFromString("filterWithName:") let filter = classValue.perform(makeSelector, with: "colorMatrix").takeUnretainedValue() as? NSObject if let filter { var matrix: [Float32] = RestingBackgroundView.colorMatrix(isDark: isDark) filter.setValue(NSValue(bytes: &matrix, objCType: "{CAColorMatrix=ffffffffffffffffffff}"), forKey: "inputColorMatrix") sublayer.filters = [filter] sublayer.setValue(1.0, forKey: "scale") } } } } } public final class LiquidLensView: UIView { private struct Params: Equatable { var size: CGSize var selectionX: CGFloat var selectionWidth: CGFloat var isDark: Bool var isLifted: Bool var isCollapsed: Bool init(size: CGSize, selectionX: CGFloat, selectionWidth: CGFloat, isDark: Bool, isLifted: Bool, isCollapsed: Bool) { self.size = size self.selectionX = selectionX self.selectionWidth = selectionWidth self.isLifted = isLifted self.isDark = isDark self.isCollapsed = isCollapsed } } private struct LensParams: Equatable { var baseFrame: CGRect var isLifted: Bool init(baseFrame: CGRect, isLifted: Bool) { self.baseFrame = baseFrame self.isLifted = isLifted } } private let containerView: UIView private let backgroundContainer: GlassBackgroundContainerView? private let genericBackgroundContainer: UIView? private let backgroundView: GlassBackgroundView private var lensView: UIView? private let liftedContainerView: UIView public let contentView: UIView private let restingBackgroundView: RestingBackgroundView private var legacySelectionView: GlassBackgroundView.ContentImageView? private var legacyContentMaskView: UIView? private var legacyContentMaskBlobView: UIImageView? private var legacyLiftedContentBlobMaskView: UIImageView? public var selectedContentView: UIView { return self.liftedContainerView } private var params: Params? private var appliedLensParams: LensParams? private var isApplyingLensParams: Bool = false private var pendingLensParams: LensParams? private var liftedDisplayLink: SharedDisplayLinkDriver.Link? public var selectionX: CGFloat? { return self.params?.selectionX } public var selectionWidth: CGFloat? { return self.params?.selectionWidth } public init(useBackgroundContainer: Bool = true) { self.containerView = UIView() if useBackgroundContainer { self.backgroundContainer = GlassBackgroundContainerView() self.genericBackgroundContainer = nil } else { self.backgroundContainer = nil self.genericBackgroundContainer = UIView() } self.backgroundView = GlassBackgroundView() self.contentView = UIView() self.liftedContainerView = UIView() self.restingBackgroundView = RestingBackgroundView() super.init(frame: CGRect()) if let backgroundContainer = self.backgroundContainer { self.addSubview(backgroundContainer) backgroundContainer.contentView.addSubview(self.backgroundView) } else if let genericBackgroundContainer = self.genericBackgroundContainer { self.addSubview(genericBackgroundContainer) genericBackgroundContainer.addSubview(self.backgroundView) } self.backgroundView.contentView.addSubview(self.containerView) self.containerView.isUserInteractionEnabled = false if #available(iOS 26.0, *) { if let viewClass = NSClassFromString("_UILiquidLensView") as AnyObject as? NSObjectProtocol { let allocSelector = NSSelectorFromString("alloc") let initSelector = NSSelectorFromString("initWithRestingBackground:") let objcAlloc = viewClass.perform(allocSelector).takeUnretainedValue() let instance = objcAlloc.perform(initSelector, with: UIView()).takeUnretainedValue() self.lensView = instance as? UIView } } if let lensView = self.lensView { if let backgroundContainer = self.backgroundContainer { backgroundContainer.layer.zPosition = 1 } else if let genericBackgroundContainer = self.genericBackgroundContainer{ genericBackgroundContainer.layer.zPosition = 1 } lensView.layer.zPosition = 10.0 self.liftedContainerView.addSubview(self.restingBackgroundView) self.containerView.addSubview(self.liftedContainerView) self.containerView.addSubview(lensView) self.containerView.addSubview(self.contentView) if let backgroundContainer = self.backgroundContainer { lensView.perform(NSSelectorFromString("setLiftedContainerView:"), with: backgroundContainer.contentView) } else if let genericBackgroundContainer = self.genericBackgroundContainer { lensView.perform(NSSelectorFromString("setLiftedContainerView:"), with: genericBackgroundContainer) } lensView.perform(NSSelectorFromString("setLiftedContentView:"), with: self.liftedContainerView) lensView.perform(NSSelectorFromString("setOverridePunchoutView:"), with: self.contentView) do { let selector = NSSelectorFromString("setLiftedContentMode:") if let method = lensView.method(for: selector) { typealias ObjCMethod = @convention(c) (AnyObject, Selector, Int32) -> Void let function = unsafeBitCast(method, to: ObjCMethod.self) function(lensView, selector, 1) } } do { let selector = NSSelectorFromString("setStyle:") if let method = lensView.method(for: selector) { typealias ObjCMethod = @convention(c) (AnyObject, Selector, Int32) -> Void let function = unsafeBitCast(method, to: ObjCMethod.self) function(lensView, selector, 1) } } do { let selector = NSSelectorFromString("setWarpsContentBelow:") if let method = lensView.method(for: selector) { typealias ObjCMethod = @convention(c) (AnyObject, Selector, Bool) -> Void let function = unsafeBitCast(method, to: ObjCMethod.self) function(lensView, selector, true) } } lensView.setValue(UIColor(white: 0.0, alpha: 0.1), forKey: "restingBackgroundColor") } else { let legacySelectionView = GlassBackgroundView.ContentImageView() self.legacySelectionView = legacySelectionView self.backgroundView.contentView.insertSubview(legacySelectionView, at: 0) let legacyContentMaskView = UIView() legacyContentMaskView.backgroundColor = .white self.legacyContentMaskView = legacyContentMaskView self.contentView.mask = legacyContentMaskView if let filter = CALayer.luminanceToAlpha() { legacyContentMaskView.layer.filters = [filter] } let legacyContentMaskBlobView = UIImageView() self.legacyContentMaskBlobView = legacyContentMaskBlobView legacyContentMaskView.addSubview(legacyContentMaskBlobView) self.containerView.addSubview(self.contentView) let legacyLiftedContentBlobMaskView = UIImageView() self.legacyLiftedContentBlobMaskView = legacyLiftedContentBlobMaskView self.liftedContainerView.mask = legacyLiftedContentBlobMaskView self.containerView.addSubview(self.liftedContainerView) } } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } public func update(size: CGSize, selectionX: CGFloat, selectionWidth: CGFloat, isDark: Bool, isLifted: Bool, isCollapsed: Bool = false, transition: ComponentTransition) { let params = Params(size: size, selectionX: selectionX, selectionWidth: selectionWidth, isDark: isDark, isLifted: isLifted, isCollapsed: isCollapsed) if self.params == params { return } self.update(params: params, transition: transition) } private func update(transition: ComponentTransition) { guard let params = self.params else { return } self.update(params: params, transition: transition) } private func updateLens(params: LensParams, animated: Bool) { guard let lensView = self.lensView else { return } if self.isApplyingLensParams { self.pendingLensParams = params return } self.isApplyingLensParams = true let previousParams = self.appliedLensParams let transition: ComponentTransition = animated ? .easeInOut(duration: 0.3) : .immediate if previousParams?.isLifted != params.isLifted { let selector = NSSelectorFromString("setLifted:animated:alongsideAnimations:completion:") var shouldScheduleUpdate = false var didProcessUpdate = false self.pendingLensParams = params if let lensView = self.lensView, let method = lensView.method(for: selector) { typealias ObjCMethod = @convention(c) (AnyObject, Selector, Bool, Bool, @escaping () -> Void, AnyObject?) -> Void let function = unsafeBitCast(method, to: ObjCMethod.self) function(lensView, selector, params.isLifted, !transition.animation.isImmediate, { [weak self] in guard let self else { return } let liftedInset: CGFloat = params.isLifted ? 4.0 : -4.0 lensView.bounds = CGRect(origin: CGPoint(), size: CGSize(width: params.baseFrame.width + liftedInset * 2.0, height: params.baseFrame.height + liftedInset * 2.0)) didProcessUpdate = true if shouldScheduleUpdate { DispatchQueue.main.async { [weak self] in guard let self, let pendingLensParams = self.pendingLensParams else { return } self.isApplyingLensParams = false self.pendingLensParams = nil self.updateLens(params: pendingLensParams, animated: !transition.animation.isImmediate) } } }, nil) } if didProcessUpdate { transition.animateView { lensView.center = CGPoint(x: params.baseFrame.midX, y: params.baseFrame.midY) } self.pendingLensParams = nil self.isApplyingLensParams = false } else { shouldScheduleUpdate = true } } else { transition.animateView { let liftedInset: CGFloat = params.isLifted ? 4.0 : -4.0 lensView.bounds = CGRect(origin: CGPoint(), size: CGSize(width: params.baseFrame.width + liftedInset * 2.0, height: params.baseFrame.height + liftedInset * 2.0)) lensView.center = CGPoint(x: params.baseFrame.midX, y: params.baseFrame.midY) } self.isApplyingLensParams = false } } private func updateLiftedLensPosition() { // Without this, the lens won't update its bouncing animations unless it's being moved if self.isApplyingLensParams { return } guard let lensView = self.lensView else { return } guard let params = self.appliedLensParams else { return } lensView.center = CGPoint(x: params.baseFrame.midX, y: params.baseFrame.midY) } private func update(params: Params, transition: ComponentTransition) { let isFirstTime = self.params == nil let transition: ComponentTransition = isFirstTime ? .immediate : transition self.params = params transition.setFrame(view: self.containerView, frame: CGRect(origin: CGPoint(), size: params.size)) if let backgroundContainer = self.backgroundContainer { transition.setFrame(view: backgroundContainer, frame: CGRect(origin: CGPoint(), size: params.size)) backgroundContainer.update(size: params.size, isDark: params.isDark, transition: transition) } else if let genericBackgroundContainer = self.genericBackgroundContainer { transition.setFrame(view: genericBackgroundContainer, frame: CGRect(origin: CGPoint(), size: params.size)) } transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: params.size)) self.backgroundView.update(size: params.size, cornerRadius: params.size.height * 0.5, isDark: params.isDark, tintColor: GlassBackgroundView.TintColor.init(kind: .panel, color: UIColor(white: params.isDark ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: transition) if self.contentView.bounds.size != params.size { self.contentView.clipsToBounds = true transition.setFrame(view: self.contentView, frame: CGRect(origin: CGPoint(), size: params.size), completion: { [weak self] completed in guard let self, completed else { return } self.contentView.clipsToBounds = false }) transition.setCornerRadius(layer: self.contentView.layer, cornerRadius: params.size.height * 0.5) self.liftedContainerView.clipsToBounds = true transition.setFrame(view: self.liftedContainerView, frame: CGRect(origin: CGPoint(), size: params.size), completion: { [weak self] completed in guard let self, completed else { return } self.liftedContainerView.clipsToBounds = false }) transition.setCornerRadius(layer: self.liftedContainerView.layer, cornerRadius: params.size.height * 0.5) } let baseLensFrame = CGRect(origin: CGPoint(x: max(0.0, min(params.selectionX, params.size.width - params.selectionWidth)), y: 0.0), size: CGSize(width: params.selectionWidth, height: params.size.height)) self.updateLens(params: LensParams(baseFrame: baseLensFrame, isLifted: params.isLifted), animated: !transition.animation.isImmediate) if let legacyContentMaskView = self.legacyContentMaskView { transition.setFrame(view: legacyContentMaskView, frame: CGRect(origin: CGPoint(), size: params.size)) } if let legacyContentMaskBlobView = self.legacyContentMaskBlobView, let legacyLiftedContentBlobMaskView = self.legacyLiftedContentBlobMaskView, let legacySelectionView = self.legacySelectionView { let lensFrame = baseLensFrame.insetBy(dx: 4.0, dy: 4.0) let effectiveLensFrame = lensFrame.insetBy(dx: params.isLifted ? -2.0 : 0.0, dy: params.isLifted ? -2.0 : 0.0) if legacyContentMaskBlobView.image?.size.height != lensFrame.height { legacyContentMaskBlobView.image = generateStretchableFilledCircleImage(diameter: lensFrame.height, color: .black) legacyLiftedContentBlobMaskView.image = legacyContentMaskBlobView.image legacySelectionView.image = generateStretchableFilledCircleImage(diameter: lensFrame.height, color: .white)?.withRenderingMode(.alwaysTemplate) } transition.setFrame(view: legacyContentMaskBlobView, frame: effectiveLensFrame) transition.setFrame(view: legacyLiftedContentBlobMaskView, frame: effectiveLensFrame) legacySelectionView.tintColor = UIColor(white: params.isDark ? 1.0 : 0.0, alpha: params.isDark ? 0.1 : 0.075) transition.setFrame(view: legacySelectionView, frame: effectiveLensFrame) } transition.setFrame(view: self.restingBackgroundView, frame: CGRect(origin: CGPoint(), size: params.size)) self.restingBackgroundView.update(isDark: params.isDark) transition.setAlpha(view: self.restingBackgroundView, alpha: (params.isLifted || params.isCollapsed) ? 0.0 : 1.0) if params.isLifted { if self.liftedDisplayLink == nil { self.liftedDisplayLink = SharedDisplayLinkDriver.shared.add(framesPerSecond: .max, { [weak self] _ in guard let self else { return } self.updateLiftedLensPosition() }) } } else if let liftedDisplayLink = self.liftedDisplayLink { self.liftedDisplayLink = nil liftedDisplayLink.invalidate() } } }