以编程方式导航到 SwiftUI 中的新视图
Posted
技术标签:
【中文标题】以编程方式导航到 SwiftUI 中的新视图【英文标题】:Programmatically navigate to new view in SwiftUI 【发布时间】:2019-06-25 12:30:39 【问题描述】:描述性示例:
登录屏幕,用户点击“登录”按钮,请求被执行,UI 显示等待指示器,然后在成功响应后我想自动将用户导航到下一个屏幕。
如何在 SwiftUI 中实现这种自动过渡?
【问题讨论】:
【参考方案1】:您可以在成功登录后将下一个视图替换为您的登录视图。例如:
struct LoginView: View
var body: some View
...
struct NextView: View
var body: some View
...
// Your starting view
struct ContentView: View
@EnvironmentObject var userAuth: UserAuth
var body: some View
if !userAuth.isLoggedin
LoginView()
else
NextView()
您应该在数据模型中处理您的登录过程,并使用诸如@EnvironmentObject
之类的绑定将isLoggedin
传递给您的视图。
注意:在 Xcode Version 11.0 beta 4 中,为了符合协议'BindableObject',必须添加willChange 属性
import Combine
class UserAuth: ObservableObject
let didChange = PassthroughSubject<UserAuth,Never>()
// required to conform to protocol 'ObservableObject'
let willChange = PassthroughSubject<UserAuth,Never>()
func login()
// login request... on success:
self.isLoggedin = true
var isLoggedin = false
didSet
didChange.send(self)
// willSet
// willChange.send(self)
//
【讨论】:
如果我正确理解了您的概念,这就像在视图树的根目录上安装了两个单独的层次结构,一个用于经过身份验证的案例,一个用于未经身份验证的情况 @zgorawski 没错。我还没有找到使用 SwiftUI 手动推送新视图的方法。这种方式还可以确保用户无法导航回登录屏幕,除非您将 isLoggedin 设置为 false。 这也是我在 Flutter 中所做的解决方案。不幸的是,它仅在登录示例或类似的“全局状态”案例中才是优雅的。但是,如果你想例如。在详细信息视图中确认某些数据版本,显示微调器,然后显示错误或关闭详细信息视图 - 这不会那么简单。 @MoRezaFarahani 我正在使用 beta 7,而 UserAuth 现在是一个 ObservableObject。在我的 ContentView 中,我在“var body: some View”行中收到错误“函数声明了一个不透明的返回类型,但在其主体中没有返回语句来推断基础类型” 我是 SwiftUI 的新手,但我完全遵循了这个并得到了这个错误:致命错误:没有找到 UserAuth 类型的 ObservableObject。 UserAuth 的 View.environmentObject(_:) 作为此视图的祖先可能会丢失。:文件 SwiftUI,第 0 行。此错误仅在运行时显示。【参考方案2】:为了将来参考,由于许多用户报告收到错误“函数声明一个不透明的返回类型”,从@MoRezaFarahani 实现上述代码需要以下语法:
struct ContentView: View
@EnvironmentObject var userAuth: UserAuth
var body: some View
if !userAuth.isLoggedin
return AnyView(LoginView())
else
return AnyView(NextView())
这适用于 Xcode 11.4 和 Swift 5
【讨论】:
这个动画正确吗?我想要两个视图之间的推送动画。【参考方案3】:struct LoginView: View
@State var isActive = false
@State var attemptingLogin = false
var body: some View
ZStack
NavigationLink(destination: HomePage(), isActive: $isActive)
Button(action:
attlempinglogin = true
// Your login function will most likely have a closure in
// which you change the state of isActive to true in order
// to trigger a transition
loginFunction() response in
if response == .success
self.isActive = true
else
self.attemptingLogin = false
)
Text("login")
WaitingIndicator()
.opacity(attemptingLogin ? 1.0 : 0.0)
使用带有 $isActive 绑定变量的导航链接
【讨论】:
【参考方案4】:根据 Swift Version 5.2
的 combine 更改来阐述其他人在上面阐述的内容,可以使用发布者进行简化。
-
创建一个类名
UserAuth
如下图别忘了导入import Combine
。
class UserAuth: ObservableObject
@Published var isLoggedin:Bool = false
func login()
self.isLoggedin = true
用
更新SceneDelegate.Swift
let contentView = ContentView().environmentObject(UserAuth())
您的身份验证视图
struct LoginView: View
@EnvironmentObject var userAuth: UserAuth
var body: some View
...
if ...
self.userAuth.login()
else
...
认证成功后你的dashboard,如果认证userAuth.isLoggedin = true
那么就会被加载。
struct NextView: View
var body: some View
...
最后,应用程序启动后要加载的初始视图。
struct ContentView: View
@EnvironmentObject var userAuth: UserAuth
var body: some View
if !userAuth.isLoggedin
LoginView()
else
NextView()
【讨论】:
这个有效。我遵循了接受的答案,但它没有首先起作用。@Published
的目的是什么?
@aldoblack 这是正确的方法! @Published
旨在与 SwiftUI 一起使用。【参考方案5】:
这是UINavigationController
上的一个扩展,它具有简单的推送/弹出功能,带有 SwiftUI 视图,可以获得正确的动画。上面大多数自定义导航的问题是推送/弹出动画已关闭。将NavigationLink
与isActive
绑定一起使用是正确的做法,但它不灵活或不可扩展。所以下面的扩展对我有用:
/**
* Since SwiftUI doesn't have a scalable programmatic navigation, this could be used as
* replacement. It just adds push/pop methods that host SwiftUI views in UIHostingController.
*/
extension UINavigationController: UINavigationControllerDelegate
convenience init(rootView: AnyView)
let hostingView = UIHostingController(rootView: rootView)
self.init(rootViewController: hostingView)
// Doing this to hide the nav bar since I am expecting SwiftUI
// views to be wrapped in NavigationViews in case they need nav.
self.delegate = self
public func pushView(view:AnyView)
let hostingView = UIHostingController(rootView: view)
self.pushViewController(hostingView, animated: true)
public func popView()
self.popViewController(animated: true)
public func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool)
navigationController.navigationBar.isHidden = true
下面是一个使用 window.rootViewController
的简单示例。
var appNavigationController = UINavigationController.init(rootView: rootView)
window.rootViewController = appNavigationController
window.makeKeyAndVisible()
// Now you can use appNavigationController like any UINavigationController, but with SwiftUI views i.e.
appNavigationController.pushView(view: AnyView(MySwiftUILoginView()))
【讨论】:
【参考方案6】:我遵循了 Gene 的回答,但我在下面修复了两个问题。首先是变量 isLoggedIn 必须具有属性 @Published 才能按预期工作。二是如何实际使用环境对象。
首先,将 UserAuth.isLoggedIn 更新为以下内容:
@Published var isLoggedin = false
didSet
didChange.send(self)
第二个是如何实际使用环境对象。这在 Gene 的回答中并没有错,我只是在 cmets 中注意到很多关于它的问题,而我没有足够的业力来回应他们。将此添加到您的 SceneDelegate 视图中:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions)
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
var userAuth = UserAuth()
// Create the SwiftUI view that provides the window contents.
let contentView = ContentView().environmentObject(userAuth)
【讨论】:
【参考方案7】:现在您只需要简单地创建一个您想要导航到的新视图的实例并将其放入 NavigationButton:
NavigationButton(destination: NextView(), isDetail: true, onTrigger: () -> Bool in
return self.done
)
Text("Login")
如果你返回true onTrigger 表示你成功登录用户。
【讨论】:
导航按钮在 SwiftUI 2 中不存在。最好删除答案。以上是关于以编程方式导航到 SwiftUI 中的新视图的主要内容,如果未能解决你的问题,请参考以下文章
Push、Segue、Summon、导航以编程方式查看 SwiftUI