ChatGPT解决这个技术问题 Extra ChatGPT

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

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

我想完成以下任务:

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

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

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

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

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

他们第一次启动应用程序时,标签栏控制器会在执行 segue 之前短暂显示。我已经尝试将代码移动到 viewWillAppear 但 segue 不会那么早工作。

当他们注销时,所有数据仍然在所有视图控制器中。如果他们登录到新帐户,则在刷新之前仍会显示旧帐户数据。我需要一种在注销时轻松清除此问题的方法。

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

您是否将登录视图控制器呈现为模态?
@TrevorGehman - 可以添加您的故事板图片
我提交了一份答案,详细说明了我最终做了什么。它类似于提供的其他一些答案,尤其是@bhavya kothari。
对于显示登录屏幕,AuthNavigation 可能很有用。如果需要,它会组织登录屏幕的显示,并且还支持自动登录。
几乎总能解决但同时感觉本可以做得更好的非常基本的问题之一

J
Joseph Francis

https://i.stack.imgur.com/KUKCP.png

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

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

}

斯威夫特 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

在注册视图控制器中:

@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 身份验证
-1 用于在 UIViewController 中使用 AppDelegate 并在那里设置 window.rootViewController。我不认为这是“最佳实践”。
不想在不发布答案的情况下给出 -1stackoverflow.com/a/30664935/1226304
我试图在 IOS8 上快速执行此操作,但是当应用程序启动并且登录屏幕显示时出现以下错误:“开始/结束外观转换的不平衡调用”。我注意到,当应用程序加载登录屏幕时,标签栏控制器上的第一个标签也被加载。通过 viewdidload 中的 println() 确认了这一点。建议?
答对了! -2。 -1 表示 UIViewController 中的 AppDelegate -1 表示将登录密钥存储在 NSUserDefaults 中。这种数据非常非常不安全!
T
Trevor Gehman

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

https://i.stack.imgur.com/rNQMA.png

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

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

}

登录视图控制器.m

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

-(void) loginWasSuccessful
{

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

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

}

你用这个通知做什么?
在 iOS 8.1(可能还有 8.0,尚未测试)中,这不再顺利运行。初始视图控制器会闪烁片刻。
这种方法有 Swift 版本吗?
@Seano 是的。将您在上面看到的代码翻译成不同的语法。 API 完全相同。没有区别。
@Julian 在 iOS 8 中,我将两行 [self.window makeKeyAndVisible]; [self.window.rootViewController presentViewController:viewController animated:animated completion:nil]; 替换为 self.window.rootViewController = viewController; 以防止闪烁。要制作动画,只需将其包装在 [UIView transitionWithView...];
D
Dimitris Bouzikas

编辑:添加注销操作。

https://i.stack.imgur.com/ZLlkk.jpg

1.首先准备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;
}

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

用户.h

#import <Foundation/Foundation.h>

@interface User : NSObject

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

@end

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

根视图控制器.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并与登录视图连接。

登录视图控制器.h

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

@interface LoginViewController : UIViewController

登录视图控制器.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 是提供额外帮助的示例项目。


示例项目非常有助于我理解登录和注销的概念.. 非常感谢 :)
S
Souhaib Guitouni

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

更新日期 2015 年 7 月 18 日

视图控制器内的 AppDelegate:

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

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

