ios Storyboard 登录屏幕的最佳实践,处理注销时的数据清除

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19962276/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 02:55:07  来源:igfitidea点击:

Best practices for Storyboard login screen, handling clearing of data upon logout

iosloginuistoryboardlogoutclear

提问by Trevor Gehman

I'm building an iOS app using a Storyboard. The root view controller is a Tab Bar Controller. I'm creating the login/logout process, and it's mostly working fine, but I've got a few issues. I need to know the BEST way to set all this up.

我正在使用 Storyboard 构建一个 iOS 应用程序。根视图控制器是一个标签栏控制器。我正在创建登录/注销过程,并且大部分工作正常,但我遇到了一些问题。我需要知道设置所有这些的最佳方法。

I want to accomplish the following:

我想完成以下任务:

  1. Show a login screen the first time the app is launched. When they login, go to the first tab of the Tab Bar Controller.
  2. Any time they launch the app after that, check if they are logged in, and skip straight to the first tab of the root Tab Bar Controller.
  3. When they manually click a logout button, show the login screen, and clear all the data from the view controllers.
  1. 首次启动应用程序时显示登录屏幕。当他们登录时,转到选项卡栏控制器的第一个选项卡。
  2. 之后任何时候他们启动应用程序,检查他们是否登录,并直接跳到根标签栏控制器的第一个标签。
  3. 当他们手动单击注销按钮时,显示登录屏幕,并清除视图控制器中的所有数据。

What I've done so far is set the root view controller to the Tab Bar Controller, and created a custom segue to my Login view controller. Inside my Tab Bar Controller class, I check whether they are logged in inside the viewDidAppearmethod, and a perform the segue: [self performSegueWithIdentifier:@"pushLogin" sender:self];

到目前为止,我所做的是将根视图控制器设置为 Tab Bar Controller,并为我的 Login 视图控制器创建了一个自定义 segue。在我的 Tab Bar Controller 类中,我检查它们是否在viewDidAppear方法内登录,并执行 segue:[self performSegueWithIdentifier:@"pushLogin" sender:self];

I also setup a notification for when the logout action needs to be performed: [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(logoutAccount) name:@"logoutAccount" object:nil];

我还设置了何时需要执行注销操作的通知: [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(logoutAccount) name:@"logoutAccount" object:nil];

Upon logout, I clear the credentials from the Keychain, run [self setSelectedIndex:0]and perform the segue to show the login view controller again.

注销后,我从钥匙串中清除凭据,运行[self setSelectedIndex:0]并执行 segue 以再次显示登录视图控制器。

This all works fine, but I'm wondering: should this logic be in the AppDelegate?I also have two issues:

这一切都很好,但我想知道:这个逻辑应该在 AppDelegate 中吗?我也有两个问题:

  • The first time they launch the app, the Tab Bar Controller shows briefly before the segue is performed. I've tried moving the code to viewWillAppearbut the segue will not work that early.
  • When they logout, all the data is still inside all the view controllers. If they login to a new account, the old account data is still displayed until they refresh. I need a way to clear this easily on logout.
  • 他们第一次启动应用程序时,标签栏控制器会在执行转场之前短暂显示。我试过将代码移到,viewWillAppear但 segue 不会那么早起作用。
  • 当他们注销时,所有数据仍然在所有视图控制器中。如果他们登录到新帐户,旧帐户数据仍会显示,直到他们刷新。我需要一种在注销时轻松清除此问题的方法。

I'm open to reworking this. I've considered making the login screen the root view controller, or creating a navigation controller in the AppDelegate to handle everything... I'm just not sure what the best method is at this point.

我愿意重新设计这个。我已经考虑过将登录屏幕作为根视图控制器,或者在 AppDelegate 中创建一个导航控制器来处理所有事情......我只是不确定此时最好的方法是什么。

采纳答案by Trevor Gehman

Here is what I ended up doing to accomplish everything. The only thing you need to consider in addition to this is (a) the login process and (b) where you are storing your app data (in this case, I used a singleton).

这是我最终为完成所有事情所做的事情。除此之外,您唯一需要考虑的是 (a) 登录过程和 (b) 存储应用程序数据的位置(在这种情况下,我使用了单例)。

Storyboard showing login view controller and main tab controller

显示登录视图控制器和主选项卡控制器的故事板

As you can see, the root view controller is my Main Tab Controller. I did this because after the user has logged in, I want the app to launch directly to the first tab. (This avoids any "flicker" where the login view shows temporarily.)

如您所见,根视图控制器是我的Main Tab Controller。我这样做是因为在用户登录后,我希望应用程序直接启动到第一个选项卡。(这避免了登录视图临时显示的任何“闪烁”。)

AppDelegate.m

AppDelegate.m

In this file, I check whether the user is already logged in. If not, I push the login view controller. I also handle the logout process, where I clear data and show the login view.

在这个文件中,我检查用户是否已经登录。如果没有,我推送登录视图控制器。我还处理注销过程,在那里我清除数据并显示登录视图。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{

    // Show login view if not logged in already
    if(![AppData isLoggedIn]) {
        [self showLoginScreen:NO];
    }

    return YES;
}

-(void) showLoginScreen:(BOOL)animated
{

    // Get login screen from storyboard and present it
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard" bundle:nil];
    LoginViewController *viewController = (LoginViewController *)[storyboard instantiateViewControllerWithIdentifier:@"loginScreen"];
    [self.window makeKeyAndVisible];
    [self.window.rootViewController presentViewController:viewController
                                             animated:animated
                                           completion:nil];
}

-(void) logout
{
    // Remove data from singleton (where all my app data is stored)
    [AppData clearData];

   // Reset view controller (this will quickly clear all the views)
   UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard" bundle:nil];
   MainTabControllerViewController *viewController = (MainTabControllerViewController *)[storyboard instantiateViewControllerWithIdentifier:@"mainView"];
   [self.window setRootViewController:viewController];

   // Show login screen
   [self showLoginScreen:NO];

}

LoginViewController.m

登录视图控制器.m

Here, if the login is successful, I simply dismiss the view and send a notification.

在这里,如果登录成功,我只需关闭视图并发送通知。

-(void) loginWasSuccessful
{

     // Send notification
     [[NSNotificationCenter defaultCenter] postNotificationName:@"loginSuccessful" object:self];

     // Dismiss login screen
     [self dismissViewControllerAnimated:YES completion:nil];

}

回答by bhavya kothari

Your storyboard should look like this

你的故事板应该是这样的

In your appDelegate.m inside your didFinishLaunchingWithOptions

在你的 appDelegate.m 中你的 didFinishLaunchingWithOptions

//authenticatedUser: check from NSUserDefaults User credential if its present then set your navigation flow accordingly

if (authenticatedUser) 
{
    self.window.rootViewController = [[UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]] instantiateInitialViewController];        
}
else
{
    UIViewController* rootController = [[UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]] instantiateViewControllerWithIdentifier:@"LoginViewController"];
    UINavigationController* navigation = [[UINavigationController alloc] initWithRootViewController:rootController];

    self.window.rootViewController = navigation;
}

In SignUpViewController.m file

在 SignUpViewController.m 文件中

- (IBAction)actionSignup:(id)sender
{
    AppDelegate *appDelegateTemp = [[UIApplication sharedApplication]delegate];

    appDelegateTemp.window.rootViewController = [[UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]] instantiateInitialViewController];
}

In file MyTabThreeViewController.m

在文件 MyTabThreeViewController.m 中

- (IBAction)actionLogout:(id)sender {

    // Delete User credential from NSUserDefaults and other data related to user

    AppDelegate *appDelegateTemp = [[UIApplication sharedApplication]delegate];

    UIViewController* rootController = [[UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]] instantiateViewControllerWithIdentifier:@"LoginViewController"];

    UINavigationController* navigation = [[UINavigationController alloc] initWithRootViewController:rootController];
    appDelegateTemp.window.rootViewController = navigation;

}

Swift 4 Version

斯威夫特 4 版本

didFinishLaunchingWithOptions in app delegate assuming your initial view controller is the signed in TabbarController.

应用程序委托中的 didFinishLaunchingWithOptions 假设您的初始视图控制器是在 TabbarController 中签名的。

