SwiftUI:当点击NavigationLink时执行的操作

6
我有一个在Mac环境下使用SwiftUI编写的NavigationView,其中包含许多NavigationLinks
每当我按下导航项时,右侧会显示导航详细信息。然而,我为我的活动导航项自定义了样式。当我按下一个项目时,我想调用一个操作。 我尝试将onTapGesture()函数应用于NavigationLink,但是它并没有按照预期工作/正确工作。
这是我的代码:
 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
         }

有时会输出日志"Tapped Nav"。我认为导航项内的图像和文本存在问题。是否有更好的方法,在单击该项时调用操作函数?

4个回答

7
  1. 请注意,导航链接可以在没有“轻触”、通过编程或从动作菜单等方式激活。
  2. 如果您有多个导航链接,请使用适当的初始化程序,它给您一些机会去做您想做的事情。

目前我们有三种不同的初始化程序,特别是第三种可以在您的情况下提供帮助。

/// 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
}

这个示例演示了如何将其用于“自定义样式”,但这可以扩展到任何用例(请查看调试窗口上的输出)。
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()
    }
}

下面是示例应用的演示

这里输入图片描述


6

如果您正在寻找一种更简单的解决方案,不那么好的一个选择是将 onAppear 添加到您要呈现的块代码中:

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

        }
     }

onAppear 的一个问题是当选择链接时它并不总是被调用。这取决于你是否使用 NavigationView,或者新的 NavigationStackNavigationSplitView,因为 SwiftUI 有一些后台行为。 - bio

1
我在我的应用程序中需要的东西有些不同,所以我想分享一个更通用的答案,说明如何在选择NavigationLink时调用特定的代码块(而不是使用Button:action)。您可以将下面的代码复制/粘贴到新项目中并运行。我添加了一堆打印语句来显示正在调用什么。
关于自定义绑定的更多信息可以在这里找到: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()")
            }
    }
}

顺便提一下,您可以使用“else”..print("** no value **")来检测BACK...例如,重置变量。尝试替换为:print("OR BACK")。 - ingconti

0

实现你想要的最简单的方法是在目标视图上设置 .onAppear 属性

NavigationLink(destination: SecondContentView()
            .onAppear(perform: {
                doSomeAction()
            }
        ) {
            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))
                
            }
        }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接