Files
yattee/Yattee/Extensions/View+LiquidGlassSheet.swift
2026-02-08 18:33:56 +01:00

151 lines
4.5 KiB
Swift

//
// View+LiquidGlassSheet.swift
// Yattee
//
// View modifiers for iOS 26 Liquid Glass morphing sheet transitions.
//
import SwiftUI
// MARK: - Transition Source Modifier (for Views)
/// View modifier that marks a view as the source for a morphing sheet transition.
/// Apply this to a Button inside a ToolbarItem to enable the morphing effect on iOS 26+.
struct LiquidGlassTransitionSourceModifier: ViewModifier {
let id: String
let namespace: Namespace.ID
func body(content: Content) -> some View {
#if os(iOS)
if #available(iOS 26, *) {
content
.matchedTransitionSource(id: id, in: namespace)
} else {
content
}
#else
content
#endif
}
}
// MARK: - Sheet Content Modifier (for sheet content)
/// View modifier that applies the zoom navigation transition to sheet content.
/// Apply this to the content inside a .sheet() to complete the morphing effect on iOS 26+.
struct LiquidGlassSheetContentModifier: ViewModifier {
let sourceID: String
let namespace: Namespace.ID
func body(content: Content) -> some View {
#if os(iOS)
if #available(iOS 26, *) {
content
.navigationTransition(.zoom(sourceID: sourceID, in: namespace))
} else {
content
}
#else
content
#endif
}
}
// MARK: - View Extensions
extension View {
/// Marks this view as the source for a Liquid Glass morphing sheet transition.
///
/// Apply this modifier to a Button (or other view) that presents a sheet. On iOS 26+,
/// the sheet will morph from this view when presented.
///
/// - Parameters:
/// - id: Unique identifier for the transition (must match the sheet content's sourceID).
/// - namespace: A namespace for the matched geometry effect.
///
/// Example:
/// ```swift
/// @Namespace private var sheetTransition
///
/// .toolbar {
/// ToolbarItem(placement: .primaryAction) {
/// Button { showSheet = true } label: {
/// Image(systemName: "gear")
/// }
/// .liquidGlassTransitionSource(id: "settings", in: sheetTransition)
/// }
/// }
/// ```
func liquidGlassTransitionSource(
id: String,
in namespace: Namespace.ID
) -> some View {
modifier(LiquidGlassTransitionSourceModifier(id: id, namespace: namespace))
}
/// Applies the Liquid Glass morphing transition to sheet content.
///
/// Apply this modifier to the content inside a `.sheet()` modifier. On iOS 26+,
/// the sheet will morph from the matched transition source when presented.
///
/// - Parameters:
/// - sourceID: Unique identifier matching the transition source's id.
/// - namespace: A namespace for the matched geometry effect (must match the source).
///
/// Example:
/// ```swift
/// .sheet(isPresented: $showSheet) {
/// SettingsView()
/// .liquidGlassSheetContent(sourceID: "settings", in: sheetTransition)
/// }
/// ```
func liquidGlassSheetContent(
sourceID: String,
in namespace: Namespace.ID
) -> some View {
modifier(LiquidGlassSheetContentModifier(sourceID: sourceID, namespace: namespace))
}
}
// MARK: - ToolbarContent Extension
extension ToolbarContent {
/// Marks this toolbar content as the source for a Liquid Glass morphing sheet transition.
///
/// Apply this modifier to a `ToolbarItem` that presents a sheet. On iOS 26+,
/// the sheet will morph from this toolbar item when presented.
///
/// - Parameters:
/// - id: Unique identifier for the transition (must match the sheet content's sourceID).
/// - namespace: A namespace for the matched geometry effect.
///
/// Example:
/// ```swift
/// @Namespace private var sheetTransition
///
/// .toolbar {
/// ToolbarItem(placement: .primaryAction) {
/// Button { showSheet = true } label: {
/// Image(systemName: "gear")
/// }
/// }
/// .liquidGlassTransitionSource(id: "settings", in: sheetTransition)
/// }
/// ```
@ToolbarContentBuilder
func liquidGlassTransitionSource(
id: String,
in namespace: Namespace.ID
) -> some ToolbarContent {
#if os(iOS)
if #available(iOS 26, *) {
self.matchedTransitionSource(id: id, in: namespace)
} else {
self
}
#else
self
#endif
}
}