SwiftUI NavigationButton without the disclosure indicator?
Asked Answered
C

20

90

When making a List with a row that pushes to a new view, SwiftUI adds a disclosure indicator ">" automatically? How do I remove it if I don't want it?

    NavigationView {
        List {
            NavigationButton(destination: DetailView()) {
                ListItem()
            }
        }
        .navigationBarTitle(Text("Some title"))
    }

On a UITableViewCell you set Accessory to None but how do I do that in SwiftUI?

Caz answered 9/6, 2019 at 16:24 Comment(2)
Take it out of the list and put it in a ForEach. Then embed this in a ScrollView. If you have text, you will have to set the foreground color back to .primary or whatever color you wish.Aesthesia
A solution for iOS 16: https://mcmap.net/q/245680/-swiftui-navigationlink-hide-arrowRigorism
I
49

Setting the NavigationLink width and hiding it did the trick for me

List {
  ForEach(pages) { page in
    HStack(spacing: 0) {
      Text("Something")

      NavigationLink(destination: Text("Somewhere")) {
        EmptyView()
      }
      .frame(width: 0)
      .opacity(0)
    }
  }
}
Ivie answered 1/11, 2019 at 21:17 Comment(3)
It Is working, but is showing a small white screen before transitioning to the other view.Visional
this adds extra spacing unless put into ZStackSediment
@IvanKvyatkovskiy The small space is because HStack has non-0 spacing by default. If you change the top HStack to HStack(spacing: 0) then it will work.Mckinnie
T
29

Swift 5, Xcode 11. ZStack works perfect.

var body: some View {
    NavigationView {
        List {
            ForEach(viewModel.currenciesViewModel) { cellViewModel in
                ZStack {
                    cellViewModel.makeView()
                    NavigationLink(destination: ChooseCurrencyListView()) {
                        EmptyView()
                    }
                    .buttonStyle(PlainButtonStyle())
                }
            }
        }
        .navigationBarHidden(true)
        .navigationBarTitle("", displayMode: .inline)
    }
}
Teresaterese answered 17/10, 2019 at 6:36 Comment(2)
Unfortunately doesn't work with .listRowInsets(EdgeInsets()).Scarletscarlett
This works perfectly in Xcode Version 12.0.1 (12A7300) Preview, but doesn't work at all in Version 12.0.1 (940.16) Simulator.Expostulatory
U
19

The easiest one. The content for each item in the list.

ZStack {
   NavigationLink(destination: DetailView()) {
       EmptyView()
   }.hidden()
   RowView()
}
Unicef answered 22/2, 2020 at 8:16 Comment(2)
For some reason, this only works on visible views/cells in a list. As soon as I scroll to a view/cell that is out of the screen, the navigationLink stops working, but will work on the views/cells that were initially visible.Formulism
Worked great for me but I had to remove the .hidden(), which should not be a problem since the navigation view is an EmptyView.Mcmillian
S
18

You can also put it in the .background modifier:

List {
    Text("Go to...")
        .background(NavigationLink("", destination: Text("Detail View")))
}

If you already have the background modifier on the Text, you can wrap the Text in a HStack and apply background to the HStack.

Scrap answered 14/10, 2020 at 18:1 Comment(1)
I found the indicator is still shown, .opacity(0.0) is need to hide the indicatorSlackjawed
G
12

As workaround I can suggest to add .padding modifier like this:

NavigationView {
        List {
            NavigationButton(destination: DetailView()) {
                ListItem()
            }
        }
        .navigationBarTitle(Text("Some title"))
    }
    .padding(.trailing, -32.0)

So you will get rows without visible disclosure:

Result

Gunflint answered 13/6, 2019 at 18:27 Comment(1)
This doesn't respect accessibility and automation, it is still there just "some" people can't see it.Roccoroch
M
8

What you can do, if you are using list, is setting the navigationlink to hidden and its frame width to zero.

    HStack{
            Button(action: {self.statusShow = 1}, label: {
                Image(systemName: "info.circle")
            })
            NavigationLink(destination: StimulatorSettingView(),
                           tag: 1,
                           selection: self.$statusShow){
                            EmptyView()

            }.hidden().frame(width: 0)
        }

This worked for me.

Maryland answered 7/5, 2020 at 11:11 Comment(0)
P
7

As of beta 6, this works well:

struct SwiftUIView: View {   
    var body: some View {

        NavigationView {
            List {
                HStack {
                    Text("My Cell Content")
                    NavigationLink(destination: Text("destination"), label: {
                        EmptyView()
                    })
                }
            }
        }
    }
}
Pinta answered 24/8, 2019 at 10:0 Comment(1)
You also use a VStack so the Text (or any other view) will take up the entire width of the List.Together
N
7

