故事板登录屏幕的最佳实践,注销时处理数据清除

我正在使用Storyboard构建一个iOS应用程序。根视图控制器是一个标签栏控制器。我正在创建登录/注销过程,它基本上工作正常,但我有一些问题。我需要知道最好的方法来设置这一切。

我想做到以下几点:

  1. 在应用程序第一次启动时显示登录屏幕。当他们登录时,转到标签栏控制器的第一个标签。
  2. 任何时候他们启动应用程序之后,检查他们是否登录,并直接跳到根标签栏控制器的第一个标签。
  3. 当他们手动单击登出按钮时,显示登录屏幕,并清除视图控制器中的所有数据。

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

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

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

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

  • 他们第一次启动应用,标签栏控制器在segue执行之前简要显示。我已经尝试将代码移动到viewWillAppear,但segue不会那么早工作。
  • 注销时,所有数据仍在所有视图控制器中。如果他们登录到一个新帐户,旧帐户数据仍然显示,直到他们刷新。我需要一种方法来清除这很容易登出。

我愿意重新修改。我考虑过让登录屏幕成为根视图控制器,或者在AppDelegate中创建一个导航控制器来处理所有事情…我只是不确定目前最好的方法是什么。

129776 次浏览

我和你的情况一样,我找到的清理数据的解决方案是删除所有的CoreData东西,我的视图控制器依赖于绘制它的信息。但是我仍然发现这种方法是非常糟糕的,我认为一个更优雅的方法可以做到这一点,没有故事板,只使用代码来管理视图控制器之间的转换。

我在Github找到了这个项目,它只通过代码来做所有这些事情,而且很容易理解。他们使用一个类似facebook的侧菜单,他们所做的是根据用户是否登录而改变中心视图控制器。当用户注销时,appDelegate从CoreData中删除数据,并将主视图控制器再次设置为登录屏幕。

我用这个来检查第一次发射:

- (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;
}

(如果用户删除应用程序并重新安装,则算作第一次启动)

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

[self.window makeKeyAndVisible];


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

因为它在常规视图控制器的顶部它独立于应用的其他部分如果你不再需要它,你可以解散视图控制器。如果用户手动按下按钮,您也可以以这种方式显示视图。

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

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

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

就是这样。

你的故事板应该看起来像这样

在你的appDelegate中。我在你的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;
}

在SignUpViewController。m文件

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


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

在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版本

didFinishLaunchingWithOptions在app delegate中假设你的初始视图控制器是TabbarController中的signed。

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


return true

在注册视图控制器中:

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

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

编辑:添加注销动作。

enter image description here

首先准备app委托文件

AppDelegate.h

#import <UIKit/UIKit.h>


@interface AppDelegate : UIResponder <UIApplicationDelegate>


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


@end

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

创建一个名为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;
}

创建一个新的控制器RootViewController,并连接到第一个视图,其中登录按钮存在。同时添加一个故事板ID: "initialView"。

RootViewController.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

#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

创建一个新的控制器LoginViewController,并连接到登录视图。

LoginViewController.h

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


@interface LoginViewController : UIViewController

LoginViewController.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.最后添加一个新的控制器ProfileViewController,并连接到tabViewController中的配置文件视图。

ProfileViewController.h

#import <UIKit/UIKit.h>


@interface ProfileViewController : UIViewController


@end

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

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

下面是我最终完成所有事情的方法。除此之外,你需要考虑的唯一一件事是(a)登录过程和(b)存储应用数据的位置(在本例中,我使用了单例)。

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

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

AppDelegate.m

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

- (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

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

-(void) loginWasSuccessful
{


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


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


}

我不喜欢bhavya的答案,因为在视图控制器中使用AppDelegate并且设置rootViewController没有动画。Trevor的回答是关于iOS8上闪烁视图控制器的问题。

乌利希期刊指南07/18/2015

视图控制器内部的AppDelegate:

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

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

AppDelegate(拥有windowrootViewController)

ViewController(拥有view)

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

如果任何对象将改变这个层次结构中的任何对象,那么代码中迟早会出现混乱。在小项目上可能没问题,但在小项目上挖掘这个混乱是没有乐趣的=]

