Лучшие практики для экрана входа в Storyboard, обработка очистки данных при выходе


290

Я создаю приложение для iOS, используя раскадровку. Корневым контроллером представления является контроллер панели вкладок. Я создаю процесс входа / выхода, и он в основном работает нормально, но у меня есть несколько проблем. Мне нужно знать ЛУЧШИЙ способ настроить все это.

Я хочу сделать следующее:

  1. Показать экран входа в систему при первом запуске приложения. Когда они войдут, перейдите на первую вкладку контроллера панели вкладок.
  2. Каждый раз, когда они запускают приложение после этого, проверьте, вошли ли они в систему, и сразу перейдите к первой вкладке корневого контроллера панели вкладок.
  3. Когда они вручную нажимают кнопку выхода из системы, показывают экран входа в систему и очищают все данные от контроллеров представления.

До сих пор я устанавливал корневой контроллер представления на контроллер панели вкладок и создавал собственный переход к моему контроллеру представления входа в систему. Внутри моего класса Tab Bar Controller я проверяю, вошли ли они внутрь viewDidAppearметода, и выполняю следующую последовательность действий:[self performSegueWithIdentifier:@"pushLogin" sender:self];

Я также настраиваю уведомление о том, когда необходимо выполнить действие выхода из системы: [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(logoutAccount) name:@"logoutAccount" object:nil];

После выхода из системы я очищаю учетные данные из цепочки для ключей, запускаю [self setSelectedIndex:0]и выполняю процедуру, чтобы снова показать контроллер вида входа в систему.

Все это прекрасно работает, но мне интересно: должна ли эта логика быть в AppDelegate?У меня также есть две проблемы:

  • Первый раз, когда они запускают приложение , контроллер панели вкладок кратко показывает перед выполнением перехода. Я пытался переместить код в, viewWillAppearно переход не будет работать так рано.
  • Когда они выходят из системы, все данные все еще находятся внутри всех контроллеров представления. Если они входят в новую учетную запись, данные старой учетной записи все еще отображаются, пока они не обновятся.Мне нужен способ, чтобы очистить это легко при выходе из системы.

Я открыт для переделки этого. Я подумал о том, чтобы сделать экран входа в систему корневым контроллером представления или создать контроллер навигации в AppDelegate для обработки всего ... Я просто не уверен, какой метод на данный момент лучший.


Вы представляете контроллер вида входа в систему как модальный?
— Вокилам

@TrevorGehman - можете добавить свою раскадровку
— rohan k shah

Я представил ответ с подробностями того, что я в итоге делал. Это похоже на некоторые другие ответы, особенно @bhavya kothari.
— Тревор Гехман

Для представления экрана входа в систему может быть полезна AuthNavigation . Он организует представление экрана входа в систему при необходимости, а также поддерживает автоматический вход в систему.
— Коди

Одна из самых основных проблем, которая почти всегда решается, но в то же время кажется, что это можно было бы сделать лучше
— Amar

Ответы:


311

Ваша раскадровка должна выглядеть так

В вашем 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;

}

Версия 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

В контроллере регистрации:

@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

Вы забыли удалить аутентификацию bool из userDefaults после выхода из системы
— CodeLover

28
-1 для использования AppDelegateвнутри UIViewControllerи установки window.rootViewControllerтам. Я не считаю это «лучшей практикой».
— Дерполюк

2
Не хотел давать -1без публикации ответа: stackoverflow.com/a/30664935/1226304
— дерполюк

1
Я пытаюсь сделать это в быстром на IOS8, но я получаю следующую ошибку, когда приложение запускается и на экране входа в систему отображается: «Несбалансированные вызовы, чтобы начать / закончить переходы появления». Я заметил, что когда приложение загружается, отображается экран входа в систему, но также загружается и первая вкладка на контроллере панели вкладок. Подтвердил это с помощью println () в viewdidload. Предложения?
— Алекс Лакайо

1
лото! -2. -1 для AppDelegateвнутренней UIViewController-1 для хранения ключа входа в NSUserDefaults. Это очень-очень небезопасно для такого рода данных!
— Skywinder

97

