在SwiftUI中,如何通过按钮点击使用NavigationView和NavigationLink?

57
我试图从登录视图推到详细视图,但无法实现。即使在登录视图中也没有显示导航栏。如何在SwiftUI中进行按钮点击推送?如何在按钮点击时使用NavigationLink?

我想在SwiftUI中通过按钮点击推送从登录视图到详细视图,但是即使在登录视图中也没有显示导航栏。请问如何实现?如何在按钮点击时使用NavigationLink?

var body: some View {
    
    NavigationView {
        VStack(alignment: .leading) {
            Text("Let's get you signed in.")
                .bold()
                .font(.system(size: 40))
                .multilineTextAlignment(.leading)
                .frame(width: 300, height: 100, alignment: .topLeading)
                .padding(Edge.Set.bottom, 50)
            
            Text("Email address:")
                .font(.headline)
            TextField("Email", text: $email)
                .frame(height:44)
                .accentColor(Color.white)
                .background(Color(UIColor.darkGray))
                .cornerRadius(4.0)
            
            Text("Password:")
                .font(.headline)
            
            SecureField("Password", text: $password)
                .frame(height:44)
                .accentColor(Color.white)
                .background(Color(UIColor.darkGray))
                .cornerRadius(4.0)
            
            Button(action: {
                print("login tapped")
            }) {
                HStack {
                    Spacer()
                    Text("Login").foregroundColor(Color.white).bold()
                    Spacer()
                }
            }
            .accentColor(Color.black)
            .padding()
            .background(Color(UIColor.darkGray))
            .cornerRadius(4.0)
            .padding(Edge.Set.vertical, 20)
        }
        .padding(.horizontal,30)
    }
    .navigationBarTitle(Text("Login"))
}
7个回答

63

iOS 16+

注意:以下是展示新视图的简化示例。有关更高级通用示例,请参见此答案

在iOS 16中,我们可以访问NavigationStackNavigationPath

用法#1

通过简单的NavigationLink激活新视图:

struct ContentView: View {
    var body: some View {
        NavigationStack {
            NavigationLink(value: "NewView") {
                Text("Show NewView")
            }
            .navigationDestination(for: String.self) { view in
                if view == "NewView" {
                    Text("This is NewView")
                }
            }
        }
    }
}

使用方法 #2

通过一个标准的 Button 激活一个新的视图:

struct ContentView: View {
    @State private var path = NavigationPath()

    var body: some View {
        NavigationStack(path: $path) {
            Button {
                path.append("NewView")
            } label: {
                Text("Show NewView")
            }
            .navigationDestination(for: String.self) { view in
                if view == "NewView" {
                    Text("This is NewView")
                }
            }
        }
    }
}

用法 #3

通过编程方式激活了一个新的视图:

struct ContentView: View {
    @State private var path = NavigationPath()

    var body: some View {
        NavigationStack(path: $path) {
            Text("Content View")
                .navigationDestination(for: String.self) { view in
                    if view == "NewView" {
                        Text("This is NewView")
                    }
                }
        }
        .onAppear {
            path.append("NewView")
        }
    }
}

iOS 13+

接受的答案使用了NavigationLink(destination:tag:selection:),这是正确的。

然而,对于只有一个NavigationLink的简单视图,您可以使用更简单的变量:NavigationLink(destination:isActive:)

用法#1

NavigationLink通过标准Button激活:

struct ContentView: View {
    @State var isLinkActive = false

    var body: some View {
        NavigationView {
            VStack(alignment: .leading) {
                ...
                NavigationLink(destination: Text("OtherView"), isActive: $isLinkActive) {
                    Button(action: {
                        self.isLinkActive = true
                    }) {
                        Text("Login")
                    }
                }
            }
            .navigationBarTitle(Text("Login"))
        }
    }
}

用法 #2

NavigationLink是通过标准的Button进行激活并隐藏

struct ContentView: View {
    @State var isLinkActive = false

    var body: some View {
        NavigationView {
            VStack(alignment: .leading) {
                ...
                Button(action: {
                    self.isLinkActive = true
                }) {
                    Text("Login")
                }
            }
            .navigationBarTitle(Text("Login"))
            .background(
                NavigationLink(destination: Text("OtherView"), isActive: $isLinkActive) {
                    EmptyView()
                }
                .hidden()
            )
        }
    }
}

使用方法 #3

NavigationLink隐藏的,且通过编程方式激活:

struct ContentView: View {
    @State var isLinkActive = false

    var body: some View {
        NavigationView {
            VStack(alignment: .leading) {
                ...
            }
            .navigationBarTitle(Text("Login"))
            .background(
                NavigationLink(destination: Text("OtherView"), isActive: $isLinkActive) {
                    EmptyView()
                }
                .hidden()
            )
        }
        .onAppear {
            self.isLinkActive = true
        }
    }
}

这里是一个GitHub存储库,其中包含不同的SwiftUI扩展,使导航更加容易。


4
控制编程式跳转的隐藏NavigationLink大开眼界,谢谢! - LordParsley
使用新的iOS菜单功能以编程方式转到NavLink时,用法#2起作用。@pawello2222-天才。 - FontFamily
4
哇,Apple 真的让 NavigationView 很令人困惑。 - ScottyBlades
等一下,NavigationLink(destination:isActive:)对于具有多个导航链接的视图不适用吗? - user482594
1
@user482594,您可以在一个视图中轻松使用多个 NavigationLink(destination:isActive:) - 由多个 @State 变量控制。如果链接彼此独立,则此方法效果最佳。但是,当您需要添加多个相关的 NavigationLinks(例如在列表中等)时,NavigationLink(destination:tag:selection:)可能更适合。 - pawello2222
显示剩余2条评论

