Multi-Destination Navigation & Modal Presentation#325
Draft
ScottPlease wants to merge 5 commits intopointfreeco:mainfrom
Draft
Multi-Destination Navigation & Modal Presentation#325ScottPlease wants to merge 5 commits intopointfreeco:mainfrom
ScottPlease wants to merge 5 commits intopointfreeco:mainfrom
Conversation
Author
ScreenRecording_01-22-2026.15-08-25_1.MP4 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Swift-Navigation Changes: Multi-Destination Navigation & Modal Presentation
Overview
We've extended swift-navigation with new APIs that coordinate multiple navigation destinations and modal presentations, solving a critical race condition problem that occurs when switching between destinations.
The Problem
When using multiple
navigationDestination(item:)orpresent(item:)modifiers with mutually exclusive state (e.g., toggling between two optional bindings), UIKit's asynchronous dismiss/present operations can conflict. A dismiss operation might complete after a new present has started, causing the newly presented view controller to be incorrectly dismissed or resulting in inconsistent navigation stack states.The Solution
We added three new APIs to
UIViewController:1.
DestinationItemstructA type-erased wrapper that encapsulates:
isPresented)makeViewController)clearBinding)2.
presents(_:)methodCoordinates modal presentations across multiple destinations by:
onDismisscallback (preventing double-dismissal), dismisses the old VC without animation, then presents the new VC3.
navigationDestinations(_:)methodCoordinates navigation pushes across multiple destinations by:
setViewControllers(_:animated:)instead of separate pop/push operations when switching destinations, which atomically updates the navigation stackpresents(_:)but for navigation controller stacksWe also added a convenience overload
navigationDestination(item1:content1:item2:content2:)for the common two-destination case.How They Accomplish the Task
The key insight is coordination through observation: instead of having independent observers react to each binding separately, a single observer watches all destinations simultaneously and makes decisions based on the complete state. This prevents the race conditions inherent in independent, asynchronous operations.
For navigation, we use
setViewControllers(_:)to perform atomic stack updates. For modals, we invalidate the old VC's dismiss callbacks before dismissing, ensuring cleanup logic doesn't fire at the wrong time.What the Tests Prove
The memory management tests (
testNavigationDestinations_ObservationDoesNotRetainModelandtestPresents_ObservationDoesNotRetainModel) verify that:These tests use the
@UIBindablemacro with@Perceptiblemodels (Swift's Observation framework) and assert thatweakModelbecomesnilafter the scope exits, confirming proper memory management even with multiple concurrent observations.Usage Examples
Navigation Destinations (TCA Integration)
Modal Presentations (TCA Integration)
Simple Observation Example
Integration with The Composable Architecture
The API seamlessly integrates with The Composable Architecture's store scoping, allowing you to coordinate multiple enum-driven destinations without race conditions when switching between them. This is particularly useful when you have destination state like:
Instead of using separate
navigationDestinationcalls for each case (which can race), usenavigationDestinations(_:)to coordinate them atomically.