UniversalGlass

repository·main·Indexed 41 days ago

https://github.com/aeastr/universalglass

A compatibility layer for SwiftUI glass APIs that enables modern glass effects on iOS 17+, macOS 13+, and other platforms. It provides a high-fidelity material fallback for older OS versions and transparently routes to native OS 26 implementations when available. Includes support for glass button styles, effect containers for fused surfaces, and an optional UniversalGlassBackports module for Apple-style shorthand syntax.

Tokens
5.6K
Snippets
22
Records
31
Agent score
50%

What's inside UniversalGlass

  1. Group Glass Effects with Effect Containers

    main

    Use UniversalGlassEffectContainer to group multiple glass-effected views. This allows for advanced grouping behaviors like unioning effects. To link views within a container, use .universalGlassEffectUnion(id:namespace:) with a shared ID and a @Namespace.

    @Namespace private var ns
    
    UniversalGlassEffectContainer {
        HStack {
            AvatarView()
                .universalGlassEffect()
                .universalGlassEffectUnion(id: "profile", namespace: ns)
    
            DetailsView()
                .universalGlassEffect()
                .universalGlassEffectUnion(id: "profile", namespace: ns)
        }
    }
  2. When to use UniversalGlassBackports

    main

    Decide whether to use the backport target based on your development needs:

    Use the Backport Target if:

    • You want to use Apple's glass syntax now without wrapping every call site in #if availability checks.
    • You are maintaining codebases that already use Apple's glass syntax but need to compile against pre-OS 26 SDKs.
    • You are building shared components that must compile on Xcode 15+ while exposing a future-facing API surface.

    Omit the Backport Target if:

    • You prefer to use the explicit universal prefixes (e.g., universalGlassEffect) directly. The core UniversalGlass module works on all supported OS versions without the backport layer.
  3. How the UniversalGlass fallback pipeline works

    main

    When the native glass effect is unavailable (e.g., on older OS versions), UniversalGlassEffectContainer activates a fallback pipeline.

    1. Registration: Child views calling universalGlassEffect register as GlassEffectParticipant via anchor preferences.
    2. Environment Signaling: The container sets the isInFallbackGlassContainer environment flag to true. This signals child modifiers to skip drawing their own glass plate to prevent double layering.
    3. Grouping: Participants are grouped to create a single continuous sheet. Grouping priority is:
      • GlassEffectUnion (namespace + id)
      • Explicit glassEffectID
      • Participant's UUID (no grouping)
    4. Rendering: The GlassEffectFallbackOverlay is drawn twice: once as a background (the merged glass plate) and once as a mask (to clip child content to the container's geometry).
    5. Transitions: UniversalGlassEffectTransition maps .materialize and .matchedGeometry to .transition(.fallbackBlur) on systems without native support to maintain soft animations.
  4. Group multiple views with glass unions

    main

    To create a single cohesive glass slab that merges multiple neighboring elements, use grouping and union helpers. On OS 26+, these forward to SwiftUI. On earlier systems, they use glassEffectParticipantContext to allow a fallback container to merge participants into a single visual unit.

    // Grouping helpers
    View.universalGlassEffectUnion(id:namespace:rendering:)
    View.universalGlassEffectID(_:in:rendering:)
  5. How UniversalGlass works

    main

    UniversalGlass uses runtime availability checks to determine if the device supports native OS 26 glass APIs.

    • On OS 26+: It transparently routes calls to the native SwiftUI glass implementations.
    • On earlier OS versions: It uses a fallback renderer that registers effects as participants via anchor preferences, groups views by union keys/IDs, and draws composite material overlays that respect shapes and transitions.

    Known Limitation: On pre-OS 26 systems, the fallback container ignores the spacing parameter.

  6. How UniversalGlass handles runtime routing and fallbacks

    main

    UniversalGlass uses a runtime routing mechanism via resolveShouldUseGlass(for:) to ensure your UI looks consistent across different OS versions.

    1. Native Mode: If the platform supports OS 26 APIs and you have not forced .material rendering, the library calls the native GlassButtonStyle or GlassProminentButtonStyle directly.
    2. Fallback Mode: If native APIs are unavailable or if you explicitly pass .material, the library uses UniversalGlassLegacyMaterialStyle.

    Fallback Behavior Details:

    • Layout: Applies control-size aware padding to match SwiftUI's default capsule metrics.
    • Visuals: Uses View.universalGlassEffect(_:in:rendering:) to draw an interactive material plate, adds a subtle white stroke, and applies a drop shadow to mimic 'liquid glass'.
    • Interactivity: Includes a scale animation for press feedback (respects reduceMotion).
    • Prominent Fallback: For prominent styles, the fallback fills the capsule with the provided .tint and uses an off-white foreground for text.
  7. Integrate UniversalGlass with SwiftUI

    main

    UniversalGlass styles are standard PrimitiveButtonStyle implementations, making them fully compatible with existing SwiftUI patterns:

    • Modifier Layering: You can layer standard modifiers like .controlSize, .tint, and .font directly onto the button.
    • Coexistence: You can use UniversalGlass helpers alongside other system styles like .borderedProminent.
    • Debugging: You can opt individual call sites into .material rendering to debug fallback behavior or ensure platform parity.
  8. Install UniversalGlass

    main

    Add UniversalGlass as a dependency in your Swift Package Manager configuration. You can target the main module for full functionality or include the optional backports module for shorthand syntax.

    Targets:

    • UniversalGlass: Main module containing glass effects, button styles, and containers.
    • UniversalGlassBackports: Optional module providing shorthand APIs like .glass and .glassEffect.
    dependencies: [
        .package(url: "https://github.com/Aeastr/UniversalGlass.git", branch: "main")
    ]
  9. Add UniversalGlassBackports to your project

    main

    To use Apple-style glass syntax (like .glassProminent) on older SDKs, you must include both the core UniversalGlass package and the UniversalGlassBackports target in your Swift Package Manager configuration. UniversalGlass provides the rendering logic, while UniversalGlassBackports exposes the symbols that mimic Apple's future API names.

    // In your Package.swift
    .package(url: "https://github.com/Aeastr/UniversalGlass", from: "1.0.0"),
    
    .target(
        name: "YourApp",
        dependencies: [
            .product(name: "UniversalGlass", package: "UniversalGlass"),
            .product(name: "UniversalGlassBackports", package: "UniversalGlass"),
        ]
    )
  10. Quick Start with UniversalGlass

    main

    To apply a glass effect to a SwiftUI view, use the .universalGlassEffect() modifier. By default, it provides a regular glass effect. You can chain configuration methods like .tint() to customize the appearance.

    import UniversalGlass
    
    Text("Hello")
        .universalGlassEffect(.regular.tint(.purple))
  11. Extend the UniversalGlass container

    main

    Developers can extend the container's behavior using the following patterns:

    • Custom Metadata: Use glassEffectParticipantContext to pass additional metadata. Modifiers should write to the environment before registering their participant.
    • Custom Grouping: To introduce new grouping semantics, extend GlassEffectGroupingKey or implement a custom overlay that consumes the participant list.
    • Performance Note: When extending, ensure heavy work is avoided in the body. The container is designed to resolve geometry in a single pass for both the mask and background layers; extensions should respect this single-pass approach.
  12. Use UniversalGlassEffectContainer for fused glass surfaces

    main

    Use UniversalGlassEffectContainer to orchestrate multiple glass effects so they render as a single fused surface.

    On OS 26+, it automatically defers to SwiftUI's native GlassEffectContainer. On earlier OS releases, it uses a fallback renderer that groups elements by shared union keys or effect IDs to create a 'melted' single slab effect. This ensures that neighboring elements appear to share a single glass plate while respecting individual shapes and hit-testing.

    @Namespace private var ns
    
    UniversalGlassEffectContainer {
        HStack {
            AvatarView()
                .universalGlassEffect()
                .universalGlassEffectUnion(id: "profile", namespace: ns)
    
            DetailsView()
                .universalGlassEffect()
                .universalGlassEffectUnion(id: "profile", namespace: ns)
        }
    }