Lian
Lian

Reputation: 157

Double Tap Gesture with NavigationLink produce random stack of DetailView

I have a very weird issue with my double-tap gesture. I don't know this is a bug or my codes are wrong. Here, I want to use the Double Tap Gesture to open my DetailView by using NavigationLink. The problem is the page of my DetailView shows randomly which is supposed to match with the page of my SourceView. If I use a regular NavigationLink without using .onTapGesture, the seletected pages are matched. I couldn't find any solution online, but I found there are some bugs in NavigationLink. My codes examples are below. Please let me know if you have the same issue in using both onTapGesture and NavigationLink.

import SwiftUI

struct DataArray: Identifiable {
    let id = UUID()
    let number: Int
    let cities: String
    var name1: String?
    var name2: String?
    var name3: String?
    var name4: String?
}

public struct ListDataArray {
    static var dot = [
    DataArray(number: 1,
        cities: "Baltimore"
        name1: "John",
        name2: "Mike"),
        
    DataArray(number: 2,
        cities: "Frederick"),
        
    DataArray(number: 3,
        cities: "Catonsville"
        name1: "Susan",
        name2: "Oliver",
        name3: "Jude",
        name4: "Erik"),
            
    ]
}

struct Home: View {
    
    var datas: [DataArray] = ListDataArray.dot
    @State var isDoubleTab: Bool = false

    var body: some View {
        NavigationView {
            
            LazyVStack(spacing: 10) {
                ForEach (datas, id: \.id) { data in
                
                    HomeView(data: data)
                        .onTapGesture(count: 2) {
                            self.isDoubleTapped.toggle()
                        }
                    NavigationLink(
                        destination: DetailView(data: data),
                        isActivate: $isDoubleTab) {
                        EmptyView()
                    }
                    
                    Divider()
                        .padding()
                }
                
            }
            .navigationBarHidden(true)
        }
    }
}

struct DetailView: View {
    var data = DataArray
    
    var body: some View {
    
        VStact {
            Text(data.cities)
            
        }
        
    }
}

struct HomeView: View {
    var data: DataArray
    var body: some View {
    
        VStack {
            HSTack {
                Text(data.cities).padding()
                
                Text(data.name1)
                
                if let sur2 = data.name2 {
                    surName2 = sur
                    
                    Text(surName2)
                }
            }
        }
    
    }
}

The above examples are closely matched my current code. For simplicity, I just shortened some duplicate names including padding() etc...

Upvotes: 2

Views: 340

Answers (1)

aheze
aheze

Reputation: 30506

The problem is that you're using the init(_:destination:isActive:) version of ​NavigationLink inside a ForEach. When you set isDoubleTab to true, all of the visible NavigationLinks will attempt to present. This will result in the unexpected behavior that you're seeing.

Instead, you'll need to use a different version of NavigationLink: init(_:destination:tag:selection:). But first, replace @State var isDoubleTab: Bool = false with a property that stores a DataArray — something like @State var selectedData: DataArray?.

@State var selectedData: DataArray? /// here!

...

.onTapGesture(count: 2) {
    /// set `selectedData` to the current loop iteration's `data`
    /// this will trigger the `NavigationLink`.
    selectedData = data
}

Then, replace your old NavigationLink(_:destination:isActive:) with this alternate version — instead of presenting once a Bool is set to true, it will present when tag matches selectedData.

NavigationLink(
    destination: DetailView(data: data),
    tag: data, /// will be presented when `tag` == `selectedData`
    selection: $selectedData
) {
    EmptyView()
}

Note that this also requires data, a DataArray, to conform to Hashable. That's as simple as adding it inside the struct definition.

struct DataArray: Identifiable, Hashable {

Result:

Detail view's data is presented correctly

Full code:

struct Home: View {
    
    var datas: [DataArray] = ListDataArray.dot
    
//    @State var isDoubleTab: Bool = false // remove this
    @State var selectedData: DataArray? /// replace with this
    
    var body: some View {
        NavigationView {
            
            LazyVStack(spacing: 10) {
                ForEach(datas, id: \.id) { data in
                    
                    HomeView(data: data)
                        .onTapGesture(count: 2) {
                            selectedData = data
                        }
                    
                    NavigationLink(
                        destination: DetailView(data: data),
                        tag: data, /// will be presented when `data` == `selectedData`
                        selection: $selectedData
                    ) {
                        EmptyView()
                    }
                    
                    Divider()
                        .padding()
                }
                
            }
            .navigationBarHidden(true)
        }
    }
}



struct DetailView: View {
    var data: DataArray
    var body: some View {
        VStack {
            Text(data.cities)
        }
    }
}

struct HomeView: View {
    var data: DataArray
    var body: some View {
        
        VStack {
            HStack {
                Text(data.cities).padding()
                Text(data.name1 ?? "")
                
                /// not sure what this is for, doesn't seem related to your problem though so I commented it out
//                if let sur2 = data.name2 {
//                    surName2 = sur
//                    Text(surName2)
//                }
            }
        }
    }
}

public struct ListDataArray {
    static var dot = [
        DataArray(
            number: 1,
            cities: "Baltimore",
            name1: "John",
            name2: "Mike"
        ),
        DataArray(
            number: 2,
            cities: "Frederick"
        ),
        DataArray(
            number: 3,
            cities: "Catonsville",
            name1: "Susan",
            name2: "Oliver",
            name3: "Jude",
            name4: "Erik"
        )
    ]
}

struct DataArray: Identifiable, Hashable {
    let id = UUID()
    let number: Int
    let cities: String
    var name1: String?
    var name2: String?
    var name3: String?
    var name4: String?
}

Other notes:

  • Your original code has a couple syntax errors and doesn't compile.
  • You should rename DataArray to just Data, since it's a struct and not an array.

Upvotes: 1

Related Questions