Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

ios

swift

swiftui

I'm developing a simple iOS app with SwiftUI with two views: a LogInView() and a HomeView().

What I want is really simple: when the user clicks on the Login Button on LogInView() I want the app to hide the LogInView() and show the HomeView(), full screen, not like a modal and without allowing the user to go back.

This could be easily done with Storyboards in Swift and UIKit, is there any way to do this with SwiftUI?

Any help is appreciated. Thanks in advance.

My code:

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)
        }
    }
}

HomeView:

struct HomeView: View {
    var body: some View {
        Text("Home Page")
    }
}
like image 672
iAlex11 Avatar asked Aug 29 '19 13:08

iAlex11


2 Answers

Update: I got some time to update the answer, and add a transition. Note that I changed Group by VStack, otherwise the transition does not work.

You can alter the duration in the withAnimationcall (button closure).

I also moved some modifiers in your button, so the whole thing is "tappable". Otherwise, only tapping on the text of the button would trigger the action.


You can use an ObservedObject, an EnvironmentObject or a Binding. Here's an example with 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)
        }
    }
}
like image 176
kontiki Avatar answered Nov 03 '22 00:11

kontiki


The answer by @kontiki is probably the most SwiftUI-y, but I will present a different solution, probably not as good! But maybe more flexible/scalable.

You can swap rootView of UIHostingController:

SceneDelegate

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

And here is the 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)
                }
            )
        )
    }
}
like image 27
Sajjon Avatar answered Nov 03 '22 02:11

Sajjon