Skip to content

Help needed. Combine Portal with programmatic NavigationStack NavigationPath navigation and Zoom transition #23

@chichkanov

Description

@chichkanov

Hey! Really like your lib and want to try to do something similar to what Family wallet app does. Basically the idea is to combine Navigation Stack + Zoom transition + Portal shared transition for the image. However, I am running into a couple of issues.

  1. Since I want to use programmatic navigation with NavigationPath, it's not clear to me what's the cleanest way to trigger isActive portal transition binding?
  2. I need to define portalTransition per each item in the list. However, the transition call either accepts an ID or an Identifiable binding, both of which doesn't seem like a good fit for my needs.

I feel like I am very close on it, but would love to hear your thoughts on how this could be achieved. Attaching a hacky demo

private struct ContentView2: View {
    
    enum NavDestination: Hashable {
        case demo(Int)
    }
    
    @State private var path = NavigationPath()
    
    @State private var triggerAnimation = false
    
    @Namespace private var namespace
    
    var body: some View {
        NavigationStack(path: $path) {
            ScrollView {
                VStack {
                    ForEach(0..<10) { index in
                        HStack {
                            Color.red
                                .frame(width: 50, height: 50)
                                .clipShape(.circle)
                                .portal(id: "demo\(index)", .source)
                            
                            Text("Some text title")
                        }
                        .frame(maxWidth: .infinity, alignment: .leading)
                        .matchedTransitionSource(id: "demo\(index)", in: namespace)
                        .onTapGesture {
                            triggerAnimation.toggle()
                            path.append(NavDestination.demo(index))
                        }
                    }
                }
            }
            .navigationDestination(for: NavDestination.self) { destination in
                switch destination {
                case .demo(let index):
                    ScrollView {
                        VStack(alignment: .leading) {
                            HStack {
                                Color.red
                                    .frame(width: 50, height: 50)
                                    .clipShape(.circle)
                                    .portal(id: "demo\(index)", .destination)
                                    .onDisappear {
                                        triggerAnimation.toggle()
                                    }
                                
                                Text("Some text bla bla bla bla")
                            }
                        }
                    }
                    .navigationTransition(.zoom(sourceID: "demo\(index)", in: namespace))
                }
            }
            // How to trigger isActive binding by only relying on current navigation path?
            .portalTransition(id: "demo0", isActive: $triggerAnimation) {
                Color.red
                    .clipShape(.circle)
            }
            .portalTransition(id: "demo9", isActive: $triggerAnimation) {
                Color.red
                    .clipShape(.circle)
            }
            // How to set the portalTransition for multiple IDs but without the identifiable?
        }
        .portalContainer()
    }
}

The example of the UI I want to implement. Obviously it has much more than that, but Portal + Zoom gets pretty close

ScreenRecording_07-29-2025.23-49-24_1.MP4

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions