Storyboard登录屏幕的最佳做法,在注销时处理数据清除

Tre*_*man 285 login clear logout ios uistoryboard

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

我想完成以下任务:

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

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

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

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

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

  • 第一次启动应用程序时,Tab Bar Controller会在执行segue之前短暂显示.我已经尝试过移动代码viewWillAppear但是segue不会那么早.
  • 当他们注销时,所有数据仍然在所有视图控制器中.如果他们登录到新帐户,旧帐户数据仍会显示,直到刷新为止.我需要一种方法在注销时轻松清除它.

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

bha*_*ari 308

你的故事板应该是这样的

在您的didFinishLaunchingWithOptions中的appDelegate.m中

//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;
}
Run Code Online (Sandbox Code Playgroud)

在SignUpViewController.m文件中

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

    appDelegateTemp.window.rootViewController = [[UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]] instantiateInitialViewController];
}
Run Code Online (Sandbox Code Playgroud)

在文件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;

}
Run Code Online (Sandbox Code Playgroud)

Swift 4版本

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

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

    return true
Run Code Online (Sandbox Code Playgroud)

在注册视图控制器中:

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

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
Run Code Online (Sandbox Code Playgroud)

  • -1用于在`UIViewController`中使用`AppDelegate`并在那里设置`window.rootViewController`.我不认为这是"最佳做法". (26认同)
  • 不想在没有发布回答的情况下给`-1`:http://stackoverflow.com/a/30664935/1226304 (2认同)

Tre*_*man 97

以下是我最终要做的事情.除此之外,您唯一需要考虑的是(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];

}
Run Code Online (Sandbox Code Playgroud)

LoginViewController.m

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

-(void) loginWasSuccessful
{

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

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

}
Run Code Online (Sandbox Code Playgroud)

  • 在iOS 8.1(可能是8.0,尚未测试)中,这不再顺利.初始View Controller闪烁了一会儿. (24认同)
  • @Julian在iOS 8中,我替换了两行```[self.window makeKeyAndVisible]; [self.window.rootViewController presentViewController:viewController animated:animated completion:nil];```````self.window.rootViewController = viewController;```来防止闪烁.要动画,只需将其包装在```[UIView transitionWithView ...];```中 (9认同)
  • 这种方法有Swift版本吗? (7认同)
  • 你用什么通知? (2认同)
  • @Seano 是的。将您在上面看到的代码转换为不同的语法。API 完全相同。没有区别。 (2认同)

Dim*_*kas 20

编辑:添加注销操作.

在此输入图像描述

1.首先准备app委托文件

AppDelegate.h

#import <UIKit/UIKit.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>

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

@end
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

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

User.h

#import <Foundation/Foundation.h>

@interface User : NSObject

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

@end
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

3.创建一个新的控制器RootViewController并与第一个视图连接,其中登录按钮处于活动状态.还添加一个Storyboard 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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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

LoginViewController.h

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

@interface LoginViewController : UIViewController
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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

ProfileViewController.h

#import <UIKit/UIKit.h>

@interface ProfileViewController : UIViewController

@end
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

LoginExample是一个示例项目以获得额外帮助.

  • 示例项目帮助我非常了解登录n注销的概念..非常感谢:) (3认同)

der*_*iuk 14

我不喜欢bhavya的答案,因为AppDelegate在View Controllers 中使用并且设置rootViewController没有动画.而Trevor的答案与iOS8上的闪存视图控制器有关.

UPD 07/18/2015

视图控制器内部的AppDelegate:

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

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

AppDelegate(拥有windowrootViewController)

ViewController(拥有view)

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

如果任何对象将更改此层次结构中的任何对象,迟早会在代码中出现问题.在小项目上可能没问题但是在bit projects =]上挖掘这个烂摊子并不好玩

UPD 07/18/2015

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

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

这个怎么运作:

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

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

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

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

动画师逻辑的工作原理:

  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]];
         }];
    }
    
    Run Code Online (Sandbox Code Playgroud)

测试项目在这里.

  • 就个人而言,我对View Controllers了解`AppDelegate`没有任何问题(我有兴趣了解你为什么这么做) - 但你对缺乏动画的评论是非常有效的.这可以通过以下答案解决:http://stackoverflow.com/questions/8053832/rootviewcontroller-animation-transition-initial-orientation-is-wrong (3认同)
  • @HughHughTeotl感谢您的评论和链接.我更新了我的答案. (2认同)

Har*_*oom 10

这是我未来旁观者的Swifty解决方案.

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

protocol LoginFlowHandler {
    func handleLogin(withWindow window: UIWindow?)
    func handleLogout(withWindow window: UIWindow?)
}
Run Code Online (Sandbox Code Playgroud)

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

}
Run Code Online (Sandbox Code Playgroud)

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
    }

}
Run Code Online (Sandbox Code Playgroud)

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


Mih*_*ado 8

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

  • 视图是否应在`viewDidAppear'中显示(对用户可见)?这仍然会产生闪烁. (2认同)
  • 不是答案.并且"将一个bool密钥存储在NSUserDefaults中以查看它是否是第一次启动."对于那种数据非常危险. (2认同)

Mah*_*toy 5

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

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

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

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

    let rootViewController: UIViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("LoginViewController")
Run Code Online (Sandbox Code Playgroud)


iAl*_*ndr 5

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

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

Then I prefer to create the Constant struct:

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

    struct kUserDefaults {
        static let isSignIn = "isSignIn"
    }
}
Run Code Online (Sandbox Code Playgroud)

In LoginViewController add IBAction:

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

In ProfileViewController add IBAction:

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

In AppDelegate add line of code in didFinishLaunchingWithOptions:

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

    Switcher.updateRootViewController()

    return true
}
Run Code Online (Sandbox Code Playgroud)

Finally create Switcher class:

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

    }

}
Run Code Online (Sandbox Code Playgroud)

That is all!