I have a NavigationView
with many NavigationLinks
in SwiftUI for Mac.
Whenever I press a Navigation Item, the Navigation Detail is being displayed on the right. However, I have a custom styling for my active Navigation Items. When I press a Item, I want to call an action. I have tried onTapGesture()
function on the NavigationLink
, however it is not working correctly/ as expected.
Here is my code:
NavigationView {
VStack{
NavigationLink(destination: SecondContentView()) {
VStack
{
Image("Calendar")
.resizable().frame(width:40, height: 40)
.colorMultiply(currentSelected == 0 ? Color(red: 57 / 255, green: 214 / 255, blue: 155 / 255) : Color(red: 255 / 255, green: 255 / 255, blue: 255 / 255))
Text("Dates")
.foregroundColor(currentSelected == 0 ? Color(red: 57 / 255, green: 214 / 255, blue: 155 / 255) : Color(red: 255 / 255, green: 255 / 255, blue: 255 / 255))
}
}
.buttonStyle(PlainButtonStyle())
.padding(18)
.background(currentSelected == 0 ? Color(.controlBackgroundColor) : Color(.clear))
.zIndex(50)
.onTapGesture {
NSLog("Tapped Nav")
self.currentSelected = 0
}
The log "Tapped Nav"
is only output sometimes.. I think there is a problem with the image and text which is inside a navigation item. Is there a better way to call an action function, when I click on the item?
One 'not that good' option if you are looking for a simpler workaround is to add onAppear
in block code you'll gonna to present:
NavigationView {
VStack{
NavigationLink(destination: SecondContentView().onAppear() { // <<-- here
NSLog("Tapped Nav")
self.currentSelected = 0
}) {
VStack
{
Image("Calendar")
.resizable().frame(width:40, height: 40)
.colorMultiply(currentSelected == 0 ? Color(red: 57 / 255, green: 214 / 255, blue: 155 / 255) : Color(red: 255 / 255, green: 255 / 255, blue: 255 / 255))
Text("Dates")
.foregroundColor(currentSelected == 0 ? Color(red: 57 / 255, green: 214 / 255, blue: 155 / 255) : Color(red: 255 / 255, green: 255 / 255, blue: 255 / 255))
}
}
Currently we have three different initializers, especially the third one could help in your case.
/// A view that controls a navigation presentation.
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public struct NavigationLink<Label, Destination> : View where Label : View, Destination : View {
/// Creates an instance that presents `destination`.
public init(destination: Destination, @ViewBuilder label: () -> Label)
/// Creates an instance that presents `destination` when active.
public init(destination: Destination, isActive: Binding<Bool>, @ViewBuilder label: () -> Label)
/// Creates an instance that presents `destination` when `selection` is set
/// to `tag`.
public init<V>(destination: Destination, tag: V, selection: Binding<V?>, @ViewBuilder label: () -> Label) where V : Hashable
/// Declares the content and behavior of this view.
public var body: some View { get }
/// The type of view representing the body of this view.
///
/// When you create a custom view, Swift infers this type from your
/// implementation of the required `body` property.
public typealias Body = some View
}
The working example shows you, how to use it for "custom styling", but this could be extended for any use case (see the print out on debug window)
import SwiftUI
class Model: ObservableObject {
@Published var selection: Int? {
willSet {
if let nv = newValue {
selected = nv
willChangeSelection?(selected)
}
}
}
var selected: Int = 0
let willChangeSelection: ((Int) -> Void)?
init( onSelection: ((Int)->Void)? ) {
willChangeSelection = onSelection
selection = 1
}
}
struct ContentView: View {
@ObservedObject var model = Model { i in
print("selected:", i)
}
var body: some View {
NavigationView {
List {
NavigationLink(destination: Detail(txt: "First"), tag: 1, selection: $model.selection) {
RowLabel(txt: "First", tag: 1, selected: model.selected)
}
NavigationLink(destination: Detail(txt: "Second"), tag: 2, selection: $model.selection) {
RowLabel(txt: "Second", tag: 2, selected: model.selected)
}
NavigationLink(destination: Detail(txt: "Third"), tag: 3, selection: $model.selection) {
RowLabel(txt: "Third", tag: 3, selected: model.selected)
}
}
.frame(width: 200, height: 300)
Detail(txt: "First")
}.frame(width: 500)
}
}
struct Detail: View {
let txt: String
var body: some View {
VStack {
Text(self.txt).font(.largeTitle)
}.frame(width: 300)
}
}
struct RowLabel: View {
let txt: String
let tag: Int
let selected: Int
var body: some View {
Text(txt)
.font(selected == tag ? .largeTitle: .footnote).padding(.leading, 10)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
The resulting sample application in action
What I needed in my app was a little different so I thought I’d share a more generic answer on how to call a specific piece of code when NavigationLink is selected (which is not using Button: action). You can copy/paste code below to a new project and run. I’ve added a bunch of print statements to show what is getting called when.
More information on custom bindings can be found here: https://www.hackingwithswift.com/quick-start/swiftui/how-to-create-custom-bindings
import SwiftUI
struct ContentView: View {
@State private var selection: Int?
func selectionBinding() -> Binding<Int?> {
let binding = Binding<Int?>(get: {
self.selection
}, set: {
self.selection = $0
// selection is optional ? so let's check for value first
if let newSelection = selection {
print("selection = \(newSelection)")
if newSelection == 1 {
doThis()
} else if newSelection == 2 {
doThat()
}
} else {
print("** no value **")
}
})
return binding
}
func doThis() {
print("doThis called...")
}
func doThat() {
print("doThat called...")
}
var body: some View {
NavigationView {
List {
NavigationLink(destination: AnotherView(selectionString: "First Selected"), tag: 1, selection: selectionBinding()) {
Text("First Link")
}
NavigationLink(destination: AnotherView(selectionString: "Second Selected"), tag: 2, selection: selectionBinding()) {
Text("Second Link")
}
}
}.onAppear() {
print("ContentView.onAppear()")
}
}
}
struct AnotherView: View {
let selectionString: String
var body: some View {
Text(selectionString)
.onAppear() {
print("AnotherView.onAppear()")
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With