58
为了解决您的问题,您需要使用NavigationLink绑定和管理标签。因此,在您的视图中创建一个状态,如下所示,只需添加在body之上即可。
@State var selection: Int? = nil

然后,按照下面的方式更新您的按钮代码以添加NavigationLink
NavigationLink(destination: Text("Test"), tag: 1, selection: $selection) {
    Button(action: {
        print("login tapped")
        self.selection = 1
    }) {
        HStack {
            Spacer()
            Text("Login").foregroundColor(Color.white).bold()
            Spacer()
        }
    }
    .accentColor(Color.black)
    .padding()
    .background(Color(UIColor.darkGray))
    .cornerRadius(4.0)
    .padding(Edge.Set.vertical, 20)
}

意思是,当选择和NavigationLink标签的值匹配时,导航将发生。
我希望这能帮到你。

我尝试使用它。但是如何在按钮点击时实际编写它,因为我遇到了错误。 - Rock
1
你实际上不需要 tag+selection 变体。有一个更简单的 isActive 变体 - 请参见这个答案 - pawello2222
1
@pawello2222 当你想通过按钮点击来触发导航时,isActive似乎很难工作。我发现这个答案中的方法非常适合这种用例。 - mota
只有在 NavigationView 中包含 navigationLink 时,此功能才能正常工作。 - Saurabh Prajapati

10
另一种方法:

SceneDelegate

if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = UIHostingController(rootView: BaseView().environmentObject(ViewRouter()))
            self.window = window
            window.makeKeyAndVisible()
        }

BaseView
import SwiftUI

struct BaseView : View {

    @EnvironmentObject var viewRouter: ViewRouter

    var body: some View {
        VStack {
            if viewRouter.currentPage == "view1" {
                FirstView()
            } else if viewRouter.currentPage == "view2" {
                SecondView()
                    .transition(.scale)
            }
        }
    }
}

#if DEBUG
struct MotherView_Previews : PreviewProvider {
    static var previews: some View {
        BaseView().environmentObject(ViewRouter())
    }
}
#endif

ViewRouter (视图路由器)
import Foundation
import Combine
import SwiftUI

class ViewRouter: ObservableObject {

    let objectWillChange = PassthroughSubject<ViewRouter,Never>()

    var currentPage: String = "view1" {
        didSet {
            withAnimation() {
                objectWillChange.send(self)
            }
        }
    }
}

第一视角
import SwiftUI

struct FirstView : View {

    @EnvironmentObject var viewRouter: ViewRouter

    var body: some View {
        VStack {
            Button(action: {self.viewRouter.currentPage = "view2"}) {
                NextButtonContent()
            }
        }
    }
}

#if DEBUG
struct FirstView_Previews : PreviewProvider {
    static var previews: some View {
        FirstView().environmentObject(ViewRouter())
    }
}
#endif

struct NextButtonContent : View {
    var body: some View {
        return Text("Next")
            .foregroundColor(.white)
            .frame(width: 200, height: 50)
            .background(Color.blue)
            .cornerRadius(15)
            .padding(.top, 50)
    }
}

SecondView(第二视图)
import SwiftUI

struct SecondView : View {

    @EnvironmentObject var viewRouter: ViewRouter

    var body: some View {
        VStack {
            Spacer(minLength: 50.0)
            Button(action: {self.viewRouter.currentPage = "view1"}) {
                BackButtonContent()
            }
        }
    }
}

#if DEBUG
struct SecondView_Previews : PreviewProvider {
    static var previews: some View {
        SecondView().environmentObject(ViewRouter())
    }
}
#endif

struct BackButtonContent : View {
    var body: some View {
        return Text("Back")
            .foregroundColor(.white)
            .frame(width: 200, height: 50)
            .background(Color.blue)
            .cornerRadius(15)
            .padding(.top, 50)
    }
}

希望这有所帮助!

2
在我看来,对于 iOS 16+ 来说,更加清晰的方式是使用一个状态布尔值来呈现视图。
struct ButtonNavigationView: View {
    @State private var isShowingSecondView : Bool = false
    
    var body: some View {
        NavigationStack {
            VStack{
                Button(action:{isShowingSecondView = true} ){
                    Text("Show second view")
                }
            }.navigationDestination(isPresented: $isShowingSecondView) {
                Text("SecondView")
            }
        }
    }
}

2
我们应该使用NavigationStack而不是NavigationView,这是在SwiftUI中处理导航的新方法,使用按钮进行导航。
您需要使用state属性包装器来进行导航,如下所示。
@State private var isShowingDashboardView = false

然后使用以下代码。
    NavigationStack {
      VStack{
         Button(action: {
           isShowingDashboardView = true
         }) {
           Text("Login")
         }
      }
       .navigationDestination(isPresented: $isShowingDashboardView, destination: { DashboardView()})
    }

这已经在 @pawello2222 的回答中说过并展示了。answer - soundflix
NavigationsStack 需要 iOS 16+。 - soundflix

0

我认为上面的答案很好,但更简单的方法应该是:

    NavigationLink {
        TargetView()
    } label: {
        Text("Click to go")
    }

0

最简单且最有效的解决方案是:

NavigationLink(destination:ScoresTableView()) {
                    Text("Scores")
                }.navigationBarHidden(true)
                    .frame(width: 90, height: 45, alignment: .center)
                    .foregroundColor(.white)
                    .background(LinearGradient(gradient: Gradient(colors: [Color.red, Color.blue]), startPoint: .leading, endPoint: .trailing))
                    .cornerRadius(10)
                    .contentShape(Rectangle())
                    .padding(EdgeInsets(top: 16, leading: UIScreen.main.bounds.size.width - 110 , bottom: 16, trailing: 20))

ScoresTableView 是目标视图。


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