以编程方式导航到 SwiftUI 中的新视图
Programmatically navigate to new view in SwiftUI
描述性示例:
登录屏幕,用户点击 "Login" 按钮,执行请求,UI 显示等待指示器,然后在成功响应后我想自动将用户导航到下一个屏幕。
如何在 Swift 中实现这种自动转换UI?
现在您只需简单地创建一个您要导航到的新视图的实例并将其放入 NavigationButton:
NavigationButton(destination: NextView(), isDetail: true, onTrigger: { () -> Bool in
return self.done
}) {
Text("Login")
}
如果您 return true onTrigger 表示您已成功登录用户。
您可以在登录成功后将下一个视图替换为您的登录视图。例如:
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
传递给您的视图。
Note: In Xcode Version 11.0 beta 4, to conform to protocol 'BindableObject' the willChange property has to be added
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)
// }
}
}
供将来参考,因为许多用户报告收到错误 "Function declares an opaque return type",要从 @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
根据 Swift Version 5.2
合并的变化来阐述其他人在上面阐述的内容,可以使用发布者进行简化。
- 创建一个class names
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 {
...
}
}
}
你的仪表板在认证成功后,如果认证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()
}
}
}
这是 UINavigationController
上的一个扩展,它具有简单的 push/pop 和 SwiftUI 视图,可以获得正确的动画。我在上面的大多数自定义导航中遇到的问题是 push/pop 动画已关闭。使用 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()))
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)
}
}
}
将导航 link 与 $isActive 绑定变量一起使用
我遵循了 Gene 的回答,但我在下面修复了两个问题。首先是变量 isLoggedIn 必须具有 属性 @Published 才能按预期工作。二是如何实际使用环境对象。
首先,将 UserAuth.isLoggedIn 更新为以下内容:
@Published var isLoggedin = false {
didSet {
didChange.send(self)
}
其次是如何实际使用环境对象。这在 Gene 的回答中并没有错,我只是注意到评论中有很多关于它的问题,我没有足够的业力来回应它们。将此添加到您的 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)
描述性示例:
登录屏幕,用户点击 "Login" 按钮,执行请求,UI 显示等待指示器,然后在成功响应后我想自动将用户导航到下一个屏幕。
如何在 Swift 中实现这种自动转换UI?
现在您只需简单地创建一个您要导航到的新视图的实例并将其放入 NavigationButton:
NavigationButton(destination: NextView(), isDetail: true, onTrigger: { () -> Bool in
return self.done
}) {
Text("Login")
}
如果您 return true onTrigger 表示您已成功登录用户。
您可以在登录成功后将下一个视图替换为您的登录视图。例如:
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
传递给您的视图。
Note: In Xcode Version 11.0 beta 4, to conform to protocol 'BindableObject' the willChange property has to be added
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)
// }
}
}
供将来参考,因为许多用户报告收到错误 "Function declares an opaque return type",要从 @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
根据 Swift Version 5.2
合并的变化来阐述其他人在上面阐述的内容,可以使用发布者进行简化。
- 创建一个class names
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 { ... } } }
你的仪表板在认证成功后,如果认证
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()
}
}
}
这是 UINavigationController
上的一个扩展,它具有简单的 push/pop 和 SwiftUI 视图,可以获得正确的动画。我在上面的大多数自定义导航中遇到的问题是 push/pop 动画已关闭。使用 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()))
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)
}
}
}
将导航 link 与 $isActive 绑定变量一起使用
我遵循了 Gene 的回答,但我在下面修复了两个问题。首先是变量 isLoggedIn 必须具有 属性 @Published 才能按预期工作。二是如何实际使用环境对象。
首先,将 UserAuth.isLoggedIn 更新为以下内容:
@Published var isLoggedin = false {
didSet {
didChange.send(self)
}
其次是如何实际使用环境对象。这在 Gene 的回答中并没有错,我只是注意到评论中有很多关于它的问题,我没有足够的业力来回应它们。将此添加到您的 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)