Portal is a SwiftUI package for element transitions across navigation contexts, scroll-based flowing headers, and advanced view mirroring capabilities.
Swift
1,105
523 commits
updated Sep 26, 2026
Animate elements between screens, flow scrolling headers into navigation bars, and mirror views in SwiftUI.
Portal provides three modules for different use cases:
_UIPortalView API. The view instance is shared rather than recreated.dependencies: [
.package(url: "https://github.com/Aeastr/Portal.git", from: "4.0.0")
]
| Target | Description |
|---|---|
PortalTransitions | Element transitions (iOS 17+) |
PortalHeaders | Flowing headers (iOS 18+) |
_PortalPrivate | View mirroring with private API |
Targeting iOS 15/16? Pin to
v2.1.0or thelegacy/ios15branch.
// 1. Wrap your app in PortalContainer
PortalContainer {
ContentView()
}
// 2. Mark the source view
Image("cover")
.portal(id: "book", .source)
// 3. Mark the destination view
Image("cover")
.portal(id: "book", .destination)
// 4. Apply the transition
.fullScreenCover(item: $selectedBook) { book in
BookDetail(book: book)
}
.portalTransition(item: $selectedBook)
The view animates smoothly from source to destination when the cover presents, and back when it dismisses.
Scroll-based header transitions that flow into the navigation bar, like Music or Photos.
NavigationStack {
ScrollView {
PortalHeaderView()
ForEach(items) { item in
ItemRow(item: item)
}
}
.portalHeaderDestination()
}
.portalHeader(title: "Favorites", subtitle: "Your starred items")
WARNING: Private API Usage
This module uses Apple's private
_UIPortalViewAPI. Apps using private APIs may be rejected by App Store Review. Use at your own discretion. Portal, Aether, and any maintainers assume no responsibility for App Store rejections, app crashes, or any other issues arising from the use of this module.
Same API as PortalTransitions, but uses Apple's private _UIPortalView for true view mirroring instead of layer snapshots. The view instance is shared rather than recreated.
Class names are obfuscated at compile-time. See Using _PortalPrivate for details.
Customize the animating layer with optional configuration closures:
// No config — frame/offset handled automatically
.portalTransition(item: $selectedBook) { book in
Image("cover")
}
// Styling only — add clips, shadows, etc. (frame/offset still automatic)
.portalTransition(item: $selectedBook) { book in
Image("cover")
} configuration: { content, isActive in
content.clipShape(.rect(cornerRadius: isActive ? 0 : 12))
}
// Full control — you handle frame/offset (for custom modifier ordering)
.portalTransition(item: $selectedBook) { book in
Image("cover")
} configuration: { content, isActive, size, position in
content
.frame(width: size.width, height: size.height)
.clipShape(.rect(cornerRadius: isActive ? 0 : 12))
.offset(x: position.x, y: position.y)
}
PortalTransitions creates a transparent PassThroughWindow that sits above your entire app UI. Source and destination views register their bounds via PreferenceKey. When a transition triggers, the view is rendered in this overlay window and animated between the two positions. The window uses a custom hitTest implementation that only captures touches on the animated layer itself—all other touches pass through to your app below, so interaction remains seamless during animations.
PortalHeaders tracks scroll position using iOS 18's ScrollGeometry and interpolates between inline and navigation bar states based on content offset thresholds.
_PortalPrivate wraps Apple's private _UIPortalView class, which creates a portal to another view's layer. Class names are obfuscated at compile-time to avoid detection. See UIPortalBridge for a standalone wrapper.
Each module includes working examples in Sources/*/Examples/:
| PortalTransitions | PortalHeaders | _PortalPrivate |
|---|---|---|
| Card Grid | With Accessory | Card Grid |
| List | Title Only | List |
| Grid Carousel | No Accessory | Comparison |
Guides and generated API documentation live in each module's DocC catalog:
Contributions welcome. See the Contributing Guide for details.
MIT. See LICENSE for details.
_UIPortalView1,607 followers · starred Jul 2025
3,240 followers · starred Apr 2025
3,489 followers · starred Apr 2025
5,285 followers · starred Apr 2025
Portal is a SwiftUI package for element transitions across navigation contexts, scroll-based flowing headers, and advanced view mirroring capabilities.
Swift
1,105
523 commits
updated Sep 26, 2026
Animate elements between screens, flow scrolling headers into navigation bars, and mirror views in SwiftUI.
Portal provides three modules for different use cases:
_UIPortalView API. The view instance is shared rather than recreated.dependencies: [
.package(url: "https://github.com/Aeastr/Portal.git", from: "4.0.0")
]
| Target | Description |
|---|---|
PortalTransitions | Element transitions (iOS 17+) |
PortalHeaders | Flowing headers (iOS 18+) |
_PortalPrivate | View mirroring with private API |
Targeting iOS 15/16? Pin to
v2.1.0or thelegacy/ios15branch.
// 1. Wrap your app in PortalContainer
PortalContainer {
ContentView()
}
// 2. Mark the source view
Image("cover")
.portal(id: "book", .source)
// 3. Mark the destination view
Image("cover")
.portal(id: "book", .destination)
// 4. Apply the transition
.fullScreenCover(item: $selectedBook) { book in
BookDetail(book: book)
}
.portalTransition(item: $selectedBook)
The view animates smoothly from source to destination when the cover presents, and back when it dismisses.
Scroll-based header transitions that flow into the navigation bar, like Music or Photos.
NavigationStack {
ScrollView {
PortalHeaderView()
ForEach(items) { item in
ItemRow(item: item)
}
}
.portalHeaderDestination()
}
.portalHeader(title: "Favorites", subtitle: "Your starred items")
WARNING: Private API Usage
This module uses Apple's private
_UIPortalViewAPI. Apps using private APIs may be rejected by App Store Review. Use at your own discretion. Portal, Aether, and any maintainers assume no responsibility for App Store rejections, app crashes, or any other issues arising from the use of this module.
Same API as PortalTransitions, but uses Apple's private _UIPortalView for true view mirroring instead of layer snapshots. The view instance is shared rather than recreated.
Class names are obfuscated at compile-time. See Using _PortalPrivate for details.
Customize the animating layer with optional configuration closures:
// No config — frame/offset handled automatically
.portalTransition(item: $selectedBook) { book in
Image("cover")
}
// Styling only — add clips, shadows, etc. (frame/offset still automatic)
.portalTransition(item: $selectedBook) { book in
Image("cover")
} configuration: { content, isActive in
content.clipShape(.rect(cornerRadius: isActive ? 0 : 12))
}
// Full control — you handle frame/offset (for custom modifier ordering)
.portalTransition(item: $selectedBook) { book in
Image("cover")
} configuration: { content, isActive, size, position in
content
.frame(width: size.width, height: size.height)
.clipShape(.rect(cornerRadius: isActive ? 0 : 12))
.offset(x: position.x, y: position.y)
}
PortalTransitions creates a transparent PassThroughWindow that sits above your entire app UI. Source and destination views register their bounds via PreferenceKey. When a transition triggers, the view is rendered in this overlay window and animated between the two positions. The window uses a custom hitTest implementation that only captures touches on the animated layer itself—all other touches pass through to your app below, so interaction remains seamless during animations.
PortalHeaders tracks scroll position using iOS 18's ScrollGeometry and interpolates between inline and navigation bar states based on content offset thresholds.
_PortalPrivate wraps Apple's private _UIPortalView class, which creates a portal to another view's layer. Class names are obfuscated at compile-time to avoid detection. See UIPortalBridge for a standalone wrapper.
Each module includes working examples in Sources/*/Examples/:
| PortalTransitions | PortalHeaders | _PortalPrivate |
|---|---|---|
| Card Grid | With Accessory | Card Grid |
| List | Title Only | List |
| Grid Carousel | No Accessory | Comparison |
Guides and generated API documentation live in each module's DocC catalog:
Contributions welcome. See the Contributing Guide for details.
MIT. See LICENSE for details.
_UIPortalView1,607 followers · starred Jul 2025
3,240 followers · starred Apr 2025
3,489 followers · starred Apr 2025
5,285 followers · starred Apr 2025