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


290

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

我要完成以下任务:

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

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

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

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

一切正常,但我想知道:这种逻辑是否应该在AppDelegate中?我还有两个问题:

  • 他们首次启动该应用程序时,会在执行Segue之前短暂显示Tab Bar Controller。我曾尝试将代码移到其中,viewWillAppear但是segue不能这么早地起作用。
  • 当他们注销时,所有数据仍位于所有视图控制器中。如果他们登录到新帐户,则在刷新之前仍会显示旧帐户数据。我需要一种在注销时轻松清除此问题的方法。

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


您是否将登录视图控制器呈现为模态?
vokilam 2014年

@TrevorGehman-可以添加您的故事板图片
rohan k shah 2014年

我提交了一个答案,其中详细说明了我最终要做的事情。这与提供的其他一些答案类似,尤其是@bhavya kothari。
Trevor Gehman 2014年

对于呈现登录屏幕,AuthNavigation可能会有用。如果需要,它可以组织登录屏幕的显示,还支持自动登录。
Codey

几乎总是可以解决的非常基本的问题之一,但与此同时感觉本来可以做得更好
amar

Answers:


310

您的故事板应如下所示

在您的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;
}

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

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

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

您忘记了注销后从userDefaults中删除bool身份验证
CodeLover

28
-1用于AppDelegate内部UIViewController设置window.rootViewController。我不认为这是“最佳做法”。
derpoliuk 2015年

2
不想放弃-1而不给出答案:stackoverflow.com/a/30664935/1226304
derpoliuk 2015年

1
我试图在IOS8上快速执行此操作,但是在应用启动时出现以下错误,并且登录屏幕显示:“开始/结束外观转换的不平衡呼叫”。我注意到,当应用程序加载时,将显示登录屏幕,而且选项卡栏控制器上的第一个选项卡也正在加载。通过viewdidload中的println()确认了这一点。有什么建议吗?
Alex Lacayo 2015年

1
答对了!-2。-1用于AppDelegate内部UIViewController-1用于将登录密钥存储在中NSUserDefaults。这种数据非常不安全!
skywinder

97

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

情节提要显示登录视图控制器和主选项卡控制器

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

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

}

2
您将通知用于什么目的?
2014年

1
@BFeher是正确的。我使用通知来触发新的数据提取。您可以使用它来做任何您想做的事情,但就我而言,我需要通知我登录成功,并且需要新数据。
Trevor Gehman 2014年

24
在iOS 8.1(也许还没有测试过的8.0)中,此操作不再顺利。初始View Controller闪烁一会儿。
BFeher 2014年

7
有这种方法的Swift版本吗?
西诺

9
@Julian在iOS 8中,我将两行[self.window makeKeyAndVisible]; [self.window.rootViewController presentViewController:viewController animated:animated completion:nil];替换self.window.rootViewController = viewController;为防止闪烁。要对其进行动画处理,只需将其包装在[UIView transitionWithView...];
BFeher 2015年

20

编辑:添加注销操作。

在此处输入图片说明

1.首先准备应用程序委托文件

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

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

用户名

#import <Foundation/Foundation.h>

@interface User : NSObject

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

@end

用户名

#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.创建一个新的控制器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

4.创建一个新的控制器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是一个示例项目,可提供额外的帮助。


3
示例项目非常帮助我理解了登录n注销的概念。.非常感谢:)
Dave

16

我不喜欢bhavya的答案,因为使用了AppDelegate内部View Controller和设置rootViewController没有动画。Trevor的答案与iOS8上的闪烁视图控制器有关。

UPD 2015年7月18日

视图控制器内的AppDelegate:

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

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

