Element transitions across navigation contexts, scroll-based flowing headers, and view mirroring for SwiftUI.
dependencies: [
.package(url: "https://github.com/Aeastr/Portal", from: "4.0.0")
]Then import the module you need:
import PortalTransitions // Element transitions (iOS 17+)
import PortalHeaders // Flowing headers (iOS 18+)
import _PortalPrivate // View mirroring with private APITargeting iOS 15/16? Pin to
v2.1.0or thelegacy/ios15branch.
Animate views between navigation contexts — sheets, navigation stacks, tabs — using a floating overlay layer.
// 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.
iOS 17+ · Uses standard SwiftUI APIs
Scroll-based header transitions that flow into the navigation bar, like Music or Photos.
NavigationStack {
ScrollView {
PortalHeaderView()
// Your content
ForEach(items) { item in
ItemRow(item: item)
}
}
.portalHeaderDestination()
}
.portalHeader(title: "Favorites", subtitle: "Your starred items")As the user scrolls, the title transitions from inline to the navigation bar with configurable snapping behavior.
iOS 18+ · Uses advanced scroll tracking APIs
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. By importing_PortalPrivate, you accept full responsibility for any consequences.
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 the wiki for details.
The Portal Wiki has full guides and API reference for each module.
The wiki is included at /wiki when you clone, so it's available offline.
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 |
Contributions welcome. See the Contributing Guide for details.
Released under the MIT License.
Built with 🍏🌀🚪 by Aether