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
diff --git a/Example/ContentView.swift b/Example/ContentView.swift
index e6ad6e3..6a179d8 100644
--- a/Example/ContentView.swift
+++ b/Example/ContentView.swift
@@ -10,45 +10,215 @@ 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)
+ }
+
+ // 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
+ VStack(alignment: .leading, spacing: 16) {
+ Text("Glass Effect")
+ .font(.title2)
+ .fontWeight(.semibold)
+ .padding(.horizontal, 4)
- VisualEffect(
- colorTint: color,
- colorTintAlpha: 0.5,
- blurRadius: blurRadius
+ DemoCard(
+ title: "Glass Regular",
+ style: .glass(.regular)
)
}
}
+ .padding()
+ }
+ }
+ }
+}
+
+// MARK: - Demo Card
+
+struct DemoCard: View {
+ let title: String
+ let style: VisualEffectView.VisualEffectStyle
+ var colorTint: Color?
+ var colorTintAlpha: CGFloat = 0
+ var blurRadius: CGFloat = 0
+ var saturation: CGFloat = 1
+ var height: CGFloat = 200
+
+ private var isCustomBlur: Bool {
+ if case .customBlur = style { return true }
+ return false
+ }
+
+ var body: some View {
+ ZStack {
+ if isCustomBlur {
+ VisualEffect(
+ colorTint: colorTint,
+ colorTintAlpha: colorTintAlpha,
+ blurRadius: blurRadius,
+ saturation: saturation
+ )
+ } else {
+ VisualEffect(style: style)
+ }
+ }
+ .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)
+ }
}
- .frame(maxWidth: .infinity, maxHeight: .infinity)
+ .padding()
+ }
+ }
+}
+
+// MARK: - Animated Background
+
+struct AnimatedBackground: View {
+ var body: some View {
+ TimelineView(.animation) { timeline in
+ let time = timeline.date.timeIntervalSinceReferenceDate
- VStack {
- Slider(value: $blurRadius, in: 0...20)
+ ZStack {
+ LinearGradient(
+ colors: [.blue, .purple, .pink, .orange],
+ startPoint: .topLeading,
+ endPoint: .bottomTrailing
+ )
- Text("Slide to blur")
- .font(.caption)
- .foregroundStyle(.secondary)
+ // 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(time / 5 + Double(index) * 2) * 100,
+ y: sin(time / 5 + Double(index) * 2) * 100
+ )
+ }
}
- .padding(.horizontal, 32)
- .padding(.bottom, 32)
}
}
}
-private extension Color {
- static let colors = [red, orange, yellow, green, teal, blue, purple, pink]
-}
+// MARK: - Preview
#Preview {
ContentView()
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")]
)
diff --git a/README.md b/README.md
index eb01634..95b354a 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@
[](https://swift.org)
[](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: "6.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
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.
diff --git a/Sources/VisualEffectView/VisualEffectView+SwiftUI.swift b/Sources/VisualEffectView/VisualEffectView+SwiftUI.swift
index 8c80fb4..dd977b8 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.
//
@@ -11,59 +11,96 @@ 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 {
- /**
- 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 for backward compatibility.
+ let style: VisualEffectStyle?
+
+ // MARK: - Blur parameters (customBlur 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
-
+ let saturation: CGFloat
+ let scale: CGFloat
+
+ // MARK: - Initializers
+
/**
- The saturation factor.
+ Style-based initializer (recommended).
- Values above 1.0 increase saturation, values below 1.0 decrease saturation, and 1.0 maintains original saturation.
+ Use this for system blur, glass effects, or explicit custom blur.
- The default value is `1.0`.
- */
- let saturation: CGFloat
-
- /**
- The scale factor for the blur effect.
+ - Parameter style: The visual effect style to apply.
- The default value is `1.0`.
+ ## Example
+ ```swift
+ VisualEffect(style: .systemBlur(.dark))
+ VisualEffect(style: .glass(.regular))
+ VisualEffect(style: .customBlur)
+ ```
*/
- let scale: CGFloat
+ public init(style: VisualEffectStyle) {
+ self.style = style
+ self.colorTint = nil
+ self.colorTintAlpha = 0
+ self.blurRadius = 0
+ self.saturation = 1
+ self.scale = 1
+ }
/**
- Initializes a `VisualEffect` view with the specified parameters.
+ Legacy customizable blur initializer.
+
+ Uses the customizable blur pipeline with fine-grained control over blur parameters.
+ Maintained for backward compatibility.
- 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`.
+ - 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, 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 // nil signals legacy mode
self.colorTint = colorTint
self.colorTintAlpha = colorTintAlpha
self.blurRadius = blurRadius
@@ -71,42 +108,48 @@ public struct VisualEffect: UIViewRepresentable {
self.scale = scale
}
+ // 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
+ // else: keep view's default (.customBlur) for backward compatibility
+ 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 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 {
- 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
}
}
-#Preview {
- ZStack {
- Color.blue
- .frame(width: 400, height: 400)
- Color.red
- .frame(width: 200, height: 100)
- VisualEffect(colorTint: .white, colorTintAlpha: 0.5, blurRadius: 18, saturation: 2.0)
- .frame(width: 300, height: 200)
- }
-}
+// MARK: - Color Extension
private extension Color {
func uiColor() -> UIColor {
diff --git a/Sources/VisualEffectView/VisualEffectView.swift b/Sources/VisualEffectView/VisualEffectView.swift
index 0f1f0e6..961b10e 100644
--- a/Sources/VisualEffectView/VisualEffectView.swift
+++ b/Sources/VisualEffectView/VisualEffectView.swift
@@ -9,15 +9,95 @@
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 {
/// Returns the instance of UIBlurEffect.
private let blurEffect = (NSClassFromString("_UICustomBlurEffect") as! UIBlurEffect.Type).init()
+ // MARK: - Public Style API
+
+ public enum VisualEffectStyle: Sendable, Hashable {
+ case none
+ case systemBlur(UIBlurEffect.Style)
+ case customBlur
+ case glass(GlassStyle) // iOS 26+
+ }
+
+ public enum GlassStyle: Sendable, Hashable {
+ case regular
+ case clear
+
+ /// Raw style value for UIGlassEffect (0 = regular, 1 = clear)
+ fileprivate var rawValue: Int {
+ switch self {
+ case .regular: return 0
+ case .clear: return 1
+ }
+ }
+
+ /// 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.
+ /// 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 {
+ guard style != oldValue else { return }
+ 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
+ )
+
+ // MARK: - Custom Blur Properties
+
/**
Tint color.
+ - Note: Only applies when `style` is `.customBlur`.
The default value is nil.
*/
open var colorTint: UIColor? {
@@ -25,6 +105,9 @@ open class VisualEffectView: UIVisualEffectView {
return sourceOver?.value(forKeyPath: "color") as? UIColor
}
set {
+ customSnapshot.colorTint = newValue
+ guard case .customBlur = style else { return }
+
prepareForChanges()
sourceOver?.setValue(newValue, forKeyPath: "color")
sourceOver?.perform(Selector(("applyRequestedEffectToView:")), with: overlayView)
@@ -35,7 +118,8 @@ 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.
*/
@@ -47,6 +131,7 @@ open class VisualEffectView: UIVisualEffectView {
/**
Blur radius.
+ - Note: Only applies when `style` is `.customBlur`.
The default value is 0.0.
*/
open var blurRadius: CGFloat {
@@ -54,6 +139,9 @@ open class VisualEffectView: UIVisualEffectView {
return gaussianBlur?.requestedValues?["inputRadius"] as? CGFloat ?? 0
}
set {
+ customSnapshot.blurRadius = newValue
+ guard case .customBlur = style else { return }
+
prepareForChanges()
gaussianBlur?.requestedValues?["inputRadius"] = newValue
applyChanges()
@@ -65,11 +153,17 @@ 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 {
get { return _value(forKey: .saturationDeltaFactor) ?? 1.0 }
- set { _setValue(newValue, forKey: .saturationDeltaFactor) }
+ set {
+ customSnapshot.saturation = newValue
+ guard case .customBlur = style else { return }
+
+ _setValue(newValue, forKey: .saturationDeltaFactor)
+ }
}
/**
@@ -77,25 +171,91 @@ 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 {
get { return _value(forKey: .scale) ?? 1.0 }
- set { _setValue(newValue, forKey: .scale) }
+ 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)
- scale = 1
+ // 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)
+ // Interface Builder instances default to custom blur
+ applyStyle(.customBlur)
+ }
+
+}
+
+// MARK: - Style
+
+private extension VisualEffectView {
+
+ func applyStyle(_ style: VisualEffectStyle) {
+ switch style {
+ case .none:
+ self.effect = nil
+
+ case .systemBlur(let style):
+ self.effect = UIBlurEffect(style: style)
+
+ case .customBlur:
+ // Switch back to custom effect object
+ self.effect = blurEffect
+ // Re-apply settings snapshot so switching styles is reversible
+ reapplyCustomSnapshot()
+
+ case .glass(let style):
+ if #available(iOS 26.0, *) {
+ self.effect = createGlassEffect(style: style)
+ } else {
+ // Graceful fallback on older OS with style-appropriate blur
+ self.effect = UIBlurEffect(style: style.fallbackBlurStyle)
+ }
+ }
+ }
+
+ /// 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
+ }
- scale = 1
+ // 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
+ self.saturation = customSnapshot.saturation
+ self.blurRadius = customSnapshot.blurRadius
+ self.colorTint = customSnapshot.colorTint
}
}
@@ -120,4 +280,5 @@ private extension VisualEffectView {
}
+// Available keys for reference:
// ["grayscaleTintLevel", "grayscaleTintAlpha", "lightenGrayscaleWithSourceOver", "colorTint", "colorTintAlpha", "colorBurnTintLevel", "colorBurnTintAlpha", "darkeningTintAlpha", "darkeningTintHue", "darkeningTintSaturation", "darkenWithSourceOver", "blurRadius", "saturationDeltaFactor", "scale", "zoom"]
diff --git a/VisualEffectView.podspec b/VisualEffectView.podspec
index 5b352f5..5f7cf84 100644
--- a/VisualEffectView.podspec
+++ b/VisualEffectView.podspec
@@ -8,14 +8,14 @@
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" }
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.swift_version = '5.0'
+ s.source_files = "Sources/VisualEffectView/*.{swift,h}"
+ s.swift_version = '5.9'
s.ios.deployment_target = '14.0'
end
diff --git a/VisualEffectView.xcodeproj/project.pbxproj b/VisualEffectView.xcodeproj/project.pbxproj
index 82d1ef5..fd52ef3 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 */
@@ -209,7 +209,7 @@
attributes = {
BuildIndependentTargetsInParallel = YES;
LastSwiftUpdateCheck = 1620;
- LastUpgradeCheck = 1620;
+ LastUpgradeCheck = 2620;
ORGANIZATIONNAME = "Lasha Efremidze";
TargetAttributes = {
8B3243FD1E07D7CB00712FEA = {
@@ -346,13 +346,14 @@
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;
+ 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 = "";
@@ -405,12 +406,13 @@
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;
+ 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 @@