AppDelegate(拥有windowrootViewController

ViewController(拥有view

可以确定顶部的对象会更改底部的对象,因为它们是在创建它们。但是如果底部的对象更改其顶部的对象也不是一件好事(我描述了一些基本的编程/ OOP原理:DIP(依赖关系反转原理:高级模块一定不能依赖于低级模块,但是它们应该取决于抽象)) )。

如果任何对象将更改此层次结构中的任何对象,则迟早代码中将出现混乱。在小型项目上可能没问题,但在位项目上挖掘这些混乱就没意思了。

UPD 2015年7月18日

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

我习惯于UINavigationController在我的应用程序中展示所有控制器。最初,我在导航堆栈中使用纯推/弹出动画显示登录视图控制器。然后,我决定以最少的更改将其更改为模式。

怎么运行的:

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

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

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

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

测试项目在这里


3
就我个人而言,我对View Controllers毫无疑问AppDelegate(我很想了解您为什么这么做)-但是您对缺少动画的评论非常有效。:可以通过这个答案要解决stackoverflow.com/questions/8053832/...
HughHughTeotl

2
@HughHughTeotl感谢您的评论和链接。我更新了答案。
derpoliuk

1
@derpoliuk如果我的基本视图控制器是UITabBarController怎么办?我不能在UINavigationController中推送它。
Giorgio

@ Giorgio,这是一个有趣的问题,我已经UITabBarController很长时间没有使用了。我可能会从窗口方法开始,而不是操纵视图控制器。
derpoliuk

11

这是我为未来任何围观者准备的Swifty解决方案。

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!


不知道我是不是很傻,但是AppDelegate不符合LoginFlowHandler。我想念什么吗?另外,我猜想这段代码仅在启动时管理登录。如何管理从视图控制器注销?
路加福音

@luke,因为所有逻辑都在扩展中实现,因此无需在AppDelegate中实现它。协议扩展是如此出色。
shannoga

1
抱歉@sirFunkenstine,这是我创建的一个自定义类,以显示一个示例,说明如何检查其应用程序缓存以检查用户以前是否登录。AppState因此,此实现将取决于您如何将用户数据保存到磁盘。
哈里·布鲁姆

@HarryBloom将如何使用该handleLogout功能?
nithinisreddy

1
@nithinisreddy,您好:要调用handleLogout功能,您需要将要从其调用的类与LoginFlowHandler协议进行兼容。然后,您将获得可以调用handleLogout方法的范围。有关如何对AppDelegate类执行此操作的示例,请参见步骤3。
哈里·布鲁姆

8

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


2
视图是否应该出现在“ viewDidAppear”中(用户可见)?这仍然会产生闪烁。
Mark13426 '16

2
没有答案。并且“将bool密钥存储在NSUserDefaults中以查看它是否是第一次启动。”对于这种数据非常危险。
skywinder

6

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

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

然后,我更喜欢创建Constant结构:

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
}

最后创建Switcher类:

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

    }

}

就这些!


故事板中最初使用哪种视图控制器有什么区别?在添加的照片中,我可以看到您在选项卡栏控制器上选中了“是初始视图控制器”选项。在AppDelegate中,您可以切换主根视图控制器,所以我想这没关系,对吗?
ShadeToD

@iAleksandr请更新iOS 13的答案。SceneDelegate的当前答案无效。
Nitesh

5

在Xcode 7中,您可以有多个StoryBoards。如果您可以将Login流保留在单独的情节提要中会更好。

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

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

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

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

3

我用它来检查首次启动:

- (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(太慢了),现在使用NSArrays和NSDictionaries管理我的数据。注销仅意味着清空那些数组和字典。另外,我确保在viewWillAppear中设置数据。

而已。


0

我处在与您相同的情况下,发现清理数据的解决方案是删除视图控制器所依赖的所有CoreData内容以绘制其信息。但是我仍然发现这种方法非常糟糕,我认为,无需情节提要和仅使用代码来管理视图控制器之间的过渡,就可以实现一种更为优雅的方法。

我在Github上发现了这个项目该项目仅通过代码即可完成所有这些工作,而且很容易理解。他们使用类似于Facebook的侧面菜单,并且根据用户是否登录来更改中心视图控制器。当用户注销时,appDelegate将从CoreData中删除数据,然后将主视图控制器再次设置为登录屏幕。


0

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

我的应用中有三个情节提要。

  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情节提要,并将其替换为登录情节提要。

从登录情节提要中,当用户成功登录后,我将用户数据保存到“用户默认值”,并初始化UserData单例以访问用户详细信息。然后,使用导航器方法加载Tab栏情节提要。

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单例并显示选项卡栏,而不是登录屏幕。


-1

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

嗨,Eli。您回答的问题已经有几个非常好的答案。当您决定回答此类问题时,请确保解释为什么您的答案比已经发布的非常好的答案更好。
Noel Widmer

嗨,诺埃尔。我注意到其他答案很快。但是我认为答案不是很完整。因此,我提交了有关swift3版本的答案。这将对新的快速程序员有所帮助。谢谢!@Noel Widmer。
王阳

您可以在帖子顶部添加该说明吗?这样,每个人都可以立即看到您的答案的好处。祝您玩得开心!:)
Noel Widmer

1
谢谢您的建议。我已添加了解释。再次感谢。@ Noel Widmer。
王阳

含糊不清的解决方案,不突出使用“ Common”关键字。
萨马里

-3

在此处输入图片说明

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

}

在视图控制器 中加载

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

为什么需要在文件ViewController.m中添加一些功能?
Eesha

@Eesha他在TabBar中添加了一个“注销” TabBar按钮项。我猜图像丢失了,否则您可能已经看过了。
helloWorld

存储登录密钥NSUserDefaults对于这种数据非常不安全!
skywinder
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.