if Auth.auth().currentUser == nil {
        let rootController = UIStoryboard(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier: "WelcomeNavigation")
        self.window?.rootViewController = rootController
    }

    return true

In Sign up view controller:

在注册视图控制器中:

@IBAction func actionSignup(_ sender: Any) {
let appDelegateTemp = UIApplication.shared.delegate as? AppDelegate
appDelegateTemp?.window?.rootViewController = UIStoryboard(name: "Main", bundle: Bundle.main).instantiateInitialViewController()
}

MyTabThreeViewController

MyTabThreeViewController

 //Remove user credentials
guard let appDel = UIApplication.shared.delegate as? AppDelegate else { return }
        let rootController = UIStoryboard(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier: "WelcomeNavigation")
        appDel.window?.rootViewController = rootController

回答by Dimitris Bouzikas

EDIT:Add logout action.

编辑:添加注销操作。

enter image description here

在此处输入图片说明

1.First of all prepare the app delegate file

1.首先准备app委托文件

AppDelegate.h

AppDelegate.h

#import <UIKit/UIKit.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;
@property (nonatomic) BOOL authenticated;

@end

AppDelegate.m

AppDelegate.m

#import "AppDelegate.h"
#import "User.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    User *userObj = [[User alloc] init];
    self.authenticated = [userObj userAuthenticated];

    return YES;
}

2.Create a class named User.

2.创建一个名为 User 的类。

User.h

用户名

#import <Foundation/Foundation.h>

@interface User : NSObject

- (void)loginWithUsername:(NSString *)username andPassword:(NSString *)password;
- (void)logout;
- (BOOL)userAuthenticated;

@end

User.m

用户名

#import "User.h"

@implementation User

- (void)loginWithUsername:(NSString *)username andPassword:(NSString *)password{

    // Validate user here with your implementation
    // and notify the root controller
    [[NSNotificationCenter defaultCenter] postNotificationName:@"loginActionFinished" object:self userInfo:nil];
}

- (void)logout{
    // Here you can delete the account
}

- (BOOL)userAuthenticated {

    // This variable is only for testing
    // Here you have to implement a mechanism to manipulate this
    BOOL auth = NO;

    if (auth) {
        return YES;
    }

    return NO;
}

3.Create a new controller RootViewController and connected with the first view, where login button live. Add also a Storyboard ID: "initialView".

3.创建一个新的控制器 RootViewController 并连接到第一个视图,登录按钮所在的位置。还添加一个故事板 ID:“initialView”。

RootViewController.h

根视图控制器.h

#import <UIKit/UIKit.h>
#import "LoginViewController.h"

@protocol LoginViewProtocol <NSObject>

- (void)dismissAndLoginView;

@end

@interface RootViewController : UIViewController

@property (nonatomic, weak) id <LoginViewProtocol> delegate;
@property (nonatomic, retain) LoginViewController *loginView;


@end

RootViewController.m

根视图控制器.m

#import "RootViewController.h"

@interface RootViewController ()

@end

@implementation RootViewController

@synthesize loginView;

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (IBAction)loginBtnPressed:(id)sender {

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(loginActionFinished:)
                                                 name:@"loginActionFinished"
                                               object:loginView];

}

#pragma mark - Dismissing Delegate Methods

-(void) loginActionFinished:(NSNotification*)notification {

    AppDelegate *authObj = (AppDelegate*)[[UIApplication sharedApplication] delegate];
    authObj.authenticated = YES;

    [self dismissLoginAndShowProfile];
}

- (void)dismissLoginAndShowProfile {
    [self dismissViewControllerAnimated:NO completion:^{
        UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
        UITabBarController *tabView = [storyboard instantiateViewControllerWithIdentifier:@"profileView"];
        [self presentViewController:tabView animated:YES completion:nil];
    }];


}

@end

4.Create a new controller LoginViewController and connected with the login view.

4.新建一个控制器LoginViewController并连接登录视图。

LoginViewController.h

登录视图控制器.h

#import <UIKit/UIKit.h>
#import "User.h"

@interface LoginViewController : UIViewController

LoginViewController.m

登录视图控制器.m

#import "LoginViewController.h"
#import "AppDelegate.h"

- (void)viewDidLoad
{
    [super viewDidLoad];
}

