G. Marc
G. Marc

Reputation: 5997

How to show NavigationLink as a button in SwiftUI

I've read a lot here about navigation in SwiftUI and tried a couple of things, but nothing is working as desired.

Basically, I have a view with a list of workouts and you can show a single workout by clicking on a row. This works as expected using NavigationView together with NavigationLink.

Now, I want a button on the detail view to start the workout. This should open a view with a timer. The view should be presented with the same animation as the detail view did and also show the name of the workout in the navigation bar with a back button.

I could implement this with a NavigationLink view on the details page, but the link always appears as a full width row with the arrow on the right side. I'd like this to be a button instead, but the NavigationLink seems to be resistant against styling.

struct WorkoutDetail: View {
    var workout: Workout

    var body: some View {
        VStack {
            NavigationLink(destination: TimerView()) {
                Text("Starten")
            }.navigationBarTitle(Text(workout.title))
        }
    }
}

struct WorkoutList: View {
    var workoutCollection: WorkoutCollection

    var body: some View {
        NavigationView {
            List(workoutCollection.workouts) { workout in
                NavigationLink(destination: WorkoutDetail(workout: workout)) {
                    WorkoutRow(workout: workout)
                }
            }.navigationBarTitle(Text("Workouts"))
        }
    }
}

Updated: Here's a screenshot to illustrate what I mean:

Current layout: 'Starten' with default right arrow. Desired layout: Blue button with yellow background.

Upvotes: 90

Views: 122882

Answers (12)

leo
leo

Reputation: 105

This works for me:

struct ContentView: View {
    var body: some View {
        NavigationView {
            NavigationLink(
                destination: WorkoutDetail(),
                label: {
                    Text("Start Workout")
                        .foregroundColor(.black)
                        .frame(maxWidth: 500)
                        .padding()
                        .background(.green)
                        .cornerRadius(16)
                        .bold()
                }
            )
            .frame(maxWidth: .infinity)
            .padding(18)
        }
    }
}

struct WorkoutDetail: View {
    var body: some View {
        Text("WorkoutDetail")
    }
}

enter image description here

Upvotes: 0

ideastouch
ideastouch

Reputation: 1417

I think that the accurate way to do it is using buttonStyle , for example

NavigationLink(destination: WorkoutDetail(workout: workout)) {
  WorkoutRow(workout: workout)
}
.buttonStyle(ButtonStyle(background: Color.yellow))

Upvotes: 82

Katharina
Katharina

Reputation: 31

Been researching this for a while. This is what worked for me:

NavigationLink(destination: {
    TimerView() // your view that you want to navigate to
}, label: {
    Text("Starten") // your label/text on the button
})
.buttonStyle(.borderedProminent) // <- important line

So basically I have a normal NavigationLink, and I just apply my regular buttonStyle to it. :)

Upvotes: 1

byaruhaf
byaruhaf

Reputation: 4733

My preferred syntax, use closures for everything

NavigationLink {
    WorkoutDetail(workout: workout)
} label: {
    WorkoutRow(workout: workout)
}
.buttonStyle(ButtonStyle3D(background: Color.yellow))

Upvotes: 0

mohit kejriwal
mohit kejriwal

Reputation: 1835

You don't need to wrap your view inside the NavigationLink to make it trigger the navigation when pressed.

We can bind a property with our NavigationLink and whenever we change that property our navigation will trigger irrespective of what action is performed. For example:

struct SwiftUI: View {
    @State private var action: Int? = 0
    
    var body: some View {
        
        NavigationView {
            VStack {
                NavigationLink(destination: Text("Destination_1"), tag: 1, selection: $action) {
                    EmptyView()
                }
                NavigationLink(destination: Text("Destination_2"), tag: 2, selection: $action) {
                    EmptyView()
                }
                
                Text("Your Custom View 1")
                    .onTapGesture {
                        //perform some tasks if needed before opening Destination view
                        self.action = 1
                    }
                Text("Your Custom View 2")
                    .onTapGesture {
                        //perform some tasks if needed before opening Destination view
                        self.action = 2
                    }
            }
        }
    }
}

Whenever you change the value of your Bindable property (i.e. action) NavigationLink will compare the pre-defined value of its tag with the binded property action, if both are equal navigation takes place.

Hence you can create your views any way you want and can trigger navigation from any view regardless of any action, just play with the property binded with NavigationLink.

Upvotes: 108

Abdullatif AlSharhan
Abdullatif AlSharhan

Reputation: 137

I changed the List to ScrollView and it worked.

Upvotes: 0

Andrew K
Andrew K

Reputation: 1599

I don't know why all these answers are making this so complicated. In SwiftUI 2.0, you just add the button inside the navigation link!

NavigationLink(destination: TimerView()) {
    Text("Starten")
}

You can apply SwiftUI styling to the Text object just as you would style any other element.

Upvotes: 6

Goblin
Goblin

Reputation: 11

You can add the navigation link directly to the button like this:

    @State var tag:Int? = nil

    ...

    NavigationLink(destination: Text("Full List"), tag: 1, selection: $tag) {
        MyButton(buttonText: "Full list") {
            self.tag = 1
        }
    }

Upvotes: 1

Chongsheng Sun
Chongsheng Sun

Reputation: 560

Very similar with Pankaj Bhalala's solution but removed ZStack:

struct ContentView: View {
    @State var selectedTag: String?

    var body: some View {
        Button(action: {
            self.selectedTag = "xx"
        }, label: {
            Image(systemName: "plus")
        })
        .background(
            NavigationLink(
                destination: Text("XX"),
                tag: "xx",
                selection: $selectedTag,
                label: { EmptyView() }
            )
        )
    }
}

Wait for a more concise solution without state. How about you?

Upvotes: 9

Pankaj Bhalala
Pankaj Bhalala

Reputation: 105

You can use NavigationLink as Button with ZStack:

@State var tag:Int? = nil
ZStack {
  NavigationLink(destination: MyModal(), tag: 1, selection: $tag) {
    EmptyView()
  }

  Button(action: {
    self.tag = 1
  }, label: {
    Text("show view tag 1")
  })
}

Hope it will help.

Upvotes: 6

yOshi
yOshi

Reputation: 890

Use the NavigationLink inside the button's label.

Button(action: {
    print("Floating Button Click")
}, label: {
    NavigationLink(destination: AddItemView()) {
         Text("Open View")
     }
})

Upvotes: 23

ShadowDES
ShadowDES

Reputation: 873

I've been playing around with this for a few days myself. I think this is what you're looking for.

struct WorkoutDetail: View {
var workout: Workout

var body: some View {
    NavigationView {
       VStack {
           NavigationLink(destination: TimerView()) {
               ButtonView()
           }.navigationBarTitle(Text(workout.title))
        }
    }
}

And create a View you want to show in the NavigationLink

struct ButtonView: View {
var body: some View {
    Text("Starten")
        .frame(width: 200, height: 100, alignment: .center)
        .background(Color.yellow)
        .foregroundColor(Color.red)
}

Note: Anything above the NavigationView appears to show up on all pages in the Navigation, making the size of the NavigationView Smaller in the links.

Upvotes: 27

Related Questions