Лучшие практики для экрана входа в 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]];
         }];
    }

Тестовый проект здесь .

derpoliuk
источник
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чтобы увидеть, запускается ли он впервые.

Mihado
источник
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

    }

}

Вот и все!

iAleksandr
источник
Есть ли разница, какой контроллер представления является начальным в раскадровке? В добавленной вами фотографии я вижу, что у вас есть опция "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 и снова устанавливает контроллер основного вида на экран входа в систему.

AMB
источник
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 и показывает панель вкладок вместо экрана входа в систему.

Jithin
источник
-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];
});}
user5575941
источник
Почему возникает необходимость добавить некоторые функции в файл ViewController.m ??
Еша
@Eesha Он добавил элемент кнопки TabBar «Выйти» в TabBar. Я думаю, что изображение отсутствует, иначе вы могли бы его увидеть.
Helloworld
Хранить ключ входа в систему NSUserDefaultsочень-очень небезопасно для такого рода данных!
Skywinder