SwiftUI: How can I get the selected row in a List before pushing another view

27,243

Solution 1

Alright, I found a not too shady solution. I used this article https://ryanashcraft.me/swiftui-programmatic-navigation shout out to him! Instead of using a NavigationLink button, I use a regular button, save the selected item when the user tap then use NavigationDestinationLink to push the new view as is self.link.presented?.value = true.

Works like a charm as of beta 3! I'll update my post if something change in the next betas.

Here's how it could look like:

struct AnotherView : View {
    private let link: NavigationDestinationLink<AnotherView2>
    @State var viewModel = AnotherViewModel()

    init() {
        self.link = NavigationDestinationLink(
            AnotherView2(),
            isDetail: true
        )
    }

    var body: some View {
        NavigationView {
            VStack {
                List(viewModel.items.identified(by: \.id)) { item in
                    Button(action: {
                        // Save the object into a global store to be used later on
                        self.viewModel.selectedItem = item
                        // Present new view
                        self.link.presented?.value = true
                    }) {
                        Text(value: item)
                    }
                }
            }
        }
    }
}

Solution 2

You can add simple TapGesture

                NavigationLink(destination: ContentView() ) {
                    Text("Row")
                        .gesture(TapGesture()
                            .onEnded({ _ in
                                //your action here
                    }))
                }
Share:
27,243
Benjamin Clanet
Author by

Benjamin Clanet

Updated on April 30, 2020

Comments

  • Benjamin Clanet
    Benjamin Clanet about 4 years

    Another SwiftUI struggle!

    I have a view that contains a list. When user taps on a row, I want to first save the selected item in my VM then push another view. The only way I can think of to solve that issue is to first save the selected row and have another button to push the next view. It seems impossible to do this with only one tap.

    Anyone have a clue?

    Here's the code by the way:

    struct AnotherView : View {
        @State var viewModel = AnotherViewModel()
    
        var body: some View {
            NavigationView {
                VStack {
                        List(viewModel.items.identified(by: \.id)) { item in
                            NavigationLink(destination: DestinationView()) {
                                Text(item)
                            }
                            // Before the new view is open, I want to save the selected item in my VM, which will write to a global store.
                            self.viewModel.selectedItem = item
                        }
                    }
            }
        }
    }
    

    Thank you!

  • P. Ent
    P. Ent over 4 years
    If you could possibly update this now that NavigationDestinationLink has been deprecated in the first SwiftUI release, that would be tremendously helpful.
  • Ben
    Ben over 4 years
    You can just use .onTapGesture { /* actions here */ } instead of .gesture(TapGesture()).onEnded({ ... })
  • malhal
    malhal over 4 years
    can onReceive be used with an @State?
  • Mike W.
    Mike W. over 4 years
    @malhal the entire view is invalidated when a @State changes, not sure what kind of publisher you would be expecting in that case. I suggest checking out: [stackoverflow.com/a/56462423/1086306]
  • malhal
    malhal over 4 years
    When an @State changes I wanted to change another @State if the new value is not nil and is used on another view. I've achieved it by using an ObservableObject implementing class and checking inside the @Published didSet to set another @Published but was wondering if there was a simpler way to hook into an @State e.g. like if we had didSetValue.
  • electromaggot
    electromaggot over 4 years
    Using this approach, isn't the "action" only executed if the Text itself is tapped and not the entire row? That is, if the tap is outside of the Text's bounds, I believe the navigation will occur but the "action" will not execute.
  • electromaggot
    electromaggot over 4 years
    Thanks. I think if you use Button(action: with your Text embedded in the button, it does take a tap from the whole row. Actually it was a lot of trouble to get both the action AND destination to work in unison, but was possible by setting a @State Boolean in the action, then testing for that Boolean in a NavigationLink(destination:isActive:) outside of the List. It was easier with what Apple deprecated!
  • Ivan Titkov
    Ivan Titkov over 4 years
    Hah agreed, after some experiment with this, I made this designer other way, I just make some action at .onAppearAction ( detailed VC). Btw you can see my implementation here github.com/titkov5/OhTronald at file TagsList -> QuotesList.onAppear()
  • Learn2Code
    Learn2Code about 4 years
    @IvanTitkov that doesnt work for a NavigationView with a List of items
  • Ivan Titkov
    Ivan Titkov about 4 years
    @Learn2Code what exactly?
  • Learn2Code
    Learn2Code about 4 years
    @IvanTitkov putting a gesture when its in a NavigationView and part of a List. It works only when its one NavigationLink only. I think Apple should fix the NavigationView when its in a List to execute the gesture as well.
  • Ribena
    Ribena about 3 years
    this doesn't appear to answer the question!