乌利希期刊指南07/18/2015

我使用UINavigationController复制模态控制器动画(tl;dr:检查项目)。

我使用UINavigationController来显示我的应用程序中的所有控制器。最初我在导航堆栈中显示登录视图控制器,使用普通的推送/弹出动画。然后我决定用最小的变化把它改成模态。

工作原理:

  1. 初始视图控制器(或self.window.rootViewController)是UINavigationController, ProgressViewController作为rootViewController。我正在显示ProgressViewController,因为DataModel可以花费一些时间来初始化,因为它初始化核心数据堆栈,如文章(我真的喜欢这种方法)。

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

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

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

动画师的逻辑工作原理:

  1. AppDelegate将自己设置为self.window.rootViewController(即UINavigationController)的委托。

  2. 如有必要,AppDelegate返回-[AppDelegate navigationController:animationControllerForOperation:fromViewController:toViewController:]中的一个动画器。

  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.

不建议在应用委托中这样做。AppDelegate管理与启动、挂起、终止等相关的应用生命周期。我建议从viewDidAppear中的初始视图控制器执行此操作。你可以从登录视图控制器中self.presentViewControllerself.dismissViewController。在NSUserDefaults中存储bool键,以查看它是否第一次启动。

在Xcode 7中,你可以有多个故事板。如果您能将Login流保存在一个单独的故事板中会更好。

这可以使用选择VIEWCONTROLLER >编辑器>重构到故事板来完成

这里是设置一个视图为rootviewcontroller -的Swift版本

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


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

enter image description here

在App Delegate.m

 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
[[UIBarButtonItem appearance] setBackButtonTitlePositionAdjustment:UIOffsetMake(0, -60)
forBarMetrics:UIBarMetricsDefault];


NSString *identifier;
BOOL isSaved = [[NSUserDefaults standardUserDefaults] boolForKey:@"loginSaved"];
if (isSaved)
{
//identifier=@"homeViewControllerId";
UIWindow* mainWindow=[[[UIApplication sharedApplication] delegate] window];
UITabBarController *tabBarVC =
[[UIStoryboard storyboardWithName:@"Main" bundle:nil] instantiateViewControllerWithIdentifier:@"TabBarVC"];
mainWindow.rootViewController=tabBarVC;
}
else
{




identifier=@"loginViewControllerId";
UIStoryboard *    storyboardobj=[UIStoryboard storyboardWithName:@"Main" bundle:nil];
UIViewController *screen = [storyboardobj instantiateViewControllerWithIdentifier:identifier];


UINavigationController *navigationController=[[UINavigationController alloc] initWithRootViewController:screen];


self.window.rootViewController = navigationController;
[self.window makeKeyAndVisible];


}


return YES;

controller.m < p >视图 在视图did load

- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.


UIBarButtonItem* barButton = [[UIBarButtonItem alloc] initWithTitle:@"Logout" style:UIBarButtonItemStyleDone target:self action:@selector(logoutButtonClicked:)];
[self.navigationItem setLeftBarButtonItem:barButton];

在登出按钮动作

-(void)logoutButtonClicked:(id)sender{


UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Alert" message:@"Do you want to logout?" preferredStyle:UIAlertControllerStyleAlert];


[alertController addAction:[UIAlertAction actionWithTitle:@"Logout" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setBool:NO forKey:@"loginSaved"];
[[NSUserDefaults standardUserDefaults] synchronize];
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
UIStoryboard *    storyboardobj=[UIStoryboard storyboardWithName:@"Main" bundle:nil];
UIViewController *screen = [storyboardobj instantiateViewControllerWithIdentifier:@"loginViewControllerId"];
[appDelegate.window setRootViewController:screen];
}]];




[alertController addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[self dismissViewControllerAnimated:YES completion:nil];
}]];


dispatch_async(dispatch_get_main_queue(), ^ {
[self presentViewController:alertController animated:YES completion:nil];
});}

以下是我的霉霉解决方案,以飨未来的围观群众。

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

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

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 { $0.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)然后我可以使我的AppDelegate符合LoginFlowHandler协议,并在启动时调用handleLogin:

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
}


}

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

感谢bhavya的解决方案。关于swift,有两种答案,但都不是很完整。我已经用swift3做过了。下面是主要代码。

在AppDelegate.swift

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.


// seclect the mainStoryBoard entry by whthere user is login.
let userDefaults = UserDefaults.standard


if let isLogin: Bool = userDefaults.value(forKey:Common.isLoginKey) as! Bool? {
if (!isLogin) {
self.window?.rootViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "LogIn")
}
}else {
self.window?.rootViewController = mainStoryboard.instantiateViewController(withIdentifier: "LogIn")
}


return true
}

在SignUpViewController.swift

@IBAction func userLogin(_ sender: UIButton) {
//handle your login work
UserDefaults.standard.setValue(true, forKey: Common.isLoginKey)
let delegateTemp = UIApplication.shared.delegate
delegateTemp?.window!?.rootViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Main")
}

在logOutAction函数中

@IBAction func logOutAction(_ sender: UIButton) {
UserDefaults.standard.setValue(false, forKey: Common.isLoginKey)
UIApplication.shared.delegate?.window!?.rootViewController = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController()
}

我在一个应用程序中也有类似的问题要解决,我使用了以下方法。我没有使用通知来处理导航。

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

  1. 启动画面故事板-用于应用程序初始化和检查用户是否已经登录
  2. 登录故事板——用于处理用户登录流程
  3. 标签栏故事板-用于显示应用程序内容
我在应用程序中的初始故事板是启动屏幕故事板。 我有导航控制器作为登录和标签栏故事板的根来处理视图控制器导航

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

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)


           }


       }


   }


}

让我们来看看可能的场景:

    第一个应用程序启动; 启动屏幕将加载,我检查用户是否已经登录。然后登录屏幕将使用Navigator类加载,如下所示

因为我有导航控制器作为根,我实例化导航控制器作为初始视图控制器。

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


let loginNav = loginSB.instantiateInitialViewcontroller() as! UINavigationController


Navigator.moveTo(loginNav, from: self)

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

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

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


Navigator.moveTo(tabBarNav, from: self)

现在用户从标签栏的设置屏幕中退出。我清除所有保存的用户数据,并导航到登录屏幕。

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


let loginNav = loginSB.instantiateInitialViewcontroller() as! UINavigationController


Navigator.moveTo(loginNav, from: self)
  • 用户登录并强制杀死应用程序

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

Create **LoginViewController** and **TabBarController**. < / >

在创建LoginViewController选项卡之后,我们需要分别添加一个名为“loginViewController”和“选项卡”的StoryboardID。

然后,我更喜欢创建常数结构体:

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


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

LoginViewController中添加IBAction:

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

ProfileViewController中添加IBAction:

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

AppDelegate中添加didFinishLaunchingWithOptions中的代码行:

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


Switcher.updateRootViewController()


return true
}

最后创建切换器类:

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


}


}

就这些!

更新Xcode 11的@iAleksandr answer,因为Scene kit会导致问题。

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

guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,let sceneDelegate = windowScene.delegate as? SceneDelegate         else {
return
}
sceneDelegate.window?.rootViewController = rootViewController
  1. 呼叫Switcher。updateRootViewcontroller在场景委托而不是应用委托,像这样:

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions)
    {
    
    
    Switcher.updateRootViewController()
    guard let _ = (scene as? UIWindowScene) else { return }
    }