Files
yattee/Yattee/Extensions/View+ZoomTransition.swift
Arkadiusz Fal 4770bbe3b9 Remove dead code found by Periphery on all platforms
Delete declarations that Periphery reports unused on every platform that
compiles them (iOS, macOS, tvOS), including nine views used only by their
own previews. Platform-gated code still used on another platform, device-
only MPV render paths, migration models, CloudKit record names, App
Intents metadata and unused parameters are kept.
2026-10-08 23:57:56 +02:00

173 lines
6.2 KiB
Swift

//
// View+ZoomTransition.swift
// Yattee
//
// View modifiers for iOS 18 zoom navigation transitions.
// Note: Zoom transitions are only available on iOS. On other platforms,
// these modifiers have no effect but are still safe to use.
//
import SwiftUI
#if os(iOS)
/// Whether the iOS binary is running on a Mac (Catalyst or "Designed for iPad").
/// Zoom transitions trap inside UIKit's _UIZoomTransitionController when a sheet
/// is dismissed on the Mac presentation path, so they must stay off there.
private let zoomTransitionsRunningOnMac: Bool =
ProcessInfo.processInfo.isMacCatalystApp || ProcessInfo.processInfo.isiOSAppOnMac
#endif
// MARK: - Environment Keys
/// Environment key to pass the navigation transition namespace through the view hierarchy.
private struct ZoomTransitionNamespaceKey: EnvironmentKey {
static let defaultValue: Namespace.ID? = nil
}
/// Environment key to control whether zoom transitions are enabled.
private struct ZoomTransitionsEnabledKey: EnvironmentKey {
static let defaultValue: Bool = true
}
extension EnvironmentValues {
/// The namespace used for zoom navigation transitions.
var zoomTransitionNamespace: Namespace.ID? {
get { self[ZoomTransitionNamespaceKey.self] }
set { self[ZoomTransitionNamespaceKey.self] = newValue }
}
/// Whether zoom transitions are enabled. Defaults to true.
var zoomTransitionsEnabled: Bool {
get { self[ZoomTransitionsEnabledKey.self] }
set { self[ZoomTransitionsEnabledKey.self] = newValue }
}
}
// MARK: - Transition Source Modifier
/// View modifier that marks a view as the source for a zoom navigation transition.
///
/// Apply this to a NavigationLink or the view it wraps. When the user navigates
/// to the destination, the view will animate with a zoom effect from this source.
/// Note: Only has an effect on iOS. On macOS and tvOS, returns the content unchanged.
struct ZoomTransitionSourceModifier<ID: Hashable>: ViewModifier {
let id: ID
@Environment(\.zoomTransitionNamespace) private var namespace
@Environment(\.zoomTransitionsEnabled) private var zoomTransitionsEnabled
func body(content: Content) -> some View {
#if os(iOS)
if zoomTransitionsEnabled, !zoomTransitionsRunningOnMac, let namespace {
content
.matchedTransitionSource(id: id, in: namespace)
} else {
content
}
#else
content
#endif
}
}
// MARK: - Transition Destination Modifier
/// View modifier that applies the zoom navigation transition to a destination view.
///
/// Apply this to the destination view of a NavigationLink. When navigating to this view,
/// it will animate with a zoom effect from the matched source.
/// Note: Only has an effect on iOS. On macOS and tvOS, returns the content unchanged.
struct ZoomTransitionDestinationModifier<ID: Hashable>: ViewModifier {
let id: ID
@Environment(\.zoomTransitionNamespace) private var namespace
@Environment(\.zoomTransitionsEnabled) private var zoomTransitionsEnabled
func body(content: Content) -> some View {
#if os(iOS)
if zoomTransitionsEnabled, !zoomTransitionsRunningOnMac, let namespace {
content
.navigationTransition(.zoom(sourceID: id, in: namespace))
} else {
content
}
#else
content
#endif
}
}
// MARK: - View Extensions
extension View {
/// Marks this view as the source for a zoom navigation transition.
///
/// Apply this modifier to a NavigationLink or the view it wraps.
/// The id must match the destination's transition id for the zoom effect to work.
///
/// Note: Only has an effect on iOS. Safe to use on all platforms.
///
/// - Parameter id: Unique identifier for the transition (e.g., video.id, channel.id).
/// - Returns: A view that serves as the source for the zoom transition.
///
/// Example:
/// ```swift
/// NavigationLink(value: NavigationDestination.channel(channel.id, source)) {
/// ChannelRowView(channel: channel)
/// }
/// .zoomTransitionSource(id: channel.id)
/// ```
func zoomTransitionSource<ID: Hashable>(id: ID) -> some View {
modifier(ZoomTransitionSourceModifier(id: id))
}
/// Applies the zoom navigation transition to this destination view.
///
/// Apply this modifier to the destination view of a NavigationLink.
/// The id must match the source's transition id for the zoom effect to work.
///
/// Note: Only has an effect on iOS. Safe to use on all platforms.
///
/// - Parameter id: Unique identifier matching the source's id.
/// - Returns: A view with the zoom transition applied.
///
/// Example:
/// ```swift
/// ChannelView(channel: channel)
/// .zoomTransitionDestination(id: channel.id)
/// ```
func zoomTransitionDestination<ID: Hashable>(id: ID) -> some View {
modifier(ZoomTransitionDestinationModifier(id: id))
}
/// Injects the zoom transition namespace into the environment.
///
/// Apply this modifier to a NavigationStack to enable zoom transitions
/// for all NavigationLinks within that stack.
///
/// - Parameter namespace: The namespace to use for matched transitions.
/// - Returns: A view with the namespace injected into the environment.
///
/// Example:
/// ```swift
/// @Namespace private var zoomTransition
///
/// NavigationStack {
/// ContentView()
/// }
/// .zoomTransitionNamespace(zoomTransition)
/// ```
func zoomTransitionNamespace(_ namespace: Namespace.ID) -> some View {
environment(\.zoomTransitionNamespace, namespace)
}
/// Sets whether zoom transitions are enabled.
///
/// Apply this at a high level in the view hierarchy (e.g., ContentView) to control
/// whether zoom navigation transitions are applied throughout the app.
///
/// - Parameter enabled: Whether zoom transitions should be enabled.
/// - Returns: A view with the zoom transitions enabled state set.
func zoomTransitionsEnabled(_ enabled: Bool) -> some View {
environment(\.zoomTransitionsEnabled, enabled)
}
}