You don't have to use NavigationLink to wrap your Label directly. It will work as long as the link is anywhere in your view hierarchy.

Here I've wrapped it in a button, which allows you to trigger an action prior to pushing the view. Since the NavigationLink has an EmptyView for the label the disclosure indicator is not visible. You can also style this with ButtonStyle.

struct NavigationButton<Destination: View, Label: View>: View {
    var action: () -> Void = { }
    var destination: () -> Destination
    var label: () -> Label

    @State private var isActive: Bool = false

    var body: some View {
        Button(action: {
            self.action()
            self.isActive.toggle()
        }) {
            self.label()
              .background(NavigationLink(destination: self.destination(), isActive: self.$isActive) {
                  EmptyView() 
               })
        }
    }
}

And to use it:

NavigationButton(
    action: { print("tapped!") },
    destination: { Text("Pushed View") },
    label: { Text("Tap me") }
  )
Nimbus answered 12/2, 2020 at 8:2 Comment(1)
Tried this on ios 14.5 but unfortunately the disclosure indicator still shows upBrisco
D
4

NavigationLink is what we should define in a scope enclosed inside a NavigationView. But when we use NavigationLink it is attached to the enclosing view, so to reuse the same NavigationLink with other views, we use tag which differentiates between different Destinations.

struct SwiftUIView: View {
@State private var viewState: Int? = 0

var body: some View {

    NavigationView {
                VStack {
                    NavigationLink(destination: Text("View 1"), tag: 1, selection: $viewState) {
                        EmptyView()
                    }

                    NavigationLink(destination: Text("View 2"), tag: 2, selection: $viewState) {
                        EmptyView()
                    }

                    Text("First View")
                        .onTapGesture {
                            self.viewState = 1
                        }

                    Text("Second View")
                        .onTapGesture {
                            self.viewState = 2
                        }
                    }
            }
    }
} 

Here we bind a Hashable property with all the NavigationLinks present in our VStack so that when a particular View is tapped we can notify which Destination should be opened by setting the value of Bindable property. If we don't notify the correct Destination by setting the value of tag, always the View defined inside the Closure of NavigationLink will be clickable and nothing else.

Using this approach you don't need to wrap all your clickable views inside NavigationView, any action on any view can use any NavigationLink just by setting the tag.

Thanks, hope this helps.

Discontinuation answered 4/8, 2019 at 12:46 Comment(0)
B
4

Works well for me!

import SwiftUI

struct LandmarkList: View {
    var body: some View {
        NavigationView {
            List(landmarkData) { landmark in
                LandmarkRow(landmark: landmark)
                NavigationLink(destination: LandmarkDetail(landmark: landmark)) {
                    EmptyView()
                }
            }
            .navigationBarTitle(Text("Landmarks"))
        }
    }
}

struct LandmarkList_Previews: PreviewProvider {
    static var previews: some View {
        ForEach(["iPhone SE", "iPhone 11 Pro Max"], id: \.self) { deviceName in
            LandmarkList()
                .previewDevice(PreviewDevice(rawValue: deviceName))
                .previewDisplayName(deviceName)
        }
    }
}

enter image description here

Brucite answered 16/3, 2020 at 19:28 Comment(0)
F
3

Use .frame(width: 0).opacity(0.0):

NavigationView {
  List {
    ForEach(options) {
      option in
        ZStack {
          YourView(option: option)
          NavigationLink(destination: ProductListView(),
            label: {
              EmptyView()
            }).frame(width: 0).opacity(0.0)
        }.listRowInsets(EdgeInsets())
    }
  }.navigationBarHidden(true)
}
Fanfani answered 18/12, 2020 at 20:34 Comment(0)
R
2

My version of this solution is to make a view modifier. I think it's the cleanest way, as it doesn't use AnyView.

Note that this solution runs the init() for the destination when it draws the element the .navigationLink() is attached to.

Usage

Text("Link")
  .navigationLink({ 
    // put your destination here
  })

How To

import SwiftUI

extension View {
  func navigationLink<Destination: View>(_ destination: @escaping () -> Destination) -> some View {
    modifier(NavigationLinkModifier(destination: destination))
  }
}

fileprivate struct NavigationLinkModifier<Destination: View>: ViewModifier {

  @ViewBuilder var destination: () -> Destination

  func body(content: Content) -> some View {
    content
      .background(
        NavigationLink(destination: self.destination) { EmptyView() }.opacity(0)
      )
  }
}
Roughage answered 17/4, 2022 at 21:4 Comment(0)
M
2