Вот что я в итоге сделал, чтобы достичь всего. Единственное, что вам нужно учитывать в дополнение к этому, это (а) процесс входа в систему и (б) где вы храните данные своего приложения (в данном случае я использовал синглтон).

Раскадровка, показывающая контроллер вида входа в систему и контроллер главной вкладки

Как вы можете видеть, контроллер корневого представления - это мой контроллер основной вкладки . Я сделал это, потому что после того, как пользователь вошел в систему, я хочу, чтобы приложение запускалось непосредственно на первой вкладке. (Это позволяет избежать «мерцания», когда временно отображается вид входа в систему.)

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
Для чего вы используете уведомление?
— восстание

1
@BFeher прав. Я использовал уведомление, чтобы вызвать новое извлечение данных. Вы можете использовать его, чтобы делать все, что вы хотите, но в моем случае мне нужно было уведомить, что вход в систему прошел успешно, и нужны были свежие данные.
— Тревор Гехман

24
В iOS 8.1 (и, возможно, 8.0, не проверял) это больше не работает гладко. Начальный View Controller мигает на короткое время.
— BFeher

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

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.

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

3. Создайте новый контроллер RootViewController и соедините с первым видом, где живут кнопки входа. Добавьте также идентификатор раскадровки: «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 и подключите его к представлению профиля во вкладке ViewViewController.

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
Пример проекта очень помог мне понять концепцию входа и выхода из системы .. большое спасибо :)
— Дейв

16

Мне не понравился ответ Бхавьи из-за использования AppDelegateвнутри View Controllers и настройки rootViewControllerне имеет анимации. И у ответа Тревора есть проблема с мигающим контроллером представления на iOS8.

UPD 18.07.2015

AppDelegate внутри View Controllers:

Изменение состояния (свойств) AppDelegate внутри контроллера представления нарушает инкапсуляцию.

Очень простая иерархия объектов в каждом проекте iOS:

AppDelegate (владеет windowи rootViewController)

ViewController (владеет view)

Это нормально, что объекты сверху меняют объекты снизу, потому что они их создают. Но это не нормально, если объекты в нижней части изменяют объекты поверх них (я описал некоторый базовый принцип программирования / ООП: DIP (Принцип инверсии зависимости: модуль высокого уровня не должен зависеть от модуля низкого уровня, но они должны зависеть от абстракций) ).

Если какой-либо объект изменит какой-либо объект в этой иерархии, рано или поздно в коде будет беспорядок. Это может быть хорошо на небольших проектах, но не интересно копаться в этом беспорядке на битовых проектах =]

UPD 18.07.2015

Я копирую анимацию модального контроллера, используя UINavigationController(tl; dr: check the project ).

Я использую, UINavigationControllerчтобы представить все контроллеры в моем приложении. Изначально я отображал контроллер вида входа в стек навигации с простой анимацией push / pop. Затем я решил изменить его на модальный с минимальными изменениями.

Как это устроено:

  1. Начальный контроллер представления (или self.window.rootViewController) - UINavigationController с ProgressViewController как rootViewController. Я показываю ProgressViewController, потому что DataModel может занять некоторое время для инициализации, потому что он встраивает основной стек данных, как в этой статье (мне действительно нравится этот подход).

  2. AppDelegate отвечает за получение обновлений статуса входа в систему.

  3. DataModel обрабатывает вход / выход пользователя, а AppDelegate наблюдает за его userLoggedInсвойством через KVO. Возможно, не лучший способ сделать это, но он работает для меня. (Почему KVO плохо, вы можете проверить в этой или этой статье (Почему не использовать уведомления? Часть).

  4. ModalDismissAnimator и ModalPresentAnimator используются для настройки push-анимации по умолчанию.

Как работает логика аниматоров:

  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 Спасибо за комментарий и за ссылку. Я обновил свой ответ.
— дерполюк

1
@derpoliuk, что если мой базовый контроллер представления является UITabBarController? Я не могу выдвинуть это в UINavigationController.
— Джорджио

@ Джорджио, это интересный вопрос, я давно не пользовался UITabBarController. Я бы, вероятно, начал с оконного подхода вместо манипулирования контроллерами представления.
— Дерполюк

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
    }

}

