From b2f83ec23afd4360cb2524db6b812c7ac625cd3b Mon Sep 17 00:00:00 2001 From: Lasha Efremidze Date: Fri, 26 Dec 2025 14:02:29 -0800 Subject: [PATCH 01/40] added glass effect --- .../VisualEffectView+SwiftUI.swift | 39 +++++++++++++++++++ .../VisualEffectView/VisualEffectView.swift | 38 ++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/Sources/VisualEffectView/VisualEffectView+SwiftUI.swift b/Sources/VisualEffectView/VisualEffectView+SwiftUI.swift index 8c80fb4..1dcf7e7 100644 --- a/Sources/VisualEffectView/VisualEffectView+SwiftUI.swift +++ b/Sources/VisualEffectView/VisualEffectView+SwiftUI.swift @@ -53,6 +53,38 @@ public struct VisualEffect: UIViewRepresentable { */ let scale: CGFloat + public enum BlurStyle: Equatable { + case system(UIBlurEffect.Style) + case custom(radius: CGFloat, saturation: CGFloat) + } + + @available(iOS 26.0, *) + public enum GlassStyle: Equatable { + case regular + case clear + case prominent + + var value: UIGlassEffect.Style { + switch self { + case .regular: .regular + case .clear: .clear + case .prominent: .prominent + } + } + } + + public enum VisualEffectStyle: Equatable { + case blur(BlurStyle) + @available(iOS 26.0, *) + case glass(GlassStyle) + } + + @available(iOS 26.0, *) + public var glassTintColor: UIColor? + + @available(iOS 26.0, *) + public var isGlassInteractive: Bool + /** Initializes a `VisualEffect` view with the specified parameters. @@ -70,6 +102,13 @@ public struct VisualEffect: UIViewRepresentable { self.saturation = saturation self.scale = scale } + + @available(iOS 26.0, *) + public init(glass: GlassStyle, tint: UIColor? = nil, interactive: Bool = false) { + self.glass = glass + self.tint = tint + self.interactive = interactive + } public func makeUIView(context: Context) -> VisualEffectView { let view = VisualEffectView() diff --git a/Sources/VisualEffectView/VisualEffectView.swift b/Sources/VisualEffectView/VisualEffectView.swift index 0f1f0e6..0aa270d 100644 --- a/Sources/VisualEffectView/VisualEffectView.swift +++ b/Sources/VisualEffectView/VisualEffectView.swift @@ -83,6 +83,27 @@ open class VisualEffectView: UIVisualEffectView { get { return _value(forKey: .scale) ?? 1.0 } set { _setValue(newValue, forKey: .scale) } } + + public var style: VisualEffectStyle = .none { + didSet { + switch style { + + case .none: + effectView.effect = nil + + case .blur(let blur): + effectView.effect = makeBlurEffect(from: blur) + + case .glass(let glass): + if #available(iOS 26.0, *) { + effectView.effect = makeGlassEffect(from: glass) + } else { + // graceful fallback + effectView.effect = UIBlurEffect(style: .systemThinMaterial) + } + } + } + } // MARK: - Initialization @@ -121,3 +142,20 @@ private extension VisualEffectView { } // ["grayscaleTintLevel", "grayscaleTintAlpha", "lightenGrayscaleWithSourceOver", "colorTint", "colorTintAlpha", "colorBurnTintLevel", "colorBurnTintAlpha", "darkeningTintAlpha", "darkeningTintHue", "darkeningTintSaturation", "darkenWithSourceOver", "blurRadius", "saturationDeltaFactor", "scale", "zoom"] + +private extension VisualEffectView { + func makeBlurEffect(from style: BlurStyle) -> UIVisualEffect { + switch style { + case .system(let uiStyle): + return UIBlurEffect(style: uiStyle) + + case .custom: + return UIBlurEffect(style: .light) // your existing custom pipeline + } + } + + @available(iOS 26.0, *) + private func makeGlassEffect(from style: GlassStyle) -> UIVisualEffect { + return UIGlassEffect(style: style.value) + } +} From 5ac2d0c0461e3c048bf6f4f8122420bc93466fac Mon Sep 17 00:00:00 2001 From: Lasha Efremidze Date: Sun, 28 Dec 2025 15:01:17 -0800 Subject: [PATCH 02/40] Update VisualEffectView.swift --- .../VisualEffectView/VisualEffectView.swift | 45 +++++++++++++------ 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/Sources/VisualEffectView/VisualEffectView.swift b/Sources/VisualEffectView/VisualEffectView.swift index 0aa270d..58e0674 100644 --- a/Sources/VisualEffectView/VisualEffectView.swift +++ b/Sources/VisualEffectView/VisualEffectView.swift @@ -84,23 +84,31 @@ open class VisualEffectView: UIVisualEffectView { set { _setValue(newValue, forKey: .scale) } } + // MARK: - Style property + public var style: VisualEffectStyle = .none { didSet { - switch style { + apply(style: style) + } + } - case .none: - effectView.effect = nil + func apply(style: VisualEffectStyle) { + switch style { + case .none: + clearEffectWorkaroundIfNeeded() + self.effect = nil - case .blur(let blur): - effectView.effect = makeBlurEffect(from: blur) + case .blur(let blur): + clearEffectWorkaroundIfNeeded() + self.effect = makeBlurEffect(from: blur) - case .glass(let glass): - if #available(iOS 26.0, *) { - effectView.effect = makeGlassEffect(from: glass) - } else { - // graceful fallback - effectView.effect = UIBlurEffect(style: .systemThinMaterial) - } + case .glass(let glass): + if #available(iOS 26.0, *) { + // Switching from blur/custom -> glass is fine. + effectView.effect = makeGlassEffect(from: glass) + } else { + // graceful fallback on older OS + apply(style: .blur(.system(.systemThinMaterial))) } } } @@ -150,12 +158,21 @@ private extension VisualEffectView { return UIBlurEffect(style: uiStyle) case .custom: - return UIBlurEffect(style: .light) // your existing custom pipeline + // Attach any UIBlurEffect; your existing private pipeline controls the look. + // (This mirrors what you were doing in your stub.) + return UIBlurEffect(style: .light) } } @available(iOS 26.0, *) - private func makeGlassEffect(from style: GlassStyle) -> UIVisualEffect { + func makeGlassEffect(from style: GlassStyle) -> UIVisualEffect { return UIGlassEffect(style: style.value) } + + /// iOS 26: Some builds have a bug where setting `effect = nil` after a `UIGlassEffect` + /// does not remove the effect; setting an intermediate effect first works around it. + func clearEffectWorkaroundIfNeeded() { + guard #available(iOS 26.0, *), self.effect is UIGlassEffect else { return } + self.effect = UIBlurEffect(style: .systemMaterial) + } } From 1cce2a4570866029e47ae11205e371b45e7fef12 Mon Sep 17 00:00:00 2001 From: Lasha Efremidze Date: Sun, 28 Dec 2025 15:02:49 -0800 Subject: [PATCH 03/40] Update VisualEffectView+SwiftUI.swift --- .../VisualEffectView+SwiftUI.swift | 26 ------------------- 1 file changed, 26 deletions(-) diff --git a/Sources/VisualEffectView/VisualEffectView+SwiftUI.swift b/Sources/VisualEffectView/VisualEffectView+SwiftUI.swift index 1dcf7e7..8420165 100644 --- a/Sources/VisualEffectView/VisualEffectView+SwiftUI.swift +++ b/Sources/VisualEffectView/VisualEffectView+SwiftUI.swift @@ -53,32 +53,6 @@ public struct VisualEffect: UIViewRepresentable { */ let scale: CGFloat - public enum BlurStyle: Equatable { - case system(UIBlurEffect.Style) - case custom(radius: CGFloat, saturation: CGFloat) - } - - @available(iOS 26.0, *) - public enum GlassStyle: Equatable { - case regular - case clear - case prominent - - var value: UIGlassEffect.Style { - switch self { - case .regular: .regular - case .clear: .clear - case .prominent: .prominent - } - } - } - - public enum VisualEffectStyle: Equatable { - case blur(BlurStyle) - @available(iOS 26.0, *) - case glass(GlassStyle) - } - @available(iOS 26.0, *) public var glassTintColor: UIColor? From 7897323b05fafbcec520602f28bbf70d19a13f32 Mon Sep 17 00:00:00 2001 From: Lasha Efremidze Date: Sun, 28 Dec 2025 15:39:26 -0800 Subject: [PATCH 04/40] Update VisualEffectView.swift --- .../VisualEffectView/VisualEffectView.swift | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/Sources/VisualEffectView/VisualEffectView.swift b/Sources/VisualEffectView/VisualEffectView.swift index 58e0674..9ad6ee7 100644 --- a/Sources/VisualEffectView/VisualEffectView.swift +++ b/Sources/VisualEffectView/VisualEffectView.swift @@ -8,6 +8,34 @@ import UIKit +public enum VisualEffectStyle: Sendable, Equatable { + case none + case blur(BlurStyle) + case glass(GlassStyle) +} + +public enum BlurStyle: Sendable, Equatable { + case system(UIBlurEffect.Style) + + /// Use your existing private blur pipeline (tint/saturation/scale/blurRadius knobs). + /// This returns *some* blur effect to attach to `self.effect`; your existing code + /// will continue to drive the private effect parameters. + case custom +} + +public enum GlassStyle: Sendable, Equatable { + case regular + case clear + + @available(iOS 26.0, *) + fileprivate var uiStyle: UIGlassEffect.Style { + switch self { + case .regular: return .regular + case .clear: return .clear + } + } +} + /// VisualEffectView is a dynamic background blur view. @objcMembers open class VisualEffectView: UIVisualEffectView { @@ -105,7 +133,7 @@ open class VisualEffectView: UIVisualEffectView { case .glass(let glass): if #available(iOS 26.0, *) { // Switching from blur/custom -> glass is fine. - effectView.effect = makeGlassEffect(from: glass) + self.effect = makeGlassEffect(from: glass) } else { // graceful fallback on older OS apply(style: .blur(.system(.systemThinMaterial))) From 45e37b31213e705bdaf37161fdf4a54c49447c29 Mon Sep 17 00:00:00 2001 From: Lasha Efremidze Date: Sun, 28 Dec 2025 15:41:52 -0800 Subject: [PATCH 05/40] Update VisualEffectView.swift --- Sources/VisualEffectView/VisualEffectView.swift | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Sources/VisualEffectView/VisualEffectView.swift b/Sources/VisualEffectView/VisualEffectView.swift index 9ad6ee7..8cae8c1 100644 --- a/Sources/VisualEffectView/VisualEffectView.swift +++ b/Sources/VisualEffectView/VisualEffectView.swift @@ -132,8 +132,7 @@ open class VisualEffectView: UIVisualEffectView { case .glass(let glass): if #available(iOS 26.0, *) { - // Switching from blur/custom -> glass is fine. - self.effect = makeGlassEffect(from: glass) + self.effect = UIGlassEffect(style: glass.uiStyle) // UIKit iOS 26 API } else { // graceful fallback on older OS apply(style: .blur(.system(.systemThinMaterial))) @@ -192,11 +191,6 @@ private extension VisualEffectView { } } - @available(iOS 26.0, *) - func makeGlassEffect(from style: GlassStyle) -> UIVisualEffect { - return UIGlassEffect(style: style.value) - } - /// iOS 26: Some builds have a bug where setting `effect = nil` after a `UIGlassEffect` /// does not remove the effect; setting an intermediate effect first works around it. func clearEffectWorkaroundIfNeeded() { From fe5debddd58f3bc5c3b28f4c90a5a0d46dcc1535 Mon Sep 17 00:00:00 2001 From: Lasha Efremidze Date: Sun, 28 Dec 2025 15:55:33 -0800 Subject: [PATCH 06/40] Update VisualEffectView.swift --- .../VisualEffectView/VisualEffectView.swift | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/Sources/VisualEffectView/VisualEffectView.swift b/Sources/VisualEffectView/VisualEffectView.swift index 8cae8c1..01fa59d 100644 --- a/Sources/VisualEffectView/VisualEffectView.swift +++ b/Sources/VisualEffectView/VisualEffectView.swift @@ -8,34 +8,6 @@ import UIKit -public enum VisualEffectStyle: Sendable, Equatable { - case none - case blur(BlurStyle) - case glass(GlassStyle) -} - -public enum BlurStyle: Sendable, Equatable { - case system(UIBlurEffect.Style) - - /// Use your existing private blur pipeline (tint/saturation/scale/blurRadius knobs). - /// This returns *some* blur effect to attach to `self.effect`; your existing code - /// will continue to drive the private effect parameters. - case custom -} - -public enum GlassStyle: Sendable, Equatable { - case regular - case clear - - @available(iOS 26.0, *) - fileprivate var uiStyle: UIGlassEffect.Style { - switch self { - case .regular: return .regular - case .clear: return .clear - } - } -} - /// VisualEffectView is a dynamic background blur view. @objcMembers open class VisualEffectView: UIVisualEffectView { From 16f834d4982c6f17ab3bafe2af886c56eef91f99 Mon Sep 17 00:00:00 2001 From: Lasha Efremidze Date: Sun, 28 Dec 2025 15:55:40 -0800 Subject: [PATCH 07/40] Update VisualEffectView.swift --- .../VisualEffectView/VisualEffectView.swift | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/Sources/VisualEffectView/VisualEffectView.swift b/Sources/VisualEffectView/VisualEffectView.swift index 01fa59d..547a38d 100644 --- a/Sources/VisualEffectView/VisualEffectView.swift +++ b/Sources/VisualEffectView/VisualEffectView.swift @@ -15,6 +15,34 @@ open class VisualEffectView: UIVisualEffectView { /// Returns the instance of UIBlurEffect. private let blurEffect = (NSClassFromString("_UICustomBlurEffect") as! UIBlurEffect.Type).init() + // MARK: - Public Style API + + public enum VisualEffectStyle: Sendable, Equatable { + case none + case systemBlur(UIBlurEffect.Style) + case customBlur + case glass(GlassStyle) // iOS 26+ + } + + public enum GlassStyle: Sendable, Equatable { + case regular + case clear + + @available(iOS 26.0, *) + fileprivate var uiStyle: UIGlassEffect.Style { + switch self { + case .regular: return .regular + case .clear: return .clear + } + } + } + + /// High-level switch for the backing material. + /// Default keeps existing behavior by using `.customBlur`. + public var style: VisualEffectStyle = .customBlur { + didSet { applyStyle(style) } + } + /** Tint color. From 0b89eb70f906fcd1c221decb01d2616d25cdb2aa Mon Sep 17 00:00:00 2001 From: Lasha Efremidze Date: Sun, 28 Dec 2025 16:02:07 -0800 Subject: [PATCH 08/40] Update VisualEffectView.swift --- .../VisualEffectView/VisualEffectView.swift | 61 ++++++------------- 1 file changed, 20 insertions(+), 41 deletions(-) diff --git a/Sources/VisualEffectView/VisualEffectView.swift b/Sources/VisualEffectView/VisualEffectView.swift index 547a38d..c976a41 100644 --- a/Sources/VisualEffectView/VisualEffectView.swift +++ b/Sources/VisualEffectView/VisualEffectView.swift @@ -120,38 +120,38 @@ open class VisualEffectView: UIVisualEffectView { } } - func apply(style: VisualEffectStyle) { +private extension VisualEffectView { + + func applyStyle(_ style: VisualEffectStyle) { switch style { case .none: - clearEffectWorkaroundIfNeeded() self.effect = nil - - case .blur(let blur): - clearEffectWorkaroundIfNeeded() - self.effect = makeBlurEffect(from: blur) - + + case .systemBlur(let style): + self.effect = UIBlurEffect(style: style) + + case .customBlur: + // Switch back to your private/custom effect object + self.effect = blurEffect + // Re-apply settings snapshot so switching styles is reversible + reapplyCustomSnapshot() + case .glass(let glass): if #available(iOS 26.0, *) { self.effect = UIGlassEffect(style: glass.uiStyle) // UIKit iOS 26 API } else { // graceful fallback on older OS - apply(style: .blur(.system(.systemThinMaterial))) + self.effect = UIBlurEffect(style: .systemThinMaterial) } } } - // MARK: - Initialization - - public override init(effect: UIVisualEffect?) { - super.init(effect: effect) - - scale = 1 - } - - required public init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - - scale = 1 + func reapplyCustomSnapshot() { + // Apply in a safe order; these call into your existing private pipeline + self.scale = customSnapshot.scale + self.saturation = customSnapshot.saturation + self.blurRadius = customSnapshot.blurRadius + self.colorTint = customSnapshot.colorTint } } @@ -177,24 +177,3 @@ private extension VisualEffectView { } // ["grayscaleTintLevel", "grayscaleTintAlpha", "lightenGrayscaleWithSourceOver", "colorTint", "colorTintAlpha", "colorBurnTintLevel", "colorBurnTintAlpha", "darkeningTintAlpha", "darkeningTintHue", "darkeningTintSaturation", "darkenWithSourceOver", "blurRadius", "saturationDeltaFactor", "scale", "zoom"] - -private extension VisualEffectView { - func makeBlurEffect(from style: BlurStyle) -> UIVisualEffect { - switch style { - case .system(let uiStyle): - return UIBlurEffect(style: uiStyle) - - case .custom: - // Attach any UIBlurEffect; your existing private pipeline controls the look. - // (This mirrors what you were doing in your stub.) - return UIBlurEffect(style: .light) - } - } - - /// iOS 26: Some builds have a bug where setting `effect = nil` after a `UIGlassEffect` - /// does not remove the effect; setting an intermediate effect first works around it. - func clearEffectWorkaroundIfNeeded() { - guard #available(iOS 26.0, *), self.effect is UIGlassEffect else { return } - self.effect = UIBlurEffect(style: .systemMaterial) - } -} From 947cbbfc1d46368c5f320cc18bad2c407fd98eba Mon Sep 17 00:00:00 2001 From: Lasha Efremidze Date: Sun, 28 Dec 2025 16:05:58 -0800 Subject: [PATCH 09/40] Update VisualEffectView.swift --- .../VisualEffectView/VisualEffectView.swift | 51 +++++++++++++++---- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/Sources/VisualEffectView/VisualEffectView.swift b/Sources/VisualEffectView/VisualEffectView.swift index c976a41..7606814 100644 --- a/Sources/VisualEffectView/VisualEffectView.swift +++ b/Sources/VisualEffectView/VisualEffectView.swift @@ -43,6 +43,22 @@ open class VisualEffectView: UIVisualEffectView { didSet { applyStyle(style) } } + // MARK: - Preserve custom settings across style switches + + private struct CustomSnapshot { + var colorTint: UIColor? + var blurRadius: CGFloat + var saturation: CGFloat + var scale: CGFloat + } + + private var customSnapshot = CustomSnapshot( + colorTint: nil, + blurRadius: 0, + saturation: 1, + scale: 1 + ) + /** Tint color. @@ -53,6 +69,7 @@ open class VisualEffectView: UIVisualEffectView { return sourceOver?.value(forKeyPath: "color") as? UIColor } set { + customSnapshot.colorTint = newValue prepareForChanges() sourceOver?.setValue(newValue, forKeyPath: "color") sourceOver?.perform(Selector(("applyRequestedEffectToView:")), with: overlayView) @@ -82,6 +99,7 @@ open class VisualEffectView: UIVisualEffectView { return gaussianBlur?.requestedValues?["inputRadius"] as? CGFloat ?? 0 } set { + customSnapshot.blurRadius = newValue prepareForChanges() gaussianBlur?.requestedValues?["inputRadius"] = newValue applyChanges() @@ -97,7 +115,10 @@ open class VisualEffectView: UIVisualEffectView { */ open var saturation: CGFloat { get { return _value(forKey: .saturationDeltaFactor) ?? 1.0 } - set { _setValue(newValue, forKey: .saturationDeltaFactor) } + set { + customSnapshot.saturation = newValue + _setValue(newValue, forKey: .saturationDeltaFactor) + } } /** @@ -109,16 +130,28 @@ open class VisualEffectView: UIVisualEffectView { */ open var scale: CGFloat { get { return _value(forKey: .scale) ?? 1.0 } - set { _setValue(newValue, forKey: .scale) } - } - - // MARK: - Style property - - public var style: VisualEffectStyle = .none { - didSet { - apply(style: style) + set { + customSnapshot.scale = newValue + _setValue(newValue, forKey: .scale) } } + + // MARK: - Initialization + + public override init(effect: UIVisualEffect?) { + super.init(effect: effect) + // Keep previous default behavior: custom pipeline “on” + applyStyle(style) + } + + required public init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + applyStyle(style) + } + +} + +// MARK: - Style private extension VisualEffectView { From d56258a8dac8b07ec3c4f03d7f653727e9e848fe Mon Sep 17 00:00:00 2001 From: Lasha Efremidze Date: Sun, 28 Dec 2025 16:06:29 -0800 Subject: [PATCH 10/40] Update VisualEffectView.swift --- Sources/VisualEffectView/VisualEffectView.swift | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Sources/VisualEffectView/VisualEffectView.swift b/Sources/VisualEffectView/VisualEffectView.swift index 7606814..237a555 100644 --- a/Sources/VisualEffectView/VisualEffectView.swift +++ b/Sources/VisualEffectView/VisualEffectView.swift @@ -16,14 +16,14 @@ open class VisualEffectView: UIVisualEffectView { private let blurEffect = (NSClassFromString("_UICustomBlurEffect") as! UIBlurEffect.Type).init() // MARK: - Public Style API - + public enum VisualEffectStyle: Sendable, Equatable { case none case systemBlur(UIBlurEffect.Style) case customBlur case glass(GlassStyle) // iOS 26+ } - + public enum GlassStyle: Sendable, Equatable { case regular case clear @@ -44,21 +44,21 @@ open class VisualEffectView: UIVisualEffectView { } // MARK: - Preserve custom settings across style switches - + private struct CustomSnapshot { var colorTint: UIColor? var blurRadius: CGFloat var saturation: CGFloat var scale: CGFloat } - + private var customSnapshot = CustomSnapshot( colorTint: nil, blurRadius: 0, saturation: 1, scale: 1 ) - + /** Tint color. @@ -80,7 +80,7 @@ open class VisualEffectView: UIVisualEffectView { /** Tint color alpha. - + Don't use it unless `colorTint` is not nil. The default value is 0.0. */ @@ -187,6 +187,7 @@ private extension VisualEffectView { self.colorTint = customSnapshot.colorTint } + } // MARK: - Helpers From 9f15fefaa4eb6aa175e2bbb542f686bedadb7740 Mon Sep 17 00:00:00 2001 From: Lasha Efremidze Date: Sun, 28 Dec 2025 16:11:47 -0800 Subject: [PATCH 11/40] Update UIViewEffectView+Helpers.swift --- Sources/VisualEffectView/UIViewEffectView+Helpers.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/VisualEffectView/UIViewEffectView+Helpers.swift b/Sources/VisualEffectView/UIViewEffectView+Helpers.swift index 29b6446..9b64e7f 100644 --- a/Sources/VisualEffectView/UIViewEffectView+Helpers.swift +++ b/Sources/VisualEffectView/UIViewEffectView+Helpers.swift @@ -1,5 +1,5 @@ // -// UIViewEffectViewiOS14.swift +// UIVisualEffectView+Helpers.swift // VisualEffectView // // Created by Lasha Efremidze on 9/14/20. From e8fbd1dff4bf2ecf0a02636bcc22eb6edacabd74 Mon Sep 17 00:00:00 2001 From: Lasha Efremidze Date: Sun, 28 Dec 2025 16:11:49 -0800 Subject: [PATCH 12/40] Update VisualEffectView+SwiftUI.swift --- Sources/VisualEffectView/VisualEffectView+SwiftUI.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/VisualEffectView/VisualEffectView+SwiftUI.swift b/Sources/VisualEffectView/VisualEffectView+SwiftUI.swift index 8420165..6c80eb9 100644 --- a/Sources/VisualEffectView/VisualEffectView+SwiftUI.swift +++ b/Sources/VisualEffectView/VisualEffectView+SwiftUI.swift @@ -2,7 +2,7 @@ // VisualEffectView+SwiftUI.swift // VisualEffectView // -// Created by 朱浩宇 on 2023/5/10. +// Created by Lasha Efremidze on 5/26/16. // Copyright © 2023 Lasha Efremidze. All rights reserved. // From 82cb57a6be44a57980e80829eb3a268e789c9142 Mon Sep 17 00:00:00 2001 From: Lasha Efremidze Date: Sun, 28 Dec 2025 16:18:23 -0800 Subject: [PATCH 13/40] Update VisualEffectView+SwiftUI.swift --- .../VisualEffectView+SwiftUI.swift | 123 +++++++++--------- 1 file changed, 61 insertions(+), 62 deletions(-) diff --git a/Sources/VisualEffectView/VisualEffectView+SwiftUI.swift b/Sources/VisualEffectView/VisualEffectView+SwiftUI.swift index 6c80eb9..f64d0f8 100644 --- a/Sources/VisualEffectView/VisualEffectView+SwiftUI.swift +++ b/Sources/VisualEffectView/VisualEffectView+SwiftUI.swift @@ -16,97 +16,96 @@ import SwiftUI The effect can be customized with parameters such as tint color, tint alpha, blur radius, and scale. */ public struct VisualEffect: UIViewRepresentable { - /** - The tint color to apply to the blur effect. - - The default value is `nil`. - */ + public + typealias VisualEffectStyle = VisualEffectView.VisualEffectStyle + + // MARK: - Style + + /// Optional high-level style selector. + /// If `nil`, the legacy customizable blur pipeline is used. + let style: VisualEffectStyle? + + // MARK: - Blur parameters (blur-only) + let colorTint: Color? - - /** - The alpha component of the tint color. - - The default value is `0.0`. - */ let colorTintAlpha: CGFloat - - /** - The radius of the blur effect. - - The default value is `0.0`. - */ let blurRadius: CGFloat - - /** - The saturation factor. - - Values above 1.0 increase saturation, values below 1.0 decrease saturation, and 1.0 maintains original saturation. - - The default value is `1.0`. - */ let saturation: CGFloat - - /** - The scale factor for the blur effect. - - The default value is `1.0`. - */ let scale: CGFloat - @available(iOS 26.0, *) - public var glassTintColor: UIColor? - - @available(iOS 26.0, *) - public var isGlassInteractive: Bool - + // MARK: - Initializers + /** - Initializes a `VisualEffect` view with the specified parameters. + Legacy initializer (unchanged). - - Parameters: - - colorTint: The tint color to apply to the blur effect. Defaults to `nil`. - - colorTintAlpha: The alpha component of the tint color. Defaults to `0.0`. - - blurRadius: The radius of the blur effect. Defaults to `0.0`. - - saturation: The saturation adjustment factor. Values above 1.0 increase saturation, values below 1.0 decrease saturation. Defaults to `1.0`. - - scale: The scale factor for the blur effect. Defaults to `1.0`. + Uses the customizable blur pipeline. */ - public init(colorTint: Color? = nil, colorTintAlpha: CGFloat = 0, blurRadius: CGFloat = 0, saturation: CGFloat = 1, scale: CGFloat = 1) { + public init( + colorTint: Color? = nil, + colorTintAlpha: CGFloat = 0, + blurRadius: CGFloat = 0, + saturation: CGFloat = 1, + scale: CGFloat = 1 + ) { + self.style = nil self.colorTint = colorTint self.colorTintAlpha = colorTintAlpha self.blurRadius = blurRadius self.saturation = saturation self.scale = scale } - - @available(iOS 26.0, *) - public init(glass: GlassStyle, tint: UIColor? = nil, interactive: Bool = false) { - self.glass = glass - self.tint = tint - self.interactive = interactive + + /** + Style-based initializer. + + Use this for system blur or Liquid Glass. + Blur-specific parameters are ignored unless the style is `.customBlur`. + */ + public init(style: VisualEffectStyle) { + self.style = style + self.colorTint = nil + self.colorTintAlpha = 0 + self.blurRadius = 0 + self.saturation = 1 + self.scale = 1 } + // MARK: - UIViewRepresentable + public func makeUIView(context: Context) -> VisualEffectView { let view = VisualEffectView() view.autoresizingMask = [.flexibleWidth, .flexibleHeight] - if let colorTint { - view.colorTint = colorTint.uiColor() + if let style { + view.style = style } - view.colorTintAlpha = colorTintAlpha - view.blurRadius = blurRadius - view.saturation = saturation - view.scale = scale + applyBlurParameters(to: view) return view } public func updateUIView(_ uiView: VisualEffectView, context: Context) { + if let style { + uiView.style = style + } + + applyBlurParameters(to: uiView) + } + + // MARK: - Helpers + + /// Applies blur-only parameters when appropriate. + private func applyBlurParameters(to view: VisualEffectView) { + // Only apply these knobs when using the legacy custom blur pipeline. + guard style == nil || style == .customBlur else { return } + if let colorTint { - uiView.colorTint = colorTint.uiColor() + view.colorTint = colorTint.uiColor() } - uiView.colorTintAlpha = colorTintAlpha - uiView.blurRadius = blurRadius - uiView.saturation = saturation - uiView.scale = scale + view.colorTintAlpha = colorTintAlpha + view.blurRadius = blurRadius + view.saturation = saturation + view.scale = scale } } From 3b30202d8accdcedead8dca58e66bc0beb4afd9f Mon Sep 17 00:00:00 2001 From: Lasha Efremidze Date: Sun, 28 Dec 2025 17:05:16 -0800 Subject: [PATCH 14/40] Update VisualEffectView+SwiftUI.swift --- .../VisualEffectView+SwiftUI.swift | 182 +++++++++++++++--- 1 file changed, 152 insertions(+), 30 deletions(-) diff --git a/Sources/VisualEffectView/VisualEffectView+SwiftUI.swift b/Sources/VisualEffectView/VisualEffectView+SwiftUI.swift index f64d0f8..6b581c4 100644 --- a/Sources/VisualEffectView/VisualEffectView+SwiftUI.swift +++ b/Sources/VisualEffectView/VisualEffectView+SwiftUI.swift @@ -11,21 +11,33 @@ import SwiftUI /** A SwiftUI view that applies a visual effect to the background of its content. - This view uses the `VisualEffectView` class to create a blur effect on the background of its content. + This view uses the `VisualEffectView` class to create blur and glass effects. - The effect can be customized with parameters such as tint color, tint alpha, blur radius, and scale. + ## Usage + + Style-based (recommended): + ```swift + VisualEffect(style: .systemBlur(.dark)) + VisualEffect(style: .glass(.regular)) + ``` + + Legacy customizable blur: + ```swift + VisualEffect(colorTint: .white, colorTintAlpha: 0.5, blurRadius: 18) + ``` + + - Note: Blur parameters (colorTint, blurRadius, etc.) only apply to `.customBlur` style. */ public struct VisualEffect: UIViewRepresentable { - public - typealias VisualEffectStyle = VisualEffectView.VisualEffectStyle + public typealias VisualEffectStyle = VisualEffectView.VisualEffectStyle // MARK: - Style /// Optional high-level style selector. - /// If `nil`, the legacy customizable blur pipeline is used. + /// If `nil`, the legacy customizable blur pipeline is used for backward compatibility. let style: VisualEffectStyle? - // MARK: - Blur parameters (blur-only) + // MARK: - Blur parameters (customBlur only) let colorTint: Color? let colorTintAlpha: CGFloat @@ -36,9 +48,50 @@ public struct VisualEffect: UIViewRepresentable { // MARK: - Initializers /** - Legacy initializer (unchanged). + Style-based initializer (recommended). + + Use this for system blur, glass effects, or explicit custom blur. + + - Parameter style: The visual effect style to apply. + + ## Example + ```swift + VisualEffect(style: .systemBlur(.dark)) + VisualEffect(style: .glass(.regular)) + VisualEffect(style: .customBlur) + ``` + */ + public init(style: VisualEffectStyle) { + self.style = style + self.colorTint = nil + self.colorTintAlpha = 0 + self.blurRadius = 0 + self.saturation = 1 + self.scale = 1 + } + + /** + Legacy customizable blur initializer. + + Uses the customizable blur pipeline with fine-grained control over blur parameters. + Maintained for backward compatibility. - Uses the customizable blur pipeline. + - Parameters: + - colorTint: Optional tint color overlay + - colorTintAlpha: Alpha value for the tint color (0.0 - 1.0) + - blurRadius: Blur intensity in points + - saturation: Color saturation multiplier (1.0 = original, >1.0 = more saturated, <1.0 = less saturated) + - scale: Scale factor for the blur effect + + ## Example + ```swift + VisualEffect( + colorTint: .white, + colorTintAlpha: 0.5, + blurRadius: 18, + saturation: 1.8 + ) + ``` */ public init( colorTint: Color? = nil, @@ -47,7 +100,7 @@ public struct VisualEffect: UIViewRepresentable { saturation: CGFloat = 1, scale: CGFloat = 1 ) { - self.style = nil + self.style = nil // nil signals legacy mode self.colorTint = colorTint self.colorTintAlpha = colorTintAlpha self.blurRadius = blurRadius @@ -55,21 +108,6 @@ public struct VisualEffect: UIViewRepresentable { self.scale = scale } - /** - Style-based initializer. - - Use this for system blur or Liquid Glass. - Blur-specific parameters are ignored unless the style is `.customBlur`. - */ - public init(style: VisualEffectStyle) { - self.style = style - self.colorTint = nil - self.colorTintAlpha = 0 - self.blurRadius = 0 - self.saturation = 1 - self.scale = 1 - } - // MARK: - UIViewRepresentable public func makeUIView(context: Context) -> VisualEffectView { @@ -79,6 +117,7 @@ public struct VisualEffect: UIViewRepresentable { if let style { view.style = style } + // else: keep view's default (.customBlur) for backward compatibility applyBlurParameters(to: view) return view @@ -96,7 +135,8 @@ public struct VisualEffect: UIViewRepresentable { /// Applies blur-only parameters when appropriate. private func applyBlurParameters(to view: VisualEffectView) { - // Only apply these knobs when using the legacy custom blur pipeline. + // Only apply these parameters when using the custom blur pipeline + // (either explicitly via style or implicitly via legacy init) guard style == nil || style == .customBlur else { return } if let colorTint { @@ -109,17 +149,99 @@ public struct VisualEffect: UIViewRepresentable { } } -#Preview { +// MARK: - Preview + +#Preview("Custom Blur") { ZStack { - Color.blue - .frame(width: 400, height: 400) + LinearGradient( + colors: [.blue, .purple], + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + .ignoresSafeArea() + Color.red .frame(width: 200, height: 100) - VisualEffect(colorTint: .white, colorTintAlpha: 0.5, blurRadius: 18, saturation: 2.0) - .frame(width: 300, height: 200) + + VisualEffect( + colorTint: .green, + colorTintAlpha: 0.5, + blurRadius: 18, + saturation: 2.0 + ) + .frame(width: 300, height: 200) + .cornerRadius(20) } } +#Preview("System Blur") { + ZStack { + Image(systemName: "sparkles") + .font(.system(size: 200)) + .foregroundStyle(.blue) + + VStack(spacing: 20) { + Text("System Blur") + .font(.title) + + VisualEffect(style: .systemBlur(.systemMaterial)) + .frame(width: 300, height: 100) + .cornerRadius(16) + .overlay { + Text("Content over blur") + .font(.headline) + } + } + } +} + +@available(iOS 17.0, *) +#Preview("Glass Effect", traits: .fixedLayout(width: 400, height: 400)) { + ZStack { + LinearGradient( + colors: [.orange, .pink], + startPoint: .top, + endPoint: .bottom + ) + .ignoresSafeArea() + + VStack(spacing: 20) { + if #available(iOS 26.0, *) { + Text("Glass Effect (iOS 26+)") + .font(.title3) + + VisualEffect(style: .glass(.regular)) + .frame(width: 300, height: 100) + .cornerRadius(20) + .overlay { + Text("Regular Glass") + } + + VisualEffect(style: .glass(.clear)) + .frame(width: 300, height: 100) + .cornerRadius(20) + .overlay { + Text("Clear Glass") + } + } else { + Text("Glass Effect") + .font(.title3) + Text("(Fallback on iOS < 26)") + .font(.caption) + + VisualEffect(style: .glass(.regular)) + .frame(width: 300, height: 100) + .cornerRadius(20) + .overlay { + Text("Regular Glass Fallback") + } + } + } + } +} + +// MARK: - Color Extension + private extension Color { func uiColor() -> UIColor { return UIColor(self) From 738fa36c820baafe95368a8e8816239417403cbe Mon Sep 17 00:00:00 2001 From: Lasha Efremidze Date: Sun, 28 Dec 2025 17:09:00 -0800 Subject: [PATCH 15/40] Update VisualEffectView.swift --- .../VisualEffectView/VisualEffectView.swift | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Sources/VisualEffectView/VisualEffectView.swift b/Sources/VisualEffectView/VisualEffectView.swift index 237a555..90b4107 100644 --- a/Sources/VisualEffectView/VisualEffectView.swift +++ b/Sources/VisualEffectView/VisualEffectView.swift @@ -9,6 +9,25 @@ import UIKit /// VisualEffectView is a dynamic background blur view. +/// +/// ## Usage +/// ```swift +/// // Legacy customizable blur (default for backward compatibility) +/// let view = VisualEffectView() +/// view.blurRadius = 20 +/// view.colorTint = .white +/// +/// // Style-based API +/// let systemView = VisualEffectView() +/// systemView.style = .systemBlur(.dark) +/// +/// // Glass effect (iOS 26+) +/// let glassView = VisualEffectView() +/// glassView.style = .glass(.regular) +/// ``` +/// +/// - Note: Custom blur properties (blurRadius, colorTint, etc.) only apply when using `.customBlur` style. +@MainActor @objcMembers open class VisualEffectView: UIVisualEffectView { @@ -35,6 +54,14 @@ open class VisualEffectView: UIVisualEffectView { case .clear: return .clear } } + + /// Fallback blur style for iOS < 26 + fileprivate var fallbackBlurStyle: UIBlurEffect.Style { + switch self { + case .regular: return .systemMaterial + case .clear: return .systemUltraThinMaterial + } + } } /// High-level switch for the backing material. From 57848749f7f78f91b2db1f1cd233f5269ad1e145 Mon Sep 17 00:00:00 2001 From: Lasha Efremidze Date: Sun, 28 Dec 2025 17:09:17 -0800 Subject: [PATCH 16/40] Update VisualEffectView.swift --- Sources/VisualEffectView/VisualEffectView.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Sources/VisualEffectView/VisualEffectView.swift b/Sources/VisualEffectView/VisualEffectView.swift index 90b4107..46244ab 100644 --- a/Sources/VisualEffectView/VisualEffectView.swift +++ b/Sources/VisualEffectView/VisualEffectView.swift @@ -86,9 +86,12 @@ open class VisualEffectView: UIVisualEffectView { scale: 1 ) + // MARK: - Custom Blur Properties + /** Tint color. + - Note: Only applies when `style` is `.customBlur`. The default value is nil. */ open var colorTint: UIColor? { @@ -108,6 +111,7 @@ open class VisualEffectView: UIVisualEffectView { /** Tint color alpha. + - Note: Only applies when `style` is `.customBlur`. Don't use it unless `colorTint` is not nil. The default value is 0.0. */ @@ -119,6 +123,7 @@ open class VisualEffectView: UIVisualEffectView { /** Blur radius. + - Note: Only applies when `style` is `.customBlur`. The default value is 0.0. */ open var blurRadius: CGFloat { @@ -138,6 +143,7 @@ open class VisualEffectView: UIVisualEffectView { Values above 1.0 increase saturation, values below 1.0 decrease saturation, and 1.0 maintains original saturation. + - Note: Only applies when `style` is `.customBlur`. The default value is 1.0. */ open var saturation: CGFloat { @@ -153,6 +159,7 @@ open class VisualEffectView: UIVisualEffectView { The scale factor determines how content in the view is mapped from the logical coordinate space (measured in points) to the device coordinate space (measured in pixels). + - Note: Only applies when `style` is `.customBlur`. The default value is 1.0. */ open var scale: CGFloat { From f5d3e8e4e7dce5a06d6053a168a1ee1bdae9e814 Mon Sep 17 00:00:00 2001 From: Lasha Efremidze Date: Sun, 28 Dec 2025 17:10:05 -0800 Subject: [PATCH 17/40] Update VisualEffectView.swift --- .../VisualEffectView/VisualEffectView.swift | 46 ++++++++++++++----- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/Sources/VisualEffectView/VisualEffectView.swift b/Sources/VisualEffectView/VisualEffectView.swift index 46244ab..16da22c 100644 --- a/Sources/VisualEffectView/VisualEffectView.swift +++ b/Sources/VisualEffectView/VisualEffectView.swift @@ -65,9 +65,15 @@ open class VisualEffectView: UIVisualEffectView { } /// High-level switch for the backing material. - /// Default keeps existing behavior by using `.customBlur`. + /// Default is `.customBlur` for backward compatibility. + /// + /// - Note: When using `.systemBlur` or `.glass`, custom blur properties + /// (blurRadius, colorTint, etc.) are ignored. public var style: VisualEffectStyle = .customBlur { - didSet { applyStyle(style) } + didSet { + guard style != oldValue else { return } + applyStyle(style) + } } // MARK: - Preserve custom settings across style switches @@ -100,6 +106,8 @@ open class VisualEffectView: UIVisualEffectView { } set { customSnapshot.colorTint = newValue + guard case .customBlur = style else { return } + prepareForChanges() sourceOver?.setValue(newValue, forKeyPath: "color") sourceOver?.perform(Selector(("applyRequestedEffectToView:")), with: overlayView) @@ -132,6 +140,8 @@ open class VisualEffectView: UIVisualEffectView { } set { customSnapshot.blurRadius = newValue + guard case .customBlur = style else { return } + prepareForChanges() gaussianBlur?.requestedValues?["inputRadius"] = newValue applyChanges() @@ -150,6 +160,8 @@ open class VisualEffectView: UIVisualEffectView { get { return _value(forKey: .saturationDeltaFactor) ?? 1.0 } set { customSnapshot.saturation = newValue + guard case .customBlur = style else { return } + _setValue(newValue, forKey: .saturationDeltaFactor) } } @@ -166,21 +178,33 @@ open class VisualEffectView: UIVisualEffectView { get { return _value(forKey: .scale) ?? 1.0 } set { customSnapshot.scale = newValue + guard case .customBlur = style else { return } + _setValue(newValue, forKey: .scale) } } // MARK: - Initialization + /// Creates a visual effect view with customizable blur (legacy default for backward compatibility). + public convenience init() { + self.init(effect: nil) + } + public override init(effect: UIVisualEffect?) { super.init(effect: effect) - // Keep previous default behavior: custom pipeline “on” - applyStyle(style) + + // If no effect provided, use legacy default for backward compatibility + if effect == nil { + applyStyle(.customBlur) + } + // Otherwise, respect the passed effect } required public init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) - applyStyle(style) + // Interface Builder instances default to custom blur + applyStyle(.customBlur) } } @@ -198,30 +222,29 @@ private extension VisualEffectView { self.effect = UIBlurEffect(style: style) case .customBlur: - // Switch back to your private/custom effect object + // Switch back to custom effect object self.effect = blurEffect // Re-apply settings snapshot so switching styles is reversible reapplyCustomSnapshot() case .glass(let glass): if #available(iOS 26.0, *) { - self.effect = UIGlassEffect(style: glass.uiStyle) // UIKit iOS 26 API + self.effect = UIGlassEffect(style: glass.uiStyle) } else { - // graceful fallback on older OS - self.effect = UIBlurEffect(style: .systemThinMaterial) + // Graceful fallback on older OS with style-appropriate blur + self.effect = UIBlurEffect(style: glass.fallbackBlurStyle) } } } func reapplyCustomSnapshot() { - // Apply in a safe order; these call into your existing private pipeline + // Apply in a safe order; these call into the existing custom pipeline self.scale = customSnapshot.scale self.saturation = customSnapshot.saturation self.blurRadius = customSnapshot.blurRadius self.colorTint = customSnapshot.colorTint } - } // MARK: - Helpers @@ -244,4 +267,5 @@ private extension VisualEffectView { } +// Available keys for reference: // ["grayscaleTintLevel", "grayscaleTintAlpha", "lightenGrayscaleWithSourceOver", "colorTint", "colorTintAlpha", "colorBurnTintLevel", "colorBurnTintAlpha", "darkeningTintAlpha", "darkeningTintHue", "darkeningTintSaturation", "darkenWithSourceOver", "blurRadius", "saturationDeltaFactor", "scale", "zoom"] From 89e34adbf6b6c73c54cf38b18a1ad5701a763353 Mon Sep 17 00:00:00 2001 From: Lasha Efremidze Date: Sun, 28 Dec 2025 17:13:24 -0800 Subject: [PATCH 18/40] Update README.md --- README.md | 77 +++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 55 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index eb01634..5fd0a00 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![Swift](https://img.shields.io/badge/Swift-5.9+-orange.svg)](https://swift.org) [![License](https://img.shields.io/github/license/efremidze/VisualEffectView.svg)](https://github.com/efremidze/VisualEffectView/blob/master/LICENSE) -**VisualEffectView** is a blur effect library with tint color support. This library uses the [UIVisualEffectView](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIVisualEffectView/) to generate the blur. +**VisualEffectView** is a dynamic blur effect library with tint color support and iOS 26+ glass effects. This library uses [UIVisualEffectView](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIVisualEffectView/) to generate the blur. @@ -23,15 +23,15 @@ $ pod try VisualEffectView ## Usage -Add an instance of VisualEffectView to your view. +### UIKit ```swift import VisualEffectView let visualEffectView = VisualEffectView(frame: CGRect(x: 0, y: 0, width: 320, height: 480)) -// Configure the view with tint color, blur radius, etc -visualEffectView.colorTint = .redColor() +// Customize the blur +visualEffectView.colorTint = .red visualEffectView.colorTintAlpha = 0.2 visualEffectView.blurRadius = 10 visualEffectView.scale = 1 @@ -39,9 +39,37 @@ visualEffectView.scale = 1 addSubview(visualEffectView) ``` -Depending on the desired effect, the effect may affect content layered behind the view or content added to the visual effect view’s contentView. After you add the visual effect view to the view hierarchy, add any subviews to the contentView property of the visual effect view. Do not add subviews directly to the visual effect view itself. Refer to the [UIVisualEffectView](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIVisualEffectView/) for more info. +You can also use different styles: -For more examples, take a look at the example project. +```swift +// System blur +visualEffectView.style = .systemBlur(.dark) + +// Glass effect (iOS 26+) +visualEffectView.style = .glass(.regular) + +// Custom blur (default) +visualEffectView.style = .customBlur +``` + +### SwiftUI + +```swift +import VisualEffectView + +struct ContentView: View { + var body: some View { + VisualEffect(colorTint: .white, colorTintAlpha: 0.5, blurRadius: 18, scale: 1) + } +} +``` + +Or use the style-based API: + +```swift +VisualEffect(style: .glass(.regular)) +VisualEffect(style: .systemBlur(.dark)) +``` ### Customization @@ -53,28 +81,23 @@ var scale: CGFloat // scale factor. default is 1 var saturation: CGFloat // saturation factor. default is 1 ``` -If you want `colorTintAlpha` to be different from `0`, make sure you always set it right after setting the `colorTint` or it may not be applied as expected. -You also have to make sure you don't set `colorTintAlpha` if `colorTint` is `nil`. +**Note:** Custom blur properties only work when `style` is `.customBlur`. -### Storyboard Support - -Works great with storyboards and xibs. +If you want `colorTintAlpha` to be different from `0`, make sure you always set it right after setting the `colorTint` or it may not be applied as expected. Don't set `colorTintAlpha` if `colorTint` is `nil`. -### SwiftUI Support +### Content View -VisualEffectView supports SwiftUI. +Add subviews to the `contentView` property, not directly to the visual effect view: ```swift -import VisualEffectView - -struct ContentView: View { - var body: some View { - VisualEffect(colorTint: .white, colorTintAlpha: 0.5, blurRadius: 10, scale: 1) - } -} +visualEffectView.contentView.addSubview(label) ``` -Make sure that `colorTintAlpha` is not set when `colorTint` is `nil`. +Refer to the [UIVisualEffectView](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIVisualEffectView/) documentation for more info. + +### Storyboard Support + +Works great with storyboards and xibs. ## Installation @@ -91,8 +114,16 @@ To install with [Carthage](https://github.com/Carthage/Carthage), simply add thi github "efremidze/VisualEffectView" ``` +### Swift Package Manager +Add VisualEffectView as a dependency in your `Package.swift` file: +```swift +dependencies: [ + .package(url: "https://github.com/efremidze/VisualEffectView.git", from: "5.0.0") +] +``` + ### Manually -1. Download and drop ```VisualEffectView.swift``` in your project. +1. Download and drop the source files in your project. 2. Congratulations! ## Communication @@ -105,6 +136,8 @@ github "efremidze/VisualEffectView" VisualEffectView utilizes a private UIKit API to do its magic. Use caution, submitting this code to the App Store adds the risk of being rejected! +The `.systemBlur()` and `.glass()` styles use only public APIs and are safe for App Store submission. + ## Credits https://github.com/collinhundley/APCustomBlurView From a4ed2fab3459297fcca1fb52bf414601d7d48409 Mon Sep 17 00:00:00 2001 From: Lasha Efremidze Date: Sun, 28 Dec 2025 17:29:44 -0800 Subject: [PATCH 19/40] Update VisualEffectView.podspec --- VisualEffectView.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VisualEffectView.podspec b/VisualEffectView.podspec index 5b352f5..7a92112 100644 --- a/VisualEffectView.podspec +++ b/VisualEffectView.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = "VisualEffectView" - s.version = "5.0.8" + s.version = "6.0.0" s.license = 'MIT' s.homepage = "https://github.com/efremidze/VisualEffectView" s.author = { "Lasha Efremidze" => "efremidzel@hotmail.com" } From 64f0a89de911173991f81c51812142fd1892461a Mon Sep 17 00:00:00 2001 From: Lasha Efremidze Date: Sun, 28 Dec 2025 17:29:47 -0800 Subject: [PATCH 20/40] Update CHANGELOG.md --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 666b2f4..5d0fdbb 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Change log +## [Version 6.0.0](https://github.com/efremidze/VisualEffectView/releases/tag/6.0.0) + +- Added iOS 26+ glass effect support with automatic fallback +- Added style-based API with `.systemBlur`, `.customBlur`, and `.glass` options + ## [Version 5.0.8](https://github.com/efremidze/VisualEffectView/releases/tag/5.0.8) - Readded saturation From 65cfcd3f63a717985335a6975dd81cdea27a01e0 Mon Sep 17 00:00:00 2001 From: Lasha Efremidze Date: Sun, 28 Dec 2025 17:33:29 -0800 Subject: [PATCH 21/40] Update ContentView.swift --- Example/ContentView.swift | 89 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/Example/ContentView.swift b/Example/ContentView.swift index e6ad6e3..84ea639 100644 --- a/Example/ContentView.swift +++ b/Example/ContentView.swift @@ -53,3 +53,92 @@ private extension Color { #Preview { ContentView() } + +#Preview("Custom Blur") { + ZStack { + LinearGradient( + colors: [.blue, .purple], + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + .ignoresSafeArea() + + Color.red + .frame(width: 200, height: 100) + + VisualEffect( + colorTint: .green, + colorTintAlpha: 0.5, + blurRadius: 18, + saturation: 2.0 + ) + .frame(width: 300, height: 200) + .cornerRadius(20) + } +} + +#Preview("System Blur") { + ZStack { + Image(systemName: "sparkles") + .font(.system(size: 200)) + .foregroundStyle(.blue) + + VStack(spacing: 20) { + Text("System Blur") + .font(.title) + + VisualEffect(style: .systemBlur(.systemMaterial)) + .frame(width: 300, height: 100) + .cornerRadius(16) + .overlay { + Text("Content over blur") + .font(.headline) + } + } + } +} + +@available(iOS 17.0, *) +#Preview("Glass Effect", traits: .fixedLayout(width: 400, height: 400)) { + ZStack { + LinearGradient( + colors: [.orange, .pink], + startPoint: .top, + endPoint: .bottom + ) + .ignoresSafeArea() + + VStack(spacing: 20) { + if #available(iOS 26.0, *) { + Text("Glass Effect (iOS 26+)") + .font(.title3) + + VisualEffect(style: .glass(.regular)) + .frame(width: 300, height: 100) + .cornerRadius(20) + .overlay { + Text("Regular Glass") + } + + VisualEffect(style: .glass(.clear)) + .frame(width: 300, height: 100) + .cornerRadius(20) + .overlay { + Text("Clear Glass") + } + } else { + Text("Glass Effect") + .font(.title3) + Text("(Fallback on iOS < 26)") + .font(.caption) + + VisualEffect(style: .glass(.regular)) + .frame(width: 300, height: 100) + .cornerRadius(20) + .overlay { + Text("Regular Glass Fallback") + } + } + } + } +} From 92ce8e2a83d96b3d4530697bb00840f01539835f Mon Sep 17 00:00:00 2001 From: Lasha Efremidze Date: Sun, 28 Dec 2025 17:36:00 -0800 Subject: [PATCH 22/40] Update VisualEffectView+SwiftUI.swift --- .../VisualEffectView+SwiftUI.swift | 91 ------------------- 1 file changed, 91 deletions(-) diff --git a/Sources/VisualEffectView/VisualEffectView+SwiftUI.swift b/Sources/VisualEffectView/VisualEffectView+SwiftUI.swift index 6b581c4..dd977b8 100644 --- a/Sources/VisualEffectView/VisualEffectView+SwiftUI.swift +++ b/Sources/VisualEffectView/VisualEffectView+SwiftUI.swift @@ -149,97 +149,6 @@ public struct VisualEffect: UIViewRepresentable { } } -// MARK: - Preview - -#Preview("Custom Blur") { - ZStack { - LinearGradient( - colors: [.blue, .purple], - startPoint: .topLeading, - endPoint: .bottomTrailing - ) - .ignoresSafeArea() - - Color.red - .frame(width: 200, height: 100) - - VisualEffect( - colorTint: .green, - colorTintAlpha: 0.5, - blurRadius: 18, - saturation: 2.0 - ) - .frame(width: 300, height: 200) - .cornerRadius(20) - } -} - -#Preview("System Blur") { - ZStack { - Image(systemName: "sparkles") - .font(.system(size: 200)) - .foregroundStyle(.blue) - - VStack(spacing: 20) { - Text("System Blur") - .font(.title) - - VisualEffect(style: .systemBlur(.systemMaterial)) - .frame(width: 300, height: 100) - .cornerRadius(16) - .overlay { - Text("Content over blur") - .font(.headline) - } - } - } -} - -@available(iOS 17.0, *) -#Preview("Glass Effect", traits: .fixedLayout(width: 400, height: 400)) { - ZStack { - LinearGradient( - colors: [.orange, .pink], - startPoint: .top, - endPoint: .bottom - ) - .ignoresSafeArea() - - VStack(spacing: 20) { - if #available(iOS 26.0, *) { - Text("Glass Effect (iOS 26+)") - .font(.title3) - - VisualEffect(style: .glass(.regular)) - .frame(width: 300, height: 100) - .cornerRadius(20) - .overlay { - Text("Regular Glass") - } - - VisualEffect(style: .glass(.clear)) - .frame(width: 300, height: 100) - .cornerRadius(20) - .overlay { - Text("Clear Glass") - } - } else { - Text("Glass Effect") - .font(.title3) - Text("(Fallback on iOS < 26)") - .font(.caption) - - VisualEffect(style: .glass(.regular)) - .frame(width: 300, height: 100) - .cornerRadius(20) - .overlay { - Text("Regular Glass Fallback") - } - } - } - } -} - // MARK: - Color Extension private extension Color { From 21bb2eb4c682c5aecbef2e16dbc9fb52d67ac3e3 Mon Sep 17 00:00:00 2001 From: Lasha Efremidze Date: Sun, 28 Dec 2025 17:42:07 -0800 Subject: [PATCH 23/40] Update project.pbxproj --- VisualEffectView.xcodeproj/project.pbxproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/VisualEffectView.xcodeproj/project.pbxproj b/VisualEffectView.xcodeproj/project.pbxproj index 82d1ef5..98a28ac 100755 --- a/VisualEffectView.xcodeproj/project.pbxproj +++ b/VisualEffectView.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 63; objects = { /* Begin PBXBuildFile section */ @@ -346,7 +346,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.6; + IPHONEOS_DEPLOYMENT_TARGET = 18.6; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -405,7 +405,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.6; + IPHONEOS_DEPLOYMENT_TARGET = 18.6; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; From 9f1950058593e1e97762f15cbb75a5ab16a2b60c Mon Sep 17 00:00:00 2001 From: Lasha Efremidze Date: Sun, 28 Dec 2025 17:42:11 -0800 Subject: [PATCH 24/40] Update VisualEffectView.swift --- Sources/VisualEffectView/VisualEffectView.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/VisualEffectView/VisualEffectView.swift b/Sources/VisualEffectView/VisualEffectView.swift index 16da22c..89d76e3 100644 --- a/Sources/VisualEffectView/VisualEffectView.swift +++ b/Sources/VisualEffectView/VisualEffectView.swift @@ -36,14 +36,14 @@ open class VisualEffectView: UIVisualEffectView { // MARK: - Public Style API - public enum VisualEffectStyle: Sendable, Equatable { + public enum VisualEffectStyle: Sendable, Hashable { case none case systemBlur(UIBlurEffect.Style) case customBlur case glass(GlassStyle) // iOS 26+ } - public enum GlassStyle: Sendable, Equatable { + public enum GlassStyle: Sendable, Hashable { case regular case clear From 7605f3ad3aa19667615b56f809e236c32997a622 Mon Sep 17 00:00:00 2001 From: Lasha Efremidze Date: Sun, 28 Dec 2025 17:47:10 -0800 Subject: [PATCH 25/40] Update ContentView.swift --- Example/ContentView.swift | 319 +++++++++++++++++++++++++------------- 1 file changed, 210 insertions(+), 109 deletions(-) diff --git a/Example/ContentView.swift b/Example/ContentView.swift index 84ea639..999b44f 100644 --- a/Example/ContentView.swift +++ b/Example/ContentView.swift @@ -10,135 +10,236 @@ import SwiftUI import VisualEffectView struct ContentView: View { - @State private var blurRadius: CGFloat = 0 + typealias VisualEffectStyle = VisualEffectView.VisualEffectStyle + @State private var blurRadius: CGFloat = 18 + @State private var colorTintAlpha: CGFloat = 0.5 + @State private var saturation: CGFloat = 1.0 var body: some View { - ZStack(alignment: .bottom) { - LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 16) { - ForEach(Color.colors, id: \.self) { color in - ZStack { - Image(systemName: "swift") - .resizable() - .scaledToFit() - .frame(width: 50, height: 100) - .foregroundStyle(.black) + ZStack { + // Animated background + AnimatedBackground() + .ignoresSafeArea() + + ScrollView { + VStack(spacing: 32) { + // Header + VStack(spacing: 8) { + Text("VisualEffectView") + .font(.largeTitle) + .fontWeight(.bold) + Text("Dynamic blur effects with style-based API") + .font(.subheadline) + .foregroundStyle(.secondary) + } + .padding(.top, 20) + + // Custom Blur Section + VStack(alignment: .leading, spacing: 16) { + Text("Custom Blur") + .font(.title2) + .fontWeight(.semibold) + .padding(.horizontal, 4) + + // Custom blur controls + VStack(alignment: .leading, spacing: 16) { + VStack(spacing: 12) { + VStack(alignment: .leading, spacing: 4) { + HStack { + Text("Blur Radius") + Spacer() + Text("\(Int(blurRadius))") + .foregroundStyle(.secondary) + } + Slider(value: $blurRadius, in: 0...30) + } + + VStack(alignment: .leading, spacing: 4) { + HStack { + Text("Tint Alpha") + Spacer() + Text(String(format: "%.2f", colorTintAlpha)) + .foregroundStyle(.secondary) + } + Slider(value: $colorTintAlpha, in: 0...1) + } + + VStack(alignment: .leading, spacing: 4) { + HStack { + Text("Saturation") + Spacer() + Text(String(format: "%.2f", saturation)) + .foregroundStyle(.secondary) + } + Slider(value: $saturation, in: 0...3) + } + } + } + .padding() + .background { + RoundedRectangle(cornerRadius: 16) + .fill(.ultraThinMaterial) + } - VisualEffect( - colorTint: color, - colorTintAlpha: 0.5, - blurRadius: blurRadius + // Custom blur demo + DemoCard( + title: "Custom Blur", + style: .customBlur, + colorTint: .white, + colorTintAlpha: colorTintAlpha, + blurRadius: blurRadius, + saturation: saturation ) + + // Color tint examples + VStack(alignment: .leading, spacing: 12) { + Text("Color Tint Examples") + .font(.headline) + .padding(.horizontal, 4) + + LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 12) { + ForEach([ + ("Red", Color.red), + ("Blue", Color.blue), + ("Green", Color.green), + ("Purple", Color.purple) + ], id: \.0) { name, color in + DemoCard( + title: name, + style: .customBlur, + colorTint: color, + colorTintAlpha: colorTintAlpha, + blurRadius: blurRadius, + saturation: saturation, + height: 120 + ) + } + } + } + } + + // Glass Effect Section + if #available(iOS 17.0, *) { + VStack(alignment: .leading, spacing: 16) { + Text("Glass Effect") + .font(.title2) + .fontWeight(.semibold) + .padding(.horizontal, 4) + + DemoCard( + title: "Glass Regular", + style: .glass(.regular) + ) + } } } + .padding() } - .frame(maxWidth: .infinity, maxHeight: .infinity) - - VStack { - Slider(value: $blurRadius, in: 0...20) - - Text("Slide to blur") - .font(.caption) - .foregroundStyle(.secondary) - } - .padding(.horizontal, 32) - .padding(.bottom, 32) } } } -private extension Color { - static let colors = [red, orange, yellow, green, teal, blue, purple, pink] -} +// MARK: - Demo Card -#Preview { - ContentView() -} - -#Preview("Custom Blur") { - ZStack { - LinearGradient( - colors: [.blue, .purple], - startPoint: .topLeading, - endPoint: .bottomTrailing - ) - .ignoresSafeArea() - - Color.red - .frame(width: 200, height: 100) - - VisualEffect( - colorTint: .green, - colorTintAlpha: 0.5, - blurRadius: 18, - saturation: 2.0 - ) - .frame(width: 300, height: 200) - .cornerRadius(20) +struct DemoCard: View { + let title: String + let style: VisualEffectView.VisualEffectStyle + let colorTint: Color? + let colorTintAlpha: CGFloat + let blurRadius: CGFloat + let saturation: CGFloat + var height: CGFloat = 200 + + init( + title: String, + style: VisualEffectView.VisualEffectStyle, + colorTint: Color? = nil, + colorTintAlpha: CGFloat = 0, + blurRadius: CGFloat = 0, + saturation: CGFloat = 1, + height: CGFloat = 200 + ) { + self.title = title + self.style = style + self.colorTint = colorTint + self.colorTintAlpha = colorTintAlpha + self.blurRadius = blurRadius + self.saturation = saturation + self.height = height } -} - -#Preview("System Blur") { - ZStack { - Image(systemName: "sparkles") - .font(.system(size: 200)) - .foregroundStyle(.blue) - - VStack(spacing: 20) { - Text("System Blur") - .font(.title) - - VisualEffect(style: .systemBlur(.systemMaterial)) - .frame(width: 300, height: 100) - .cornerRadius(16) - .overlay { - Text("Content over blur") + + var body: some View { + ZStack { + Group { + if style == .customBlur { + VisualEffect( + colorTint: colorTint, + colorTintAlpha: colorTintAlpha, + blurRadius: blurRadius, + saturation: saturation + ) + } else { + VisualEffect(style: style) + } + } + .frame(height: height) + .cornerRadius(20) + .overlay { + VStack(spacing: 8) { + Text(title) .font(.headline) + if style == .customBlur { + Text("Blur: \(Int(blurRadius))") + .font(.caption) + .foregroundStyle(.secondary) + } } + .padding() + } } } } -@available(iOS 17.0, *) -#Preview("Glass Effect", traits: .fixedLayout(width: 400, height: 400)) { - ZStack { - LinearGradient( - colors: [.orange, .pink], - startPoint: .top, - endPoint: .bottom - ) - .ignoresSafeArea() - - VStack(spacing: 20) { - if #available(iOS 26.0, *) { - Text("Glass Effect (iOS 26+)") - .font(.title3) - - VisualEffect(style: .glass(.regular)) - .frame(width: 300, height: 100) - .cornerRadius(20) - .overlay { - Text("Regular Glass") - } - - VisualEffect(style: .glass(.clear)) - .frame(width: 300, height: 100) - .cornerRadius(20) - .overlay { - Text("Clear Glass") - } - } else { - Text("Glass Effect") - .font(.title3) - Text("(Fallback on iOS < 26)") - .font(.caption) - - VisualEffect(style: .glass(.regular)) - .frame(width: 300, height: 100) - .cornerRadius(20) - .overlay { - Text("Regular Glass Fallback") - } +// MARK: - Animated Background + +struct AnimatedBackground: View { + @State private var rotation: Double = 0 + + var body: some View { + ZStack { + LinearGradient( + colors: [.blue, .purple, .pink, .orange], + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + + // Animated shapes + ForEach(0..<3, id: \.self) { index in + Circle() + .fill( + LinearGradient( + colors: [.white.opacity(0.2), .clear], + startPoint: .top, + endPoint: .bottom + ) + ) + .frame(width: 200 + Double(index) * 100) + .offset( + x: cos(rotation + Double(index) * 2) * 100, + y: sin(rotation + Double(index) * 2) * 100 + ) + } + } + .onAppear { + withAnimation(.linear(duration: 20).repeatForever(autoreverses: false)) { + rotation = .pi * 2 } } } } + +// MARK: - Preview + +#Preview { + ContentView() +} From ffb6789d39a301dde9e133e284c4527c095e8a9f Mon Sep 17 00:00:00 2001 From: Lasha Efremidze Date: Sun, 28 Dec 2025 17:52:44 -0800 Subject: [PATCH 26/40] Update project.pbxproj --- VisualEffectView.xcodeproj/project.pbxproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/VisualEffectView.xcodeproj/project.pbxproj b/VisualEffectView.xcodeproj/project.pbxproj index 98a28ac..fe0d274 100755 --- a/VisualEffectView.xcodeproj/project.pbxproj +++ b/VisualEffectView.xcodeproj/project.pbxproj @@ -491,6 +491,7 @@ INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 26.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -529,6 +530,7 @@ INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 26.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", From 0de757873c50fd8927c8a86ef22f0ef2cb715b75 Mon Sep 17 00:00:00 2001 From: Lasha Efremidze Date: Sun, 28 Dec 2025 18:07:01 -0800 Subject: [PATCH 27/40] Update VisualEffectView.swift --- Sources/VisualEffectView/VisualEffectView.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Sources/VisualEffectView/VisualEffectView.swift b/Sources/VisualEffectView/VisualEffectView.swift index 89d76e3..e1314a7 100644 --- a/Sources/VisualEffectView/VisualEffectView.swift +++ b/Sources/VisualEffectView/VisualEffectView.swift @@ -47,6 +47,7 @@ open class VisualEffectView: UIVisualEffectView { case regular case clear + #if compiler(>=6.0) @available(iOS 26.0, *) fileprivate var uiStyle: UIGlassEffect.Style { switch self { @@ -54,6 +55,7 @@ open class VisualEffectView: UIVisualEffectView { case .clear: return .clear } } + #endif /// Fallback blur style for iOS < 26 fileprivate var fallbackBlurStyle: UIBlurEffect.Style { @@ -228,12 +230,17 @@ private extension VisualEffectView { reapplyCustomSnapshot() case .glass(let glass): + #if compiler(>=6.0) if #available(iOS 26.0, *) { self.effect = UIGlassEffect(style: glass.uiStyle) } else { // Graceful fallback on older OS with style-appropriate blur self.effect = UIBlurEffect(style: glass.fallbackBlurStyle) } + #else + // Fallback for older compilers that don't have UIGlassEffect + self.effect = UIBlurEffect(style: glass.fallbackBlurStyle) + #endif } } From 1316b5e5f51833c46b4f96fc6b10afc1954f37a9 Mon Sep 17 00:00:00 2001 From: Lasha Efremidze Date: Sun, 28 Dec 2025 18:08:06 -0800 Subject: [PATCH 28/40] Revert "Update project.pbxproj" This reverts commit ffb6789d39a301dde9e133e284c4527c095e8a9f. --- VisualEffectView.xcodeproj/project.pbxproj | 2 -- 1 file changed, 2 deletions(-) diff --git a/VisualEffectView.xcodeproj/project.pbxproj b/VisualEffectView.xcodeproj/project.pbxproj index fe0d274..98a28ac 100755 --- a/VisualEffectView.xcodeproj/project.pbxproj +++ b/VisualEffectView.xcodeproj/project.pbxproj @@ -491,7 +491,6 @@ INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 26.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -530,7 +529,6 @@ INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 26.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", From 469382e8d46b43683057b2a10e46fcee76226689 Mon Sep 17 00:00:00 2001 From: Lasha Efremidze Date: Sun, 28 Dec 2025 18:11:30 -0800 Subject: [PATCH 29/40] Update ContentView.swift --- Example/ContentView.swift | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/Example/ContentView.swift b/Example/ContentView.swift index 999b44f..4ea42f6 100644 --- a/Example/ContentView.swift +++ b/Example/ContentView.swift @@ -119,18 +119,16 @@ struct ContentView: View { } // Glass Effect Section - if #available(iOS 17.0, *) { - VStack(alignment: .leading, spacing: 16) { - Text("Glass Effect") - .font(.title2) - .fontWeight(.semibold) - .padding(.horizontal, 4) - - DemoCard( - title: "Glass Regular", - style: .glass(.regular) - ) - } + VStack(alignment: .leading, spacing: 16) { + Text("Glass Effect") + .font(.title2) + .fontWeight(.semibold) + .padding(.horizontal, 4) + + DemoCard( + title: "Glass Regular", + style: .glass(.regular) + ) } } .padding() From 1b5655ee8127a81354097431cd7be8b4bc08e43c Mon Sep 17 00:00:00 2001 From: Lasha Efremidze Date: Sun, 28 Dec 2025 18:12:43 -0800 Subject: [PATCH 30/40] updated project --- VisualEffectView.xcodeproj/project.pbxproj | 8 +++++--- .../xcshareddata/xcschemes/Example.xcscheme | 2 +- .../xcshareddata/xcschemes/VisualEffectView.xcscheme | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/VisualEffectView.xcodeproj/project.pbxproj b/VisualEffectView.xcodeproj/project.pbxproj index 98a28ac..fd52ef3 100755 --- a/VisualEffectView.xcodeproj/project.pbxproj +++ b/VisualEffectView.xcodeproj/project.pbxproj @@ -209,7 +209,7 @@ attributes = { BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 1620; - LastUpgradeCheck = 1620; + LastUpgradeCheck = 2620; ORGANIZATIONNAME = "Lasha Efremidze"; TargetAttributes = { 8B3243FD1E07D7CB00712FEA = { @@ -350,9 +350,10 @@ MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; + STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -408,9 +409,10 @@ IPHONEOS_DEPLOYMENT_TARGET = 18.6; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; + STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; diff --git a/VisualEffectView.xcodeproj/xcshareddata/xcschemes/Example.xcscheme b/VisualEffectView.xcodeproj/xcshareddata/xcschemes/Example.xcscheme index 4d3e190..e281e9a 100644 --- a/VisualEffectView.xcodeproj/xcshareddata/xcschemes/Example.xcscheme +++ b/VisualEffectView.xcodeproj/xcshareddata/xcschemes/Example.xcscheme @@ -1,6 +1,6 @@ Date: Sun, 28 Dec 2025 18:16:25 -0800 Subject: [PATCH 31/40] Update Package.swift --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index a200332..4137217 100644 --- a/Package.swift +++ b/Package.swift @@ -23,5 +23,5 @@ let package = Package( // Targets can depend on other targets in this package, and on products in packages which this package depends on. .target(name: "VisualEffectView") ], - swiftLanguageVersions: [.v5] + swiftLanguageVersions: [.v5, .version("6")] ) From bcf5406854d95de56bd0b9bd441da28cedc07092 Mon Sep 17 00:00:00 2001 From: Lasha Efremidze Date: Sun, 28 Dec 2025 18:17:17 -0800 Subject: [PATCH 32/40] Update VisualEffectView.podspec --- VisualEffectView.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VisualEffectView.podspec b/VisualEffectView.podspec index 7a92112..30e0493 100644 --- a/VisualEffectView.podspec +++ b/VisualEffectView.podspec @@ -16,6 +16,6 @@ Pod::Spec.new do |s| s.summary = "Dynamic blur background view with tint color (UIVisualEffectView subclass)" s.source = { :git => 'https://github.com/efremidze/VisualEffectView.git', :tag => s.version } s.source_files = "Sources/*.swift" - s.swift_version = '5.0' + s.swift_version = '5.9' s.ios.deployment_target = '14.0' end From f17e05cfa226fda44d8e27ba93722843abfdbd48 Mon Sep 17 00:00:00 2001 From: Lasha Efremidze Date: Sun, 28 Dec 2025 18:22:20 -0800 Subject: [PATCH 33/40] Update VisualEffectView.swift --- Sources/VisualEffectView/VisualEffectView.swift | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/Sources/VisualEffectView/VisualEffectView.swift b/Sources/VisualEffectView/VisualEffectView.swift index e1314a7..80dc7b4 100644 --- a/Sources/VisualEffectView/VisualEffectView.swift +++ b/Sources/VisualEffectView/VisualEffectView.swift @@ -47,15 +47,13 @@ open class VisualEffectView: UIVisualEffectView { case regular case clear - #if compiler(>=6.0) - @available(iOS 26.0, *) - fileprivate var uiStyle: UIGlassEffect.Style { + /// Raw style value for UIGlassEffect (0 = regular, 1 = clear) + fileprivate var rawValue: Int { switch self { - case .regular: return .regular - case .clear: return .clear + case .regular: return 0 + case .clear: return 1 } } - #endif /// Fallback blur style for iOS < 26 fileprivate var fallbackBlurStyle: UIBlurEffect.Style { @@ -230,17 +228,12 @@ private extension VisualEffectView { reapplyCustomSnapshot() case .glass(let glass): - #if compiler(>=6.0) if #available(iOS 26.0, *) { self.effect = UIGlassEffect(style: glass.uiStyle) } else { // Graceful fallback on older OS with style-appropriate blur self.effect = UIBlurEffect(style: glass.fallbackBlurStyle) } - #else - // Fallback for older compilers that don't have UIGlassEffect - self.effect = UIBlurEffect(style: glass.fallbackBlurStyle) - #endif } } From d32bc43c29070ecda562da4a8707ae2b61d4936c Mon Sep 17 00:00:00 2001 From: Lasha Efremidze Date: Sun, 28 Dec 2025 18:23:30 -0800 Subject: [PATCH 34/40] Update VisualEffectView.swift --- Sources/VisualEffectView/VisualEffectView.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/VisualEffectView/VisualEffectView.swift b/Sources/VisualEffectView/VisualEffectView.swift index 80dc7b4..e981a5d 100644 --- a/Sources/VisualEffectView/VisualEffectView.swift +++ b/Sources/VisualEffectView/VisualEffectView.swift @@ -227,12 +227,12 @@ private extension VisualEffectView { // Re-apply settings snapshot so switching styles is reversible reapplyCustomSnapshot() - case .glass(let glass): + case .glass(let style): if #available(iOS 26.0, *) { - self.effect = UIGlassEffect(style: glass.uiStyle) + self.effect = createGlassEffect(style: style) } else { // Graceful fallback on older OS with style-appropriate blur - self.effect = UIBlurEffect(style: glass.fallbackBlurStyle) + self.effect = UIBlurEffect(style: style.fallbackBlurStyle) } } } From f82ad7a75c56afdafe10b4e90ce8408909d7c798 Mon Sep 17 00:00:00 2001 From: Lasha Efremidze Date: Sun, 28 Dec 2025 18:26:28 -0800 Subject: [PATCH 35/40] Update VisualEffectView.swift --- Sources/VisualEffectView/VisualEffectView.swift | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Sources/VisualEffectView/VisualEffectView.swift b/Sources/VisualEffectView/VisualEffectView.swift index e981a5d..961b10e 100644 --- a/Sources/VisualEffectView/VisualEffectView.swift +++ b/Sources/VisualEffectView/VisualEffectView.swift @@ -237,6 +237,19 @@ private extension VisualEffectView { } } + /// Creates UIGlassEffect using runtime reflection to avoid compile-time SDK dependency + func createGlassEffect(style: GlassStyle) -> UIVisualEffect? { + // Check if UIGlassEffect exists at runtime (iOS 26+) + guard let glassEffectClass = NSClassFromString("UIGlassEffect") as? NSObject.Type else { + return nil + } + + // Create the effect using KVC to avoid compile-time type checking + let glassEffect = glassEffectClass.init() + glassEffect.setValue(style.rawValue, forKey: "style") + return glassEffect as? UIVisualEffect + } + func reapplyCustomSnapshot() { // Apply in a safe order; these call into the existing custom pipeline self.scale = customSnapshot.scale From c1bf56e3249609c3e4f43ecf42f37341fe50ad66 Mon Sep 17 00:00:00 2001 From: Lasha Efremidze Date: Sun, 28 Dec 2025 18:29:07 -0800 Subject: [PATCH 36/40] Update ContentView.swift --- Example/ContentView.swift | 51 ++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/Example/ContentView.swift b/Example/ContentView.swift index 4ea42f6..ea57d8b 100644 --- a/Example/ContentView.swift +++ b/Example/ContentView.swift @@ -201,36 +201,33 @@ struct DemoCard: View { // MARK: - Animated Background struct AnimatedBackground: View { - @State private var rotation: Double = 0 - var body: some View { - ZStack { - LinearGradient( - colors: [.blue, .purple, .pink, .orange], - startPoint: .topLeading, - endPoint: .bottomTrailing - ) + TimelineView(.animation) { timeline in + let time = timeline.date.timeIntervalSinceReferenceDate - // Animated shapes - ForEach(0..<3, id: \.self) { index in - Circle() - .fill( - LinearGradient( - colors: [.white.opacity(0.2), .clear], - startPoint: .top, - endPoint: .bottom + ZStack { + LinearGradient( + colors: [.blue, .purple, .pink, .orange], + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + + // Animated shapes + ForEach(0..<3, id: \.self) { index in + Circle() + .fill( + LinearGradient( + colors: [.white.opacity(0.2), .clear], + startPoint: .top, + endPoint: .bottom + ) ) - ) - .frame(width: 200 + Double(index) * 100) - .offset( - x: cos(rotation + Double(index) * 2) * 100, - y: sin(rotation + Double(index) * 2) * 100 - ) - } - } - .onAppear { - withAnimation(.linear(duration: 20).repeatForever(autoreverses: false)) { - rotation = .pi * 2 + .frame(width: 200 + Double(index) * 100) + .offset( + x: cos(time / 5 + Double(index) * 2) * 100, + y: sin(time / 5 + Double(index) * 2) * 100 + ) + } } } } From a6ed0315cebee2bfe38c5882a1f4079c94ff4216 Mon Sep 17 00:00:00 2001 From: Lasha Efremidze Date: Sun, 28 Dec 2025 18:30:40 -0800 Subject: [PATCH 37/40] Update ContentView.swift --- Example/ContentView.swift | 71 +++++++++++++++------------------------ 1 file changed, 28 insertions(+), 43 deletions(-) diff --git a/Example/ContentView.swift b/Example/ContentView.swift index ea57d8b..1b42206 100644 --- a/Example/ContentView.swift +++ b/Example/ContentView.swift @@ -142,58 +142,43 @@ struct ContentView: View { struct DemoCard: View { let title: String let style: VisualEffectView.VisualEffectStyle - let colorTint: Color? - let colorTintAlpha: CGFloat - let blurRadius: CGFloat - let saturation: CGFloat + var colorTint: Color? = nil + var colorTintAlpha: CGFloat = 0 + var blurRadius: CGFloat = 0 + var saturation: CGFloat = 1 var height: CGFloat = 200 - init( - title: String, - style: VisualEffectView.VisualEffectStyle, - colorTint: Color? = nil, - colorTintAlpha: CGFloat = 0, - blurRadius: CGFloat = 0, - saturation: CGFloat = 1, - height: CGFloat = 200 - ) { - self.title = title - self.style = style - self.colorTint = colorTint - self.colorTintAlpha = colorTintAlpha - self.blurRadius = blurRadius - self.saturation = saturation - self.height = height + private var isCustomBlur: Bool { + if case .customBlur = style { return true } + return false } var body: some View { ZStack { - Group { - if style == .customBlur { - VisualEffect( - colorTint: colorTint, - colorTintAlpha: colorTintAlpha, - blurRadius: blurRadius, - saturation: saturation - ) - } else { - VisualEffect(style: style) - } + if isCustomBlur { + VisualEffect( + colorTint: colorTint, + colorTintAlpha: colorTintAlpha, + blurRadius: blurRadius, + saturation: saturation + ) + } else { + VisualEffect(style: style) } - .frame(height: height) - .cornerRadius(20) - .overlay { - VStack(spacing: 8) { - Text(title) - .font(.headline) - if style == .customBlur { - Text("Blur: \(Int(blurRadius))") - .font(.caption) - .foregroundStyle(.secondary) - } + } + .frame(height: height) + .clipShape(RoundedRectangle(cornerRadius: 20)) + .overlay { + VStack(spacing: 8) { + Text(title) + .font(.headline) + if isCustomBlur { + Text("Blur: \(Int(blurRadius))") + .font(.caption) + .foregroundStyle(.secondary) } - .padding() } + .padding() } } } From ac6b9fa1b2514d38238bdf7da0207b43463ccd0e Mon Sep 17 00:00:00 2001 From: Lasha Efremidze Date: Sun, 28 Dec 2025 18:40:15 -0800 Subject: [PATCH 38/40] Update ContentView.swift --- Example/ContentView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Example/ContentView.swift b/Example/ContentView.swift index 1b42206..6a179d8 100644 --- a/Example/ContentView.swift +++ b/Example/ContentView.swift @@ -142,7 +142,7 @@ struct ContentView: View { struct DemoCard: View { let title: String let style: VisualEffectView.VisualEffectStyle - var colorTint: Color? = nil + var colorTint: Color? var colorTintAlpha: CGFloat = 0 var blurRadius: CGFloat = 0 var saturation: CGFloat = 1 From bf8bf07fa4894401fd4e77e9ea9aebe90a180752 Mon Sep 17 00:00:00 2001 From: Lasha Efremidze Date: Sun, 28 Dec 2025 18:42:09 -0800 Subject: [PATCH 39/40] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5fd0a00..95b354a 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ github "efremidze/VisualEffectView" Add VisualEffectView as a dependency in your `Package.swift` file: ```swift dependencies: [ - .package(url: "https://github.com/efremidze/VisualEffectView.git", from: "5.0.0") + .package(url: "https://github.com/efremidze/VisualEffectView.git", from: "6.0.0") ] ``` From 9685e293a493ba5ddb5094f644e6ff85ea832f2f Mon Sep 17 00:00:00 2001 From: Lasha Efremidze Date: Sun, 28 Dec 2025 18:45:17 -0800 Subject: [PATCH 40/40] Update VisualEffectView.podspec --- VisualEffectView.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VisualEffectView.podspec b/VisualEffectView.podspec index 30e0493..5f7cf84 100644 --- a/VisualEffectView.podspec +++ b/VisualEffectView.podspec @@ -15,7 +15,7 @@ Pod::Spec.new do |s| s.documentation_url = 'https://efremidze.github.io/VisualEffectView/' s.summary = "Dynamic blur background view with tint color (UIVisualEffectView subclass)" s.source = { :git => 'https://github.com/efremidze/VisualEffectView.git', :tag => s.version } - s.source_files = "Sources/*.swift" + s.source_files = "Sources/VisualEffectView/*.{swift,h}" s.swift_version = '5.9' s.ios.deployment_target = '14.0' end