Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI action when press on NavigationLink

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?

like image 231
lvollmer Avatar asked Feb 17 '20 10:02

lvollmer


3 Answers

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))

        }
     }
like image 187
Renata Faria Avatar answered Nov 18 '22 06:11

Renata Faria


  1. Take in the account, that navigation link could be activated without any "tap", programmatically, or from action menu etc.
  2. If you have more than one navigation link, use the proper initializer, which gives you some oportunity to do what you want to do

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

enter image description here

like image 6
user3441734 Avatar answered Nov 18 '22 07:11

user3441734


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()")
            }
    }
}
like image 1
xdeleon Avatar answered Nov 18 '22 07:11

xdeleon