Отсюда мое расширение протокола будет обрабатывать логику или определять, будет ли пользователь входить / выходить, а затем соответствующим образом изменять окна rootViewController!


Не уверен, что я глуп, но AppDelegate не соответствует LoginFlowHandler. Я что-то упускаю? Кроме того, я предполагаю, что этот код управляет входом только при запуске. Как мне управлять выходом из контроллера вида?
— Лука

@luke, поскольку вся логика реализована в расширении, нет необходимости реализовывать ее в AppDelegate. Вот что так здорово в расширениях протокола.
— Шаннога

1
Извините @sirFunkenstine, это был пользовательский класс, который я создал, чтобы показать пример того, как можно проверить кеш приложения, чтобы проверить, вошел ли пользователь ранее в систему или нет. AppStateСледовательно, эта реализация будет зависеть от того, как вы сохраняете свои пользовательские данные на диск.
— Гарри Блум

@HarryBloom, как использовать handleLogoutфункциональность?
— nithinisreddy

1
Привет @nithinisreddy - чтобы вызвать функцию handleLogout, вам нужно будет согласовать класс, из которого вы вызываете, с LoginFlowHandlerпротоколом. После этого вы получите возможность вызова метода handleLogout. Смотрите мой шаг 3 для примера того, как я это сделал для класса AppDelegate.
— Гарри Блум

8

Делать это из приложения делегата НЕ рекомендуется. AppDelegate управляет жизненным циклом приложения, связанным с запуском, приостановкой, завершением и т. Д. Я предлагаю сделать это с вашего начального контроллера просмотра в viewDidAppear. Можно self.presentViewControllerи self.dismissViewControllerиз логина просмотра контроллера. Сохраните boolключ, NSUserDefaultsчтобы увидеть, запускается ли он впервые.


2
Должно ли представление появиться (быть видимым для пользователя) в `viewDidAppear '? Это все равно создаст мерцание.
— Mark13426

2
Не ответ И «Хранить ключ bool в NSUserDefaults, чтобы увидеть, запускается ли он впервые.» Очень и очень опасно для такого рода данных.
— Skywinder

6

Создайте ** LoginViewController ** и ** TabBarController **.

После создания 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

    }

}

Вот и все!


Есть ли разница, какой контроллер представления является начальным в раскадровке? В добавленной вами фотографии я вижу, что у вас есть опция "is Initial View Controller", отмеченная на панели вкладок Controller. В AppDelegate вы переключаете основной контроллер корневого представления, так что я думаю, это не имеет значения, не так ли?
— ShadeToD

@iAleksandr Пожалуйста, обновите ответ для iOS 13. Coz of SceneDelegate текущий ответ не работает.
— Нитеш

5

В Xcode 7 вы можете иметь несколько storyBoards. Будет лучше, если вы сможете сохранить поток входа в систему в отдельной раскадровке.

Это можно сделать с помощью 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 из корня окна приложения и заменяет ее раскадровкой входа в систему.

Из раскадровки входа в систему, когда пользователь успешно вошел в систему, я сохраняю данные пользователя в User Defaults и инициализирую синглтон 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 и показывает панель вкладок вместо экрана входа в систему.


-1

Спасибо бхавья за решение. Было два ответа о Свифте, но они не очень целы. Я сделал это в 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.
— WangYang

Можете ли вы добавить это объяснение в верхней части вашего сообщения? Таким образом, каждый может сразу увидеть пользу вашего ответа. Хорошо провести время на ТАК! :)
— Ноэль Видмер

1
Баки для вашего предложения. Я добавил объяснение. Спасибо еще раз. @ Ноэль Видмер.
— WangYang

Расплывчатое решение, которое не выделяет использование ключевого слова Common.
— Самаре

-3

введите описание изображения здесь

В приложении 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;

}

view controller.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. Я думаю, что изображение отсутствует, иначе вы могли бы его увидеть.
— Helloworld

Хранить ключ входа в систему NSUserDefaultsочень-очень небезопасно для такого рода данных!
— Skywinder
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.