简体   繁体   中英

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?

  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

在此处输入图片说明

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

        }
     }

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()")
            }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM