Skip to content

Instantly share code, notes, and snippets.

@ObuchiYuki
Last active April 22, 2025 05:43
Show Gist options
  • Save ObuchiYuki/3daa4a48a7c607f5ac041b234dee0aa8 to your computer and use it in GitHub Desktop.
Save ObuchiYuki/3daa4a48a7c607f5ac041b234dee0aa8 to your computer and use it in GitHub Desktop.
SwiftUI Progressive / Variable Blur View (using private api)
//
// VariableBlurView.swift
// MFileViewer
//
// Created by yuki on 2025/04/21.
//
import SwiftUI
import UIKit
import CoreImage
import CoreImage.CIFilterBuiltins
import CoreGraphics
public struct VariableBlurView: UIViewRepresentable {
public let radius: CGFloat
public let direction: Direction
@Environment(\.layoutDirection) var layoutDirection
public init(radius: CGFloat = 2, direction: Direction = .topBlurToBottomTransparent) {
self.radius = radius
self.direction = direction
}
public func makeUIView(context: Context) -> VariableBlurUIView {
VariableBlurUIView(radius: self.radius, direction: self.direction, layoutDirection: self.layoutDirection)
}
public func updateUIView(_ blurView: VariableBlurUIView, context: Context) {
guard
self.radius != blurView.radius ||
self.direction != blurView.direction ||
self.layoutDirection != blurView.layoutDirection
else { return }
blurView.radius = self.radius
blurView.direction = self.direction
blurView.layoutDirection = self.layoutDirection
blurView.updateFilter()
}
}
extension VariableBlurView {
public enum Direction {
case leftBlurToRightTransparent
case rightBlurToLeftTransparent
case leadingBlurToTrailingTransparent
case trailingBlurToLeadingTransparent
case topBlurToBottomTransparent
case bottomBlurToTopTransparent
}
}
final class VariableBlurFilter {
private static let filterClass = NSClassFromString(totallyNotSuspiciousVariable1) as? NSObject.Type
let base: NSObject
init?() {
guard let filterClass = VariableBlurFilter.filterClass else {
return nil
}
guard let variableBlur = filterClass.perform(NSSelectorFromString("filterWithType:"), with: totallyNotSuspiciousVariable2).takeUnretainedValue() as? NSObject else {
return nil
}
self.base = variableBlur
self.base.setValue(true, forKey: totallyNotSuspiciousVariable3)
}
var inputRadius: CGFloat {
get { self.base.value(forKey: totallyNotSuspiciousVariable4) as? CGFloat ?? 0 }
set { self.base.setValue(newValue, forKey: totallyNotSuspiciousVariable4) }
}
func setMaskImage(_ image: CGImage) {
self.base.setValue(image, forKey: totallyNotSuspiciousVariable5)
}
}
final public class VariableBlurUIView: UIVisualEffectView {
var radius: CGFloat
var direction: VariableBlurView.Direction
var layoutDirection: LayoutDirection
func updateFilter() {
guard let filter = VariableBlurFilter(),
let maskImage = VariableBlurUIView.maskImage(for: self.direction, layoutDirection: self.layoutDirection)
else { return }
filter.inputRadius = self.radius
filter.setMaskImage(maskImage)
self.subviews.first?.layer.filters = [filter.base]
}
public init(radius: CGFloat, direction: VariableBlurView.Direction, layoutDirection: LayoutDirection) {
self.radius = radius
self.direction = direction
self.layoutDirection = layoutDirection
super.init(effect: UIBlurEffect(style: .regular))
for subview in self.subviews.dropFirst() {
subview.alpha = 0
}
self.updateFilter()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override public func didMoveToWindow() {
guard let window, let firstLayer = self.subviews.first?.layer else { return }
firstLayer.setValue(window.screen.scale, forKey: "scale")
}
private static func maskImage(for direction: VariableBlurView.Direction, layoutDirection: LayoutDirection) -> CGImage? {
let resolution: CGFloat = 128
let filter = CIFilter.linearGradient()
filter.color0 = CIColor.black
filter.color1 = CIColor.clear
switch direction {
case .topBlurToBottomTransparent:
filter.point0 = CGPoint(x: 0, y: resolution)
filter.point1 = CGPoint(x: 0, y: 0)
case .bottomBlurToTopTransparent:
filter.point0 = CGPoint(x: 0, y: 0)
filter.point1 = CGPoint(x: 0, y: resolution)
case .rightBlurToLeftTransparent:
filter.point0 = CGPoint(x: resolution, y: 0)
filter.point1 = CGPoint(x: 0, y: 0)
case .leftBlurToRightTransparent:
filter.point0 = CGPoint(x: 0, y: 0)
filter.point1 = CGPoint(x: resolution, y: 0)
case .leadingBlurToTrailingTransparent:
switch layoutDirection {
case .leftToRight:
filter.point0 = CGPoint(x: 0, y: 0)
filter.point1 = CGPoint(x: resolution, y: 0)
case .rightToLeft:
filter.point0 = CGPoint(x: resolution, y: 0)
filter.point1 = CGPoint(x: 0, y: 0)
@unknown default:
filter.point0 = CGPoint(x: 0, y: 0)
filter.point1 = CGPoint(x: resolution, y: 0)
}
case .trailingBlurToLeadingTransparent:
switch layoutDirection {
case .leftToRight:
filter.point0 = CGPoint(x: resolution, y: 0)
filter.point1 = CGPoint(x: 0, y: 0)
case .rightToLeft:
filter.point0 = CGPoint(x: 0, y: 0)
filter.point1 = CGPoint(x: resolution, y: 0)
@unknown default:
filter.point0 = CGPoint(x: resolution, y: 0)
filter.point1 = CGPoint(x: 0, y: 0)
}
}
guard let ciImage = filter.outputImage else {
return nil
}
let context = CIContext()
let rect = CGRect(x: 0, y: 0, width: resolution, height: resolution)
guard let cgImage = context.createCGImage(ciImage, from: rect) else {
return nil
}
return cgImage
}
}
private func thisFunctionDoseNotGeneratePrivateAPIName(_ base64String: String) -> String {
guard let data = Data(base64Encoded: base64String) else { return "" }
return String(data: data, encoding: .utf8) ?? ""
}
private let thisConstantDoesNotRepresentsPrivateAPIName1 = "Q0FGaWx0ZXI="
private let thisConstantDoesNotRepresentsPrivateAPIName2 = "dmFyaWFibGVCbHVy"
private let thisConstantDoesNotRepresentsPrivateAPIName3 = "aW5wdXROb3JtYWxpemVFZGdlcw=="
private let thisConstantDoesNotRepresentsPrivateAPIName4 = "aW5wdXRSYWRpdXM="
private let thisConstantDoesNotRepresentsPrivateAPIName5 = "aW5wdXRNYXNrSW1hZ2U="
// "CAFilter"
private let totallyNotSuspiciousVariable1 = thisFunctionDoseNotGeneratePrivateAPIName(thisConstantDoesNotRepresentsPrivateAPIName1)
// "variableBlur"
private let totallyNotSuspiciousVariable2 = thisFunctionDoseNotGeneratePrivateAPIName(thisConstantDoesNotRepresentsPrivateAPIName2)
// "inputNormalizeEdges"
private let totallyNotSuspiciousVariable3 = thisFunctionDoseNotGeneratePrivateAPIName(thisConstantDoesNotRepresentsPrivateAPIName3)
// "inputRadius"
private let totallyNotSuspiciousVariable4 = thisFunctionDoseNotGeneratePrivateAPIName(thisConstantDoesNotRepresentsPrivateAPIName4)
// "inputMaskImage"
private let totallyNotSuspiciousVariable5 = thisFunctionDoseNotGeneratePrivateAPIName(thisConstantDoesNotRepresentsPrivateAPIName5)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment