Skip to content

Instantly share code, notes, and snippets.

@Netanelshoshan
Created October 11, 2025 15:04
Show Gist options
  • Select an option

  • Save Netanelshoshan/ede7a2fb4b58181b7de8a657dd7c7c66 to your computer and use it in GitHub Desktop.

Select an option

Save Netanelshoshan/ede7a2fb4b58181b7de8a657dd7c7c66 to your computer and use it in GitHub Desktop.
import SwiftUI
// MARK: - Backported Glass Style Enum
enum BackportedGlassStyle {
case regular
case interactive
case tinted(Color)
case tintedInteractive(Color)
@available(iOS 26.0, *)
func toNativeGlass() -> Glass {
switch self {
case .regular:
return .regular
case .interactive:
return .regular.interactive()
case .tinted(let color):
return .regular.tint(color)
case .tintedInteractive(let color):
return .regular.tint(color).interactive()
}
}
func toLegacyMaterial() -> Material {
switch self {
case .regular:
return .ultraThinMaterial
case .interactive:
return .thinMaterial
case .tinted(_), .tintedInteractive(_):
return .regularMaterial
}
}
var tintColor: Color? {
switch self {
case .tinted(let color), .tintedInteractive(let color):
return color
case .regular, .interactive:
return nil
}
}
var isInteractive: Bool {
switch self {
case .interactive, .tintedInteractive(_):
return true
case .regular, .tinted(_):
return false
}
}
func tint(_ color: Color) -> BackportedGlassStyle {
switch self {
case .regular:
return .tinted(color)
case .interactive:
return .tintedInteractive(color)
case .tinted(_):
return .tinted(color)
case .tintedInteractive(_):
return .tintedInteractive(color)
}
}
func interactive() -> BackportedGlassStyle {
switch self {
case .regular:
return .interactive
case .interactive:
return self
case .tinted(let color):
return .tintedInteractive(color)
case .tintedInteractive(_):
return self
}
}
}
// MARK: - Legacy Glass Effect (for older iOS versions)
private struct LegacyGlassModifier<S: Shape>: ViewModifier {
let shape: S
let style: BackportedGlassStyle
func body(content: Content) -> some View {
content.background(
ZStack {
shape.fill(style.toLegacyMaterial())
if let tintColor = style.tintColor {
shape.fill(tintColor.opacity(0.3))
.blendMode(.overlay)
}
}
)
}
}
// MARK: - Modern Glass Effect (for iOS 26+)
@available(iOS 26.0, *)
private struct ModernGlassModifier<S: Shape>: ViewModifier {
let shape: S
let style: BackportedGlassStyle
func body(content: Content) -> some View {
content.glassEffect(style.toNativeGlass(), in: shape)
}
}
// MARK: - Backported View Modifier
extension View {
@ViewBuilder
func glassEffect<S: Shape>(_ style: BackportedGlassStyle = .regular, in shape: S) -> some View {
if #available(iOS 26.0, *) {
self.modifier(ModernGlassModifier(shape: shape, style: style))
} else {
self.modifier(LegacyGlassModifier(shape: shape, style: style))
}
}
}
// MARK: - Backported GlassEffectContainer
struct GlassEffectContainer<Content: View>: View {
let spacing: CGFloat
@ViewBuilder var content: () -> Content
init(spacing: CGFloat = 20.0, @ViewBuilder content: @escaping () -> Content) {
self.spacing = spacing
self.content = content
}
var body: some View {
if #available(iOS 26.0, *) {
SwiftUI.GlassEffectContainer(spacing: spacing) {
content()
}
} else {
content()
}
}
}
// MARK: - Backported Button Styles
struct BackportedGlassButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.padding(.horizontal, 16)
.padding(.vertical, 8)
.background {
if #available(iOS 26.0, *) {
configuration.label
.buttonStyle(SwiftUI.GlassButtonStyle())
} else {// Legacy fallback
RoundedRectangle(cornerRadius: 8)
.fill(.ultraThinMaterial)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(.white.opacity(0.2), lineWidth: 0.5)
)
}
}
.scaleEffect(configuration.isPressed ? 0.98 : 1.0)
.opacity(configuration.isPressed ? 0.8 : 1.0)
.animation(.easeInOut(duration: 0.1), value: configuration.isPressed)
}
}
struct BackportedGlassProminentButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.padding(.horizontal, 20)
.padding(.vertical, 12)
.background {
if #available(iOS 26.0, *) {
configuration.label
.buttonStyle(SwiftUI.GlassProminentButtonStyle())
} else {
// legacy fallback with more prominent appearance
RoundedRectangle(cornerRadius: 10)
.fill(.thickMaterial)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(.white.opacity(0.3), lineWidth: 1)
)
.shadow(color: .black.opacity(0.1), radius: 2, x: 0, y: 1)
}
}
.scaleEffect(configuration.isPressed ? 0.98 : 1.0)
.opacity(configuration.isPressed ? 0.8 : 1.0)
.animation(.easeInOut(duration: 0.1), value: configuration.isPressed)
}
}
// MARK: - Native Glass Modifiers
@available(iOS 26.0, *)
private struct NativeGlassEffectIDModifier<ID: Hashable>: ViewModifier {
let id: ID
let namespace: Namespace.ID
func body(content: Content) -> some View {
content.glassEffectID(id, in: namespace)
}
}
@available(iOS 26.0, *)
private struct NativeGlassEffectUnionModifier<ID: Hashable>: ViewModifier {
let id: ID
let namespace: Namespace.ID
func body(content: Content) -> some View {
content.glassEffectUnion(id: id, namespace: namespace)
}
}
// MARK: - Morphing and Transition Modifiers
extension View {
@ViewBuilder
func glassEffectID<ID: Hashable>(_ id: ID, in namespace: Namespace.ID) -> some View {
if #available(iOS 26.0, *) {
// Use the native SwiftUI glassEffectID method
self.modifier(NativeGlassEffectIDModifier(id: id, namespace: namespace))
} else {
// No-op on older iOS
self
}
}
@ViewBuilder
func glassEffectUnion<ID: Hashable>(id: ID, namespace: Namespace.ID) -> some View {
if #available(iOS 26.0, *) {
self.modifier(NativeGlassEffectUnionModifier(id: id, namespace: namespace))
} else {
// No-op on older iOS versions
self
}
}
}
// MARK: - ButtonStyle Extensions
extension ButtonStyle where Self == BackportedGlassButtonStyle {
static var glass: BackportedGlassButtonStyle {
BackportedGlassButtonStyle()
}
}
extension ButtonStyle where Self == BackportedGlassProminentButtonStyle {
static var glassProminent: BackportedGlassProminentButtonStyle {
BackportedGlassProminentButtonStyle()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment