完全移动到其他视图并且不允许在 SwiftUI 中返回

Completely move to other view and don't allow to go back in SwiftUI

我正在开发一个带有 SwiftUI 的简单 iOS 应用,有两个视图:LogInView()HomeView()

我想要的非常简单:当用户点击 LogInView() 上的 Login 按钮时,我希望应用程序隐藏 LogInView() 并显示HomeView(),全屏,不像模式一样并且不允许用户返回。

这可以通过 Swift 和 UIKit 中的 Storyboards 轻松完成,有什么方法可以使用 SwiftUI 实现吗?

感谢任何帮助。 提前致谢。

我的代码:

LogInView:

struct LogInView: View {
    var body: some View {
        VStack {
            Text("Welcome to Mamoot!")
                .font(.largeTitle)
                .fontWeight(.heavy)
            Text("We are glad to have you here.")
            Text("Please log in with your Mastodon or Twitter account to continue.")
                .multilineTextAlignment(.center)
                .lineLimit(4)
                .padding()
            Spacer()
            FloatingTextField(title: "Username", placeholder: "Username", width: 300, type: "Username")
            FloatingTextField(title: "Password", placeholder: "Password", width: 300, type: "password")
                .padding(.top, -50)
            Spacer()
            ZStack {
                Button(action: { /* go to HomeView() */ }) {
                    Text("Log in")
                        .foregroundColor(Color.white)
                        .bold()
                        .shadow(color: .red, radius: 10)
                }
                .padding(.leading, 140)
                    .padding(.trailing, 140)
                    .padding(.top, 15)
                    .padding(.bottom, 15)
                    .background(Color.red)
                    .cornerRadius(10)
            }
            .padding(.bottom)
        }
    }
}

主页视图:

struct HomeView: View {
    var body: some View {
        Text("Home Page")
    }
}

更新: 我有时间更新答案,并添加一个过渡。注意我用VStack改了Group,不然transition不行

您可以在 withAnimation 调用(按钮关闭)中更改持续时间。

我还在你的按钮中移动了一些修饰符,所以整个事情是 "tappable"。否则,只有点击按钮的文本才会触发操作。


您可以使用 ObservedObjectEnvironmentObjectBinding。这是 ObservedObject:

的示例
import SwiftUI

class Model: ObservableObject {
    @Published var loggedIn = false
}

struct ContentView: View {
    @ObservedObject var model = Model()

    var body: some View {
        VStack {
            if model.loggedIn {
                HomeView().transition(.opacity)
            } else {
                LogInView(model: model).transition(.opacity)
            }
        }
    }
}

struct HomeView: View {
    var body: some View {
        Text("Home Page")
    }
}

struct LogInView: View {
    @ObservedObject var model: Model

    var body: some View {
        VStack {
            Text("Welcome to Mamoot!")
                .font(.largeTitle)
                .fontWeight(.heavy)
            Text("We are glad to have you here.")
            Text("Please log in with your Mastodon or Twitter account to continue.")
                .multilineTextAlignment(.center)
                .lineLimit(4)
                .padding()
            Spacer()
            //            FloatingTextField(title: "Username", placeholder: "Username", width: 300, type: "Username")
            //            FloatingTextField(title: "Password", placeholder: "Password", width: 300, type: "password")
            //                .padding(.top, -50)
            Spacer()
            ZStack {
                Button(action: {
                    withAnimation(.easeInOut(duration: 1.0)) {
                        self.model.loggedIn = true
                    }
                }) {
                    Text("Log in")
                        .foregroundColor(Color.white)
                        .bold()
                        .shadow(color: .red, radius: 10)
                        // moved modifiers here, so the whole button is tappable
                        .padding(.leading, 140)
                        .padding(.trailing, 140)
                        .padding(.top, 15)
                        .padding(.bottom, 15)
                        .background(Color.red)
                        .cornerRadius(10)
                }
            }
            .padding(.bottom)
        }
    }
}

@kontiki 的回答可能是最 SwiftUI-y,但我会提出一个不同的解决方案,可能不太好!但也许更多 flexible/scalable.

你可以交换 rootView of UIHostingController:

场景代理

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?
    fileprivate lazy var appCoordinator: AppCoordinator = {

        let rootViewController: UIHostingController<AnyView> = .init(rootView: EmptyView().eraseToAny())

        window?.rootViewController = rootViewController

        let navigationHandler: (AnyScreen, TransitionAnimation) -> Void = { [unowned rootViewController, window] (newRootScreen: AnyScreen, transitionAnimation: TransitionAnimation) in

            UIView.transition(
                with: window!,
                duration: 0.5,
                options: transitionAnimation.asUIKitTransitionAnimation,
                animations: { rootViewController.rootView = newRootScreen },
                completion: nil
            )
        }


        return AppCoordinator(
            dependencies: (
                securePersistence: KeyValueStore(KeychainSwift()),
                preferences: .default
            ),
            navigator: navigationHandler
        )
    }()

    func scene(
        _ scene: UIScene,
        willConnectTo session: UISceneSession,
        options connectionOptions: UIScene.ConnectionOptions
    ) {
        self.window = .fromScene(scene)
        appCoordinator.start()
    }
}

enum TransitionAnimation {
    case flipFromLeft
    case flipFromRight
}

private extension TransitionAnimation {
    var asUIKitTransitionAnimation: UIView.AnimationOptions {
        switch self {
        case .flipFromLeft: return UIView.AnimationOptions.transitionFlipFromLeft
        case .flipFromRight: return UIView.AnimationOptions.transitionFlipFromRight
        }
    }
}

应用协调器

这里是 AppCoordinator:

    final class AppCoordinator {

    private let preferences: Preferences
    private let securePersistence: SecurePersistence
    private let navigationHandler: (AnyScreen, TransitionAnimation) -> Void

    init(

        dependencies: (securePersistence: SecurePersistence, preferences: Preferences),

        navigator navigationHandler: @escaping (AnyScreen, TransitionAnimation) -> Void

    ) {

        self.preferences = dependencies.preferences
        self.securePersistence = dependencies.securePersistence
        self.navigationHandler = navigationHandler
    }
}

// MARK: Internal
internal extension AppCoordinator {

    func start() {
        navigate(to: initialDestination)
    }
}

// MARK: Destination
private extension AppCoordinator {
    enum Destination {
        case welcome, getStarted, main
    }

    func navigate(to destination: Destination, transitionAnimation: TransitionAnimation = .flipFromLeft) {

        let screen = screenForDestination(destination)
        navigationHandler(screen, transitionAnimation)
    }

    func screenForDestination(_ destination: Destination) -> AnyScreen {
        switch destination {
        case .welcome: return AnyScreen(welcome)
        case .getStarted: return AnyScreen(getStarted)
        case .main: return AnyScreen(main)
        }
    }

    var initialDestination: Destination {
        guard preferences.hasAgreedToTermsAndPolicy else {
            return .welcome
        }

        guard securePersistence.isAccountSetup else {
            return .getStarted
        }

        return .main
    }

}

// MARK: - Screens
private extension AppCoordinator {

    var welcome: some Screen {
        WelcomeScreen()
            .environmentObject(
                WelcomeViewModel(
                    preferences: preferences,
                    termsHaveBeenAccepted: { [unowned self] in self.start() }
                )
            )
    }

    var getStarted: some Screen {
        GetStartedScreen()
            .environmentObject(
                GetStartedViewModel(
                    preferences: preferences,
                    securePersistence: securePersistence,
                    walletCreated: { [unowned self] in self.navigate(to: .main) }
                )
        )
    }

    var main: some Screen {
        return MainScreen().environmentObject(
            MainViewModel(
                preferences: preferences,
                securePersistence: securePersistence,
                walletDeleted: { [unowned self] in
                    self.navigate(to: .getStarted, transitionAnimation: .flipFromRight)
                }
            )
        )
    }
}