Here's a reusable "plain" navigation link view (i.e. without the chevron disclosure indicator) that can be a drop-in replacement for NavigationLink:

struct PlainNavigationLink<Label, Destination>: View where Label: View, Destination: View {
    @ViewBuilder var destination: () -> Destination
    @ViewBuilder var label: () -> Label

    var body: some View {
        label()
            .background(
                NavigationLink(destination: destination, label: {})
                    .opacity(0)
            )
    }
}

To use it, simply replace NavigationLink with PlainNavigationLink:

NavigationView { // or NavigationStack in iOS 16
    List {
        ForEach(1...30, id: \.self) { _ in
            PlainNavigationLink {
                Text("Hello, world!")
            } label: {
                Text("Hello, world!")
            }
        }
    }
}

We can also extend it with convenience initializers for LocalizedStringKey and String, just like NavigationLink does.

Machellemachete answered 28/6, 2022 at 12:17 Comment(0)
W
1

This helps to push and pass the model to the next navigation view controller.

struct ContentView : View {
    @State var model = PostListViewModel()

    var body: some View {
        NavigationView {
            List(model.post) { post in
                ListCell(listData: post)
                }.navigationBarTitle(Text("My Post"))
        }
    }

}

struct ListCell: View {
    var listData: Post
    var body: some View {
        return NavigationButton(destination: DetailContentView(post: listData)) {
            HStack {
                ImageRow(model: listData) // Get image
                VStack(alignment: .leading) {
                    Text(listData.login).font(.headline).lineLimit(nil)
                    Text(listData.url).font(.subheadline).lineLimit(nil)
                    }.padding(.leading, 10)
                }.padding(.init(top: 5, leading: 0, bottom: 5, trailing: 0))
        }
    }
}
Wishful answered 1/7, 2019 at 7:35 Comment(0)
O
0

just came here looking for the answer to this question, but none of the proposed solutions worked for me (can't have an empty view, because i want to put something in the list row; i'm already messing with the padding (and increasing trailing padding didn't seem to work) ... i was about to give up, and then something occurred to me: what if you crank up the z-index of the list row itself? seemed somewhat unlikely, but i gave it a try and, i'll be damned, it worked! i was so pleasantly surprised, i felt like sharing ...

e.g.:

// in body of your list row view
HStack(alignment: .top, spacing: 0.0) {
    // stuff ...
}
.zIndex(9999999999)
Overtop answered 13/10, 2019 at 13:41 Comment(1)
This does work, although zIndex(.infinity) is a bit more elegant than 999999Wiebmer
B
0

If you need children behaviour for List and NavigationLink, without additional discloser in the same time, I want to promote this tricky solution, main point at HStack

var body: some View {
    NavigationView {
        List(items, children: \.items) { item in
            ZStack {
                NavigationLink(destination: DetailsView()) {
                    EmptyView()
                }.hidden()
                HStack {
                    RowView(item: item)
                    Spacer()
                }
            }
        }
    }
}
Beaird answered 8/4, 2021 at 19:4 Comment(1)
But doesn't the .hidden() disable the ability to tap to navigate to Detailsview()?Neolatin
A
0
  1. Use ZStack
  2. add an Empty view in the Navigation Link
  3. Set the opacity to zero

Code Sample:

ZStack (alignment: .leading) {
       NavigationLink(destination: DetailsView()) {
           EmptyView()
        }
        .opacity(0.0)
        RowCell()
    }
Autophyte answered 4/10, 2023 at 20:33 Comment(0)
O
-1

Once you put your button in a scrollview, the disclosure button will be hidden. Just make sure to disable your scroll indicator.

Oleoresin answered 27/5, 2020 at 23:13 Comment(0)
O
-3

there is no documentation yet, so you can use ScrollView for now

  NavigationView {
        ScrollView {
            ForEach(0...100){ x in
                NavigationButton(destination: Text("ss")) {
                    HStack {
                          Text(String(x))
                          Spacer()
                        }
                        .padding()
                        .background(Color.white)
                        .shadow(radius:1,y:1)
                }
             }
             .frame(width: UIScreen.main.bounds.width - 32)
             .padding()
        }
    }

enter image description here

Oliva answered 28/6, 2019 at 20:41 Comment(0)
D
-4

Removing List and just using ForEach works fine with navigation link. You just have to create your own list row. This works for me

NavigationView {
   ForEach(pages) {
    page in
      NavigationLink(destination: DetailView()) {
         ListItem()
     }
   }
}
Diarmuid answered 14/1, 2020 at 0:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.