描述性示例:
登录界面,用户点击“登录”按钮,发出请求,UI显示等待指示器,然后在成功响应后,我希望自动将用户导航到下一个屏幕。
如何在SwiftUI中实现这样的自动转换?
成功登录后,您可以使用自己的登录视图替换下一个视图。例如:
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)
// }
}
}
为了以后的参考,许多用户报告出现了“函数声明不透明返回类型”的错误,要实现上述代码(@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。
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 绑定变量的导航链接
基于 Swift 5.2 的更新,结合变化说明如下:可以使用发布者(publishers)简化处理。
UserAuth
的类,如下所示,不要忘记导入 import Combine
。class UserAuth: ObservableObject {
@Published var isLoggedin:Bool = false
func login() {
self.isLoggedin = true
}
}
Update SceneDelegate.Swift
with
let contentView = ContentView().environmentObject(UserAuth())
Your authentication view
struct LoginView: View {
@EnvironmentObject var userAuth: UserAuth
var body: some View {
...
if ... {
self.userAuth.login()
} else {
...
}
}
}
Your dashboard after successful authentication, if the authentication userAuth.isLoggedin = true
then it will be loaded.
struct NextView: View {
var body: some View {
...
}
}
Lastly, the initial view to be loaded once the application is launched.
struct ContentView: View {
@EnvironmentObject var userAuth: UserAuth
var body: some View {
if !userAuth.isLoggedin {
LoginView()
} else {
NextView()
}
}
}
@Published
的目的是什么? - aldoblack@Published
旨在与 SwiftUI 一起使用。 - LetsGoBrandon这里有一个关于UINavigationController
的扩展,它具有简单的推入/弹出功能,并且能够得到正确的 SwiftUI 视图动画。我对大多数自定义导航的问题在于推入/弹出动画不对。使用带有isActive
绑定的NavigationLink
是正确的方法,但它不够灵活或可扩展。因此,下面的扩展对我很有用:
/**
* 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()))
@Published var isLoggedin = false {
didSet {
didChange.send(self)
}
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)
NavigationButton(destination: NextView(), isDetail: true, onTrigger: { () -> Bool in
return self.done
}) {
Text("Login")
}