我正在使用Storyboard构建一个iOS应用程序。根视图控制器是一个标签栏控制器。我正在创建登录/注销过程,它基本上工作正常,但我有一些问题。我需要知道最好的方法来设置这一切。
我想做到以下几点:
在应用程序第一次启动时显示登录屏幕。当他们登录时,转到标签栏控制器的第一个标签。
任何时候他们启动应用程序之后,检查他们是否登录,并直接跳到根标签栏控制器的第一个标签。
当他们手动单击登出按钮时,显示登录屏幕,并清除视图控制器中的所有数据。
到目前为止,我所做的是将根视图控制器设置为标签栏控制器,并创建了一个自定义segue到Login视图控制器。在我的标签栏控制器类中,我检查他们是否在viewDidAppear方法中登录,并执行segue: [self performSegueWithIdentifier:@"pushLogin" sender:self];
我还设置了一个通知,当注销操作需要执行:[[NSNotificationCenter defaultCenter] addObserver:自我选择器:@选择器(logoutAccount)名称:@“logoutAccount”对象:nil];
注销后,我从Keychain中清除凭据,运行[self setSelectedIndex:0],并执行segue再次显示登录视图控制器。
这一切都很好,但我想知道:这个逻辑应该在AppDelegate中吗?我还有两个问题:
他们第一次启动应用程序时,标签栏控制器在segue执行之前简要显示。我已经尝试移动代码到viewWillAppear,但segue不会工作那么早。
注销时,所有数据仍在所有视图控制器中。如果他们登录到一个新帐户,旧帐户数据仍然显示,直到他们刷新。我需要一种方法来清除这很容易登出。
我愿意重新修改。我考虑过让登录屏幕成为根视图控制器,或者在AppDelegate中创建一个导航控制器来处理所有事情…我只是不确定目前最好的方法是什么。
创建LoginViewController和TabBarController之后,我们需要分别添加一个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
}
}
就这些!
我用这个来检查第一次发射:
- (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中设置我的数据。
就是这样。
更新Xcode 11的@iAleksandr answer,因为Scene kit会导致问题。
取代
let appDelegate = UIApplication.shared.delegate as!应用委托
appDelegate.window?.rootViewController = rootViewController
With
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,let sceneDelegate = windowScene.delegate as? SceneDelegate else {
return
}
sceneDelegate.window?.rootViewController = rootViewController
打电话给换衣者。updateRootViewcontroller在场景委托而不是应用委托,像这样:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene. connectionOptions)
{
Switcher.updateRootViewController ()
警卫让_ =(场景为?UIWindowScene) else {return}
}
创建LoginViewController和TabBarController之后,我们需要分别添加一个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
}
}
就这些!