- (IBAction)submitBtnPressed:(id)sender {
    User *userObj = [[User alloc] init];

    // Here you can get the data from login form
    // and proceed to authenticate process
    NSString *username = @"username retrieved through login form";
    NSString *password = @"password retrieved through login form";
    [userObj loginWithUsername:username andPassword:password];
}

@end

5.At the end add a new controller ProfileViewController and connected with the profile view in the tabViewController.

5.最后添加一个新的控制器 ProfileViewController 并与 tabViewController 中的配置文件视图相连。

ProfileViewController.h

ProfileViewController.h

#import <UIKit/UIKit.h>

@interface ProfileViewController : UIViewController

@end

ProfileViewController.m

ProfileViewController.m

#import "ProfileViewController.h"
#import "RootViewController.h"
#import "AppDelegate.h"
#import "User.h"

@interface ProfileViewController ()

@end

@implementation ProfileViewController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];

}

- (void) viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    if(![(AppDelegate*)[[UIApplication sharedApplication] delegate] authenticated]) {

        UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];

        RootViewController *initView =  (RootViewController*)[storyboard instantiateViewControllerWithIdentifier:@"initialView"];
        [initView setModalPresentationStyle:UIModalPresentationFullScreen];
        [self presentViewController:initView animated:NO completion:nil];
    } else{
        // proceed with the profile view
    }
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (IBAction)logoutAction:(id)sender {

   User *userObj = [[User alloc] init];
   [userObj logout];

   AppDelegate *authObj = (AppDelegate*)[[UIApplication sharedApplication] delegate];
   authObj.authenticated = NO;

   UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];

   RootViewController *initView =  (RootViewController*)[storyboard instantiateViewControllerWithIdentifier:@"initialView"];
   [initView setModalPresentationStyle:UIModalPresentationFullScreen];
   [self presentViewController:initView animated:NO completion:nil];

}

@end

LoginExampleis a sample project for extra help.

LoginExample是一个提供额外帮助的示例项目。

回答by derpoliuk

I didn't like bhavya's answer because of using AppDelegateinside View Controllers and setting rootViewControllerhas no animation. And Trevor's answer has issue with flashing view controller on iOS8.

我不喜欢 bhavya 的回答,因为AppDelegate在视图控制器内部使用并且设置rootViewController没有动画。特雷弗的回答在 iOS8 上闪烁视图控制器有问题。

UPD 07/18/2015

UPD 07/18/2015

AppDelegate inside View Controllers:

视图控制器中的 AppDelegate:

Changing AppDelegate state (properties) inside view controller breaks encapsulation.

在视图控制器中更改 AppDelegate 状态(属性)会破坏封装。

Very simple hierarchy of objects in every iOS project:

每个 iOS 项目中非常简单的对象层次结构:

AppDelegate (owns windowand rootViewController)