AppDelegate(拥有 windowrootViewController

ViewController(拥有 view

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

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

更新日期 2015 年 7 月 18 日

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

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

这个怎么运作:

初始视图控制器(或 self.window.rootViewController)是 UINavigationController,其中 ProgressViewController 作为 rootViewController。我展示 ProgressViewController 是因为 DataModel 可能需要一些时间来初始化,因为它像本文一样初始化核心数据堆栈(我真的很喜欢这种方法)。 AppDelegate 负责获取登录状态更新。 DataModel 处理用户登录/注销,AppDelegate 通过 KVO 观察它的 userLoggedIn 属性。可以说这不是最好的方法,但它对我有用。 (为什么 KVO 不好,可以查看这篇或者这篇文章(Why Not Use Notifications? 部分)。ModalDismissAnimator 和 ModalPresentAnimator 用于自定义默认推送动画。

动画师逻辑如何工作:

AppDelegate 将自己设置为 self.window.rootViewController(即 UINavigationController)的委托。如有必要,AppDelegate 返回 -[AppDelegate navigationController:animationControllerForOperation:fromViewController:toViewController:] 中的动画器之一。动画师实现 -transitionDuration: 和 -animateTransition: 方法。 -[ModalPresentAnimator animateTransition:]: - (void)animateTransition:(id)transitionContext { UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; [[transitionContext containerView] addSubview:toViewController.view]; CGRect frame = toViewController.view.frame; CGRect toFrame = 框架; frame.origin.y = CGRectGetHeight(frame); toViewController.view.frame = 框架; [UIView animateWithDuration:[self transitionDuration:transitionContext] 动画:^ { toViewController.view.frame = toFrame; } 完成:^(BOOL 完成) { [transitionContext completeTransition:![transitionContext transitionWasCancelled]]; }]; }

测试项目是 here


就我个人而言,我对 View Controller 了解 AppDelegate 没有任何问题(我很想了解您为什么这样做) - 但您对缺乏动画的评论非常有效。这可以通过这个答案来解决:stackoverflow.com/questions/8053832/…
@HughHughTeotl 感谢您的评论和链接。我更新了我的答案。
@derpoliuk 如果我的基本视图控制器是 UITabBarController 怎么办?我无法将它推送到 UINavigationController 中。
@Giorgio,这是一个有趣的问题,我很长时间没有使用 UITabBarController 了。我可能会从 window approach 开始,而不是操作视图控制器。
p
pkamb

这是我为任何未来的旁观者准备的 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 中实现它。这就是协议扩展的伟大之处。
抱歉@sirFunkenstine,这是我创建的一个自定义类,用于展示如何检查他们的应用程序缓存以检查用户以前是否登录过。因此,此 AppState 实施将取决于您如何将用户数据保存到磁盘。
@HarryBloom 如何使用 handleLogout 功能?
嗨@nithinisreddy - 要调用 handleLogout 功能,您需要使您调用的类符合 LoginFlowHandler 协议。然后您将获得能够调用 handleLogout 方法的范围。有关我如何为 AppDelegate 类执行此操作的示例,请参阅我的步骤 3。
M
Mihado

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


视图是否应该出现在“viewDidAppear”中(对用户可见)?这仍然会产生闪烁。
不是答案。并且“在 NSUserDefaults 中存储一个 bool 键以查看它是否是第一次启动。”对于这种数据来说是非常非常危险的。
i
iAleksandr

https://i.stack.imgur.com/4TMu3.png

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

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

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 中,你切换主根视图控制器,所以我想没关系,是吗?
@iAleksandr 请更新 iOS 13 的答案。因为 SceneDelegate 当前的答案不起作用。
嘿哥们儿。当用户点击注册时,您的代码不起作用。请也添加此功能..
M
Mahbub Morshed

在 Xcode 7 中,您可以拥有多个故事板。如果您可以将登录流程保留在单独的情节提要中,那就更好了。

这可以使用 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")

T
Thorsten

我用它来检查首次启动:

- (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 中设置我的数据。

而已。


N
Naresh

更新 Xcode 11 的 @iAleksandr 答案,这会导致由于场景套件而出现问题。

代替

让 appDelegate = UIApplication.shared.delegate 为! 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

在 Scene 委托而不是 App 委托中调用 Switcher.updateRootViewcontroller,如下所示: ) 否则 { 返回 } }


a
amb

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

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


4
4b0

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

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

启动画面故事板 - 用于应用程序初始化和检查用户是否已登录 登录故事板 - 用于处理用户登录流程 标签栏故事板 - 用于显示应用程序内容

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

我创建了一个 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 单例以访问用户详细信息。然后使用导航器方法加载选项卡栏故事板。

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 单例并显示标签栏而不是登录屏幕。


W
WangYang

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

嗨,伊莱。您回答的问题已经有几个非常好的答案。当您决定回答这样的问题时,请务必解释为什么您的答案比已经发布的非常好的答案更好。
嗨,诺埃尔。我注意到 swift 的其他答案。但我认为答案不是很完整。所以我提交了关于 swift3 版本的答案。这将对新的 swift 程序员有所帮助。谢谢!@Noel Widmer。
您可以在帖子顶部添加该解释吗?这样,每个人都可以立即看到您的答案的好处。祝你玩得开心! :)
您的建议的坦克。我已经添加了解释。再次感谢。@Noel Widmer。
不突出使用“通用”关键字的模糊解决方案。
u
user5575941

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;

}

视图控制器.m 在视图中确实加载了

- (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他在TabBar中添加了一个“注销”TabBar按钮项。我猜图像丢失了,否则你可能已经看到了。
将登录密钥存储在 NSUserDefaults 中对于此类数据非常非常不安全!