AppDelegate(拥有windowrootViewController

ViewController (owns view)

视图控制器(拥有view

It's ok that objects from the top change objects at the bottom, because they are creating them. But it's not ok if objects on the bottom change objects on top of them (I described some basic programming/OOP principle : DIP (Dependency Inversion Principle : high level module must not depend on the low level module, but they should depend on abstractions)).

顶部的对象更改底部的对象是可以的,因为它们正在创建它们。但是如果底部的对象改变它们上面的对象就不行(我描述了一些基本的编程/OOP原则:DIP(依赖倒置原则:高级模块不能依赖于低级模块,但它们应该依赖于抽象) )。

If any object will change any object in this hierarchy, sooner or later there will be a mess in the code. It might be ok on the small projects but it's no fun to dig through this mess on the bit projects =]

如果任何对象会更改此层次结构中的任何对象,那么迟早会在代码中出现混乱。在小项目上可能没问题,但在位项目上挖掘这个烂摊子并不有趣=]

UPD 07/18/2015

UPD 07/18/2015

I replicate modal controller animations using UINavigationController(tl;dr: check the project).

我使用UINavigationController(tl;dr: check the project)复制模态控制器动画。

I'm using UINavigationControllerto present all controllers in my app. Initially I displayed login view controller in navigation stack with plain push/pop animation. Than I decided to change it to modal with minimal changes.

我正在使用UINavigationController在我的应用程序中显示所有控制器。最初我在导航堆栈中显示登录视图控制器,带有普通的推送/弹出动画。比我决定以最小的更改将其更改为模态。

How it works:

这个怎么运作:

  1. Initial view controller (or self.window.rootViewController) is UINavigationController with ProgressViewController as a rootViewController. I'm showing ProgressViewController because DataModel can take some time to initialize because it inits core data stack like in this article(I really like this approach).

  2. AppDelegate is responsible for getting login status updates.

  3. DataModel handles user login/logout and AppDelegate is observing it's userLoggedInproperty via KVO. Arguably not the best method to do this but it works for me. (Why KVO is bad, you can check in thisor this article(Why Not Use Notifications? part).

  4. ModalDismissAnimator and ModalPresentAnimator are used to customize default push animation.

  1. 初始视图控制器(或self.window.rootViewController)是 UINavigationController 与 ProgressViewController 作为rootViewController. 我展示 ProgressViewController 是因为 DataModel 可能需要一些时间来初始化,因为它会像本文一样初始化核心数据堆栈(我真的很喜欢这种方法)。

  2. AppDelegate 负责获取登录状态更新。

  3. DataModel 处理用户登录/注销,AppDelegateuserLoggedIn通过 KVO观察它的属性。可以说不是最好的方法,但它对我有用。(为什么 KVO 不好,您可以查看这篇这篇文章(为什么不使用通知?部分)。

  4. ModalDismissAnimator 和 ModalPresentAnimator 用于自定义默认推送动画。

How animators logic works:

动画师逻辑的工作原理:

  1. AppDelegate sets itself as a delegate of self.window.rootViewController(which is UINavigationController).

  2. AppDelegate returns one of animators in -[AppDelegate navigationController:animationControllerForOperation:fromViewController:toViewController:]if necessary.

  3. Animators implement -transitionDuration:and -animateTransition:methods. -[ModalPresentAnimator animateTransition:]:

    - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
    {
        UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
        [[transitionContext containerView] addSubview:toViewController.view];
        CGRect frame = toViewController.view.frame;
        CGRect toFrame = frame;
        frame.origin.y = CGRectGetHeight(frame);
        toViewController.view.frame = frame;
        [UIView animateWithDuration:[self transitionDuration:transitionContext]
                         animations:^
         {
             toViewController.view.frame = toFrame;
         } completion:^(BOOL finished)
         {
             [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
         }];
    }
    
  1. AppDelegate 将自己设置为self.window.rootViewController(UINavigationController)的委托 。

  2. -[AppDelegate navigationController:animationControllerForOperation:fromViewController:toViewController:]如有必要,AppDelegate 会返回其中一位动画师。

  3. 动画师实现-transitionDuration:-animateTransition:方法。-[ModalPresentAnimator animateTransition:]

    - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
    {
        UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
        [[transitionContext containerView] addSubview:toViewController.view];
        CGRect frame = toViewController.view.frame;
        CGRect toFrame = frame;
        frame.origin.y = CGRectGetHeight(frame);
        toViewController.view.frame = frame;
        [UIView animateWithDuration:[self transitionDuration:transitionContext]
                         animations:^
         {
             toViewController.view.frame = toFrame;
         } completion:^(BOOL finished)
         {
             [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
         }];
    }
    

Test project is here.

测试项目在这里

回答by Harry Bloom

Here's my Swifty solution for any future onlookers.

这是我为任何未来的旁观者提供的 Swifty 解决方案。

1) Create a protocol to handle both login and logout functions:

1)创建一个协议来处理登录和注销功能:

protocol LoginFlowHandler {
    func handleLogin(withWindow window: UIWindow?)
    func handleLogout(withWindow window: UIWindow?)
}

2) Extend said protocol and provide the functionality here for logging out:

2)扩展所述协议并在此处提供用于注销的功能:

extension LoginFlowHandler {

    func handleLogin(withWindow window: UIWindow?) {

        if let _ = AppState.shared.currentUserId {
            //User has logged in before, cache and continue
            self.showMainApp(withWindow: window)
        } else {
            //No user information, show login flow
            self.showLogin(withWindow: window)
        }
    }

    func handleLogout(withWindow window: UIWindow?) {

        AppState.shared.signOut()

        showLogin(withWindow: window)
    }

    func showLogin(withWindow window: UIWindow?) {
        window?.subviews.forEach { 
class AppDelegate: UIResponder, UIApplicationDelegate, LoginFlowHandler {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

        window = UIWindow.init(frame: UIScreen.main.bounds)

        initialiseServices()

        handleLogin(withWindow: window)

        return true
    }

}
.removeFromSuperview() } window?.rootViewController = nil window?.rootViewController = R.storyboard.login.instantiateInitialViewController() window?.makeKeyAndVisible() } func showMainApp(withWindow window: UIWindow?) { window?.rootViewController = nil window?.rootViewController = R.storyboard.mainTabBar.instantiateInitialViewController() window?.makeKeyAndVisible() } }

3) Then I can conform my AppDelegate to the LoginFlowHandler protocol, and call handleLoginon startup:

3)然后我可以让我的 AppDelegate 符合 LoginFlowHandler 协议,并handleLogin在启动时调用:

struct Constants {
    struct StoryboardID {
        static let signInViewController = "SignInViewController"
        static let mainTabBarController = "MainTabBarController"
    }

    struct kUserDefaults {
        static let isSignIn = "isSignIn"
    }
}

From here, my protocol extension will handle the logic or determining if the user if logged in/out, and then change the windows rootViewController accordingly!

从这里开始,我的协议扩展将处理逻辑或确定用户是否登录/注销,然后相应地更改 windows rootViewController!

回答by Mihado

Doing this from the app delegate is NOT recommended. AppDelegate manages the app life cycle that relate to launching, suspending, terminating and so on. I suggest doing this from your initial view controller in the viewDidAppear. You can self.presentViewControllerand self.dismissViewControllerfrom the login view controller. Store a boolkey in NSUserDefaultsto see if it's launching for the first time.

不建议从应用程序委托执行此操作。AppDelegate 管理与启动、暂停、终止等相关的应用程序生命周期。我建议从viewDidAppear. 你可以self.presentViewControllerself.dismissViewController从登录视图控制器。存储一个bool密钥,NSUserDefaults看看它是否是第一次启动。

回答by iAleksandr

Create **LoginViewController** and **TabBarController**.

创建 **LoginViewController** 和 **TabBarController**。

After creating the LoginViewControllerand TabBarController, we need to add a StoryboardID as “loginViewController” and “tabBarController” respectively.

创建后LoginViewControllerTabBarController,我们需要添加一个StoryboardID为“ loginViewController”和“ tabBarController分别”。

Then I prefer to create the Constantstruct:

然后我更喜欢创建常量结构:

@IBAction func tapSignInButton(_ sender: UIButton) {
    UserDefaults.standard.set(true, forKey: Constants.kUserDefaults.isSignIn)
    Switcher.updateRootViewController()
}

In LoginViewControlleradd IBAction:

LoginViewController添加IBAction

@IBAction func tapSignOutButton(_ sender: UIButton) {
    UserDefaults.standard.set(false, forKey: Constants.kUserDefaults.isSignIn)
    Switcher.updateRootViewController()
}

In ProfileViewControlleradd IBAction:

ProfileViewController添加IBAction

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

    Switcher.updateRootViewController()

    return true
}

In AppDelegateadd line of code in didFinishLaunchingWithOptions:

AppDelegate中的didFinishLaunchingWithOptions 中添加代码行:

import UIKit

class Switcher {

    static func updateRootViewController() {

        let status = UserDefaults.standard.bool(forKey: Constants.kUserDefaults.isSignIn)
        var rootViewController : UIViewController?

        #if DEBUG
        print(status)
        #endif

        if (status == true) {
            let mainStoryBoard = UIStoryboard(name: "Main", bundle: nil)
            let mainTabBarController = mainStoryBoard.instantiateViewController(withIdentifier: Constants.StoryboardID.mainTabBarController) as! MainTabBarController
            rootViewController = mainTabBarController
        } else {
            let mainStoryBoard = UIStoryboard(name: "Main", bundle: nil)
            let signInViewController = mainStoryBoard.instantiateViewController(withIdentifier: Constants.StoryboardID.signInViewController) as! SignInViewController
            rootViewController = signInViewController
        }

        let appDelegate = UIApplication.shared.delegate as! AppDelegate
        appDelegate.window?.rootViewController = rootViewController

    }

}

Finally create Switcherclass:

最后创建Switcher类:

    let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
    appDelegate.window!.rootViewController = newRootViewController

    let rootViewController: UIViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("LoginViewController")

That is all!

就这些!

回答by Mahbub Morshed

In Xcode 7 you can have multiple storyBoards. It will be better if you can keep the Login flow in a separate storyboard.

在 Xcode 7 中,您可以拥有多个故事板。如果您可以将登录流程保留在单独的故事板中会更好。

This can be done using SELECT VIEWCONTROLLER > Editor > Refactor to Storyboard

这可以使用SELECT VIEWCONTROLLER > Editor > Refactor to Storyboard 来完成

And here is the Swift version for setting a view as the RootViewContoller-

这是用于将视图设置为 RootViewContoller 的 Swift 版本-

- (NSInteger) checkForFirstLaunch
{
    NSInteger result = 0; //no first launch

    // Get current version ("Bundle Version") from the default Info.plist file
    NSString *currentVersion = (NSString*)[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
    NSArray *prevStartupVersions = [[NSUserDefaults standardUserDefaults] arrayForKey:@"prevStartupVersions"];
    if (prevStartupVersions == nil)
    {
        // Starting up for first time with NO pre-existing installs (e.g., fresh
        // install of some version)
        [[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObject:currentVersion] forKey:@"prevStartupVersions"];
        result = 1; //first launch of the app
    } else {
        if (![prevStartupVersions containsObject:currentVersion])
        {
            // Starting up for first time with this version of the app. This
            // means a different version of the app was alread installed once
            // and started.
            NSMutableArray *updatedPrevStartVersions = [NSMutableArray arrayWithArray:prevStartupVersions];
            [updatedPrevStartVersions addObject:currentVersion];
            [[NSUserDefaults standardUserDefaults] setObject:updatedPrevStartVersions forKey:@"prevStartupVersions"];
            result = 2; //first launch of this version of the app
        }
    }

    // Save changes to disk
    [[NSUserDefaults standardUserDefaults] synchronize];

    return result;
}

回答by Thorsten

I use this to check for first launch:

我用它来检查首次启动:

[self.window makeKeyAndVisible];

if (firstLaunch == 1) {
    UINavigationController *_login = [[UINavigationController alloc] initWithRootViewController:loginController];
    [self.window.rootViewController presentViewController:_login animated:NO completion:nil];
}

(if the user deletes the app and re-installs it, it counts like a first launch)

(如果用户删除该应用程序并重新安装它,则视为首次启动)

In the AppDelegate I check for first launch and create a navigation-controller with the login screens (login and register), which I put on top of the current main window:

在 AppDelegate 中,我检查首次启动并创建一个带有登录屏幕(登录和注册)的导航控制器,我将其放在当前主窗口的顶部:

KeychainItemWrapper *keychainItem = [[KeychainItemWrapper alloc] initWithIdentifier:@"com.youridentifier" accessGroup:nil];
[keychainItem setObject:password forKey:(__bridge id)(kSecValueData)];
[keychainItem setObject:email forKey:(__bridge id)(kSecAttrAccount)];

As this is on top of the regular view controller it's independent from the rest of your app and you can just dismiss the view controller, if you don't need it anymore. And you can also present the view this way, if the user presses a button manually.

由于它位于常规视图控制器之上,因此它独立于应用程序的其余部分,如果您不再需要它,您可以关闭视图控制器。如果用户手动按下按钮,您也可以通过这种方式呈现视图。

BTW: I save the login-data from my users like this:

顺便说一句:我像这样保存用户的登录数据:

class Navigator: NSObject {

???static func moveTo(_ destinationViewController: UIViewController, from sourceViewController: UIViewController, transitionStyle: UIModalTransitionStyle? = .crossDissolve, completion: (() -> ())? = nil) {
???????

???????DispatchQueue.main.async {

???????????if var topController = UIApplication.shared.keyWindow?.rootViewController {

???????????????while let presentedViewController = topController.presentedViewController {

???????????????????topController = presentedViewController

???????????????}

???????????????
???????????????destinationViewController.modalTransitionStyle = (transitionStyle ?? nil)!

???????????????sourceViewController.present(destinationViewController, animated: true, completion: completion)

???????????}

???????}

???}

}

For the logout: I switched away from CoreData (too slow) and use NSArrays and NSDictionaries to manage my data now. Logout just means to empty those arrays and dictionaries. Plus I make sure to set my data in viewWillAppear.

对于注销:我从 CoreData(太慢)切换到现在使用 NSArrays 和 NSDictionaries 来管理我的数据。注销只是意味着清空这些数组和字典。另外,我确保在 viewWillAppear 中设置我的数据。

That's it.

就是这样。

回答by Jithin

I had a similar issue to solve in an app and I used the following method. I didn't use notifications for handling the navigation.

我在应用程序中遇到了类似的问题,我使用了以下方法。我没有使用通知来处理导航。

I have three storyboards in the app.

我在应用程序中有三个故事板。

  1. Splash screen storyboard - for app initialisation and checking if the user is already logged in
  2. Login storyboard - for handling user login flow
  3. Tab bar storyboard - for displaying the app content
  1. 启动画面故事板 - 用于应用程序初始化并检查用户是否已登录
  2. 登录故事板 - 用于处理用户登录流程
  3. 标签栏故事板 - 用于显示应用程序内容

My initial storyboard in the app is Splash screen storyboard. I have navigation controller as the root of login and tab bar storyboard to handle view controller navigations.

我在应用程序中的初始故事板是闪屏故事板。我有导航控制器作为登录和标签栏故事板的根来处理视图控制器导航。

I created a Navigator class to handle the app navigation and it looks like this:

我创建了一个 Navigator 类来处理应用导航,它看起来像这样:

let loginSB = UIStoryboard(name: "splash", bundle: nil)

let loginNav = loginSB.instantiateInitialViewcontroller() as! UINavigationController

Navigator.moveTo(loginNav, from: self)

Let's look at the possible scenarios:

让我们看看可能的场景:

  • First app launch; Splash screen will be loaded where I check if the user is already signed in. Then login screen will be loaded using the Navigator class as follows;
  • 第一个应用程序启动;将在我检查用户是否已登录的地方加载启动画面。然后将使用 Navigator 类加载登录屏幕,如下所示;

Since I have navigation controller as the root, I instantiate the navigation controller as initial view controller.

由于我将导航控制器作为根,我将导航控制器实例化为初始视图控制器。

Let tabBarSB = UIStoryboard(name: "tabBar", bundle: nil)
let tabBarNav = tabBarSB.instantiateInitialViewcontroller() as! UINavigationController

Navigator.moveTo(tabBarNav, from: self)

This removes the slpash storyboard from app window's root and replaces it with login storyboard.

这将从应用程序窗口的根目录中删除 slpash 故事板,并将其替换为登录故事板。

From login storyboard, when the user is successfully logged in, I save the user data to User Defaults and initialize a UserData singleton to access the user details. Then Tab bar storyboard is loaded using the navigator method.

从登录故事板中,当用户成功登录时,我将用户数据保存到 User Defaults 并初始化 UserData 单例以访问用户详细信息。然后使用导航器方法加载标签栏故事板。

let loginSB = UIStoryboard(name: "splash", bundle: nil)

let loginNav = loginSB.instantiateInitialViewcontroller() as! UINavigationController

Navigator.moveTo(loginNav, from: self)

Now the user signs out from the settings screen in tab bar. I clear all the saved user data and navigate to login screen.

现在用户从选项卡栏中的设置屏幕注销。我清除所有保存的用户数据并导航到登录屏幕。

##代码##
  • User is logged in and force kills the app
  • 用户已登录并强制终止应用程序

When user launches the app, Splash screen will be loaded. I check if user is logged in and access the user data from User Defaults. Then initialize the UserData singleton and shows tab bar instead of login screen.

当用户启动应用程序时,将加载启动画面。我检查用户是否已登录并从用户默认值访问用户数据。然后初始化 UserData 单例并显示标签栏而不是登录屏幕。