Práticas recomendadas para a tela de login do Storyboard, lidando com a limpeza de dados após o logout


290

Estou criando um aplicativo iOS usando um Storyboard. O controlador de visualização raiz é um controlador de barra de guias. Estou criando o processo de login / logout, e está funcionando muito bem, mas tenho alguns problemas. Eu preciso saber a melhor maneira de configurar tudo isso.

Quero realizar o seguinte:

  1. Mostra uma tela de login na primeira vez que o aplicativo é iniciado. Quando eles fizerem login, vá para a primeira guia do Controlador da barra de guias.
  2. Sempre que iniciarem o aplicativo, verifique se estão logados e pule diretamente para a primeira guia do Controlador de barras de guias raiz.
  3. Quando eles clicam manualmente em um botão de logout, mostram a tela de logon e limpe todos os dados dos controladores de exibição.

O que eu fiz até agora foi definir o controlador de exibição raiz como Controlador de barra de guias e criar um segue personalizado para o meu controlador de exibição de login. Dentro da minha classe Tab Bar Controller, verifico se eles estão logados dentro do viewDidAppearmétodo e faço o seguinte:[self performSegueWithIdentifier:@"pushLogin" sender:self];

Também configurei uma notificação para quando a ação de logout precisa ser executada: [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(logoutAccount) name:@"logoutAccount" object:nil];

Após o logout, limpo as credenciais do Keychain, execute [self setSelectedIndex:0]e execute as instruções para mostrar o controlador de exibição de login novamente.

Isso tudo funciona bem, mas estou me perguntando: essa lógica deve estar no AppDelegate? Eu também tenho dois problemas:

  • Na primeira vez em que eles iniciam o aplicativo , o Tab Bar Controller é exibido brevemente antes de a execução ser realizada. Eu tentei mover o código para, viewWillAppearmas o seguinte não funcionará tão cedo.
  • Quando eles saem, todos os dados ainda estão dentro de todos os controladores de exibição. Se eles fizerem login em uma nova conta, os dados antigos da conta ainda serão exibidos até que sejam atualizados. Preciso de uma maneira de limpar isso facilmente ao sair.

Estou aberto a refazer isso. Eu considerei tornar a tela de login o controlador de exibição raiz ou criar um controlador de navegação no AppDelegate para lidar com tudo ... Só não tenho certeza qual é o melhor método neste momento.


Você apresenta o controlador de visualização de login como modal?
vokilam

@TrevorGehman - pode adicionar uma foto no seu storyboard
rohan k shah

Enviei uma resposta com os detalhes do que acabei fazendo. É semelhante a algumas das outras respostas fornecidas, especialmente @bhavya kothari.
Trevor Gehman

Para apresentar a tela de login, o AuthNavigation pode ser útil. Ele organiza a apresentação de uma tela de login, se necessário, e também suporta o login automático.
Codey

Um dos problemas básicos que quase sempre é resolvido, mas ao mesmo tempo sente como poderia ter sido feito melhor
amar

Respostas:


311

Seu storyboard deve ficar assim

No appDelegate.m dentro do seu 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;
}

No arquivo SignUpViewController.m

- (IBAction)actionSignup:(id)sender
{
    AppDelegate *appDelegateTemp = [[UIApplication sharedApplication]delegate];

    appDelegateTemp.window.rootViewController = [[UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]] instantiateInitialViewController];
}

No arquivo 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;

}

Versão Swift 4

didFinishLaunchingWithOptions no delegado do aplicativo, assumindo que seu controlador de exibição inicial é o TabbarController assinado.

if Auth.auth().currentUser == nil {
        let rootController = UIStoryboard(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier: "WelcomeNavigation")
        self.window?.rootViewController = rootController
    }

    return true

Em Inscrever-se view controller:

@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

Você esqueceu de excluir a autenticação bool do userDefaults após o logout
CodeLover

28
-1 para usar AppDelegatedentro UIViewControllere definir window.rootViewControllerlá. Não considero isso uma "melhor prática".
Derpoliuk

2
Não queria dar -1sem postar uma resposta: stackoverflow.com/a/30664935/1226304
derpoliuk

1
Estou tentando fazer isso rapidamente no IOS8, mas recebo o seguinte erro quando o aplicativo é iniciado e a tela de login mostra: "Chamadas desequilibradas para iniciar / terminar as transições de aparência". Percebi que, quando o aplicativo carrega, a tela de login é exibida, mas também a primeira guia do controlador da barra de guias também está sendo carregada. Confirmado isso via println () em viewdidload. Sugestões?
Alex Lacayo

1
Bingo! -2. -1 para AppDelegatedentro UIViewController-1 para Armazenar a chave de login NSUserDefaults. É muito, muito inseguro para esse tipo de dados!
skywinder

97

Aqui está o que eu acabei fazendo para realizar tudo. A única coisa que você precisa considerar além disso é (a) o processo de login e (b) onde você está armazenando os dados do aplicativo (nesse caso, usei um singleton).

Storyboard mostrando o controlador de visualização de login e o controlador da guia principal

Como você pode ver, o controlador de visualização raiz é o meu Controlador de guias principais . Fiz isso porque depois que o usuário faz login, quero que o aplicativo seja iniciado diretamente na primeira guia. (Isso evita qualquer "tremulação" em que a visualização de login seja exibida temporariamente.)

AppDelegate.m

Neste arquivo, verifico se o usuário já está logado. Caso contrário, pressione o controlador de exibição de login. Também lido com o processo de logout, onde apago dados e mostro a visualização de logon.

- (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

Aqui, se o login for bem-sucedido, simplesmente rejeito a visualização e envio uma notificação.

-(void) loginWasSuccessful
{

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

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

}

2
Para que você usa a notificação?
rebelião

1
@BFeher está certa. Usei a notificação para acionar uma nova atração de dados. Você pode usá-lo para fazer o que quiser, mas, no meu caso, eu precisava ser notificado de que o login foi bem-sucedido e de novos dados.
Trevor Gehman

24
No iOS 8.1 (e talvez 8.0, não testei), isso não funciona mais sem problemas. O View Controller inicial pisca por um breve momento.
BFeher

7
Existe uma versão Swift dessa abordagem?
Seano

9
@Julian No iOS 8, substituo as duas linhas [self.window makeKeyAndVisible]; [self.window.rootViewController presentViewController:viewController animated:animated completion:nil];por self.window.rootViewController = viewController;para evitar tremulações. Para animar que apenas envolvê-la em um[UIView transitionWithView...];
BFeher

20

EDIT: Adicione ação de logout.

insira a descrição da imagem aqui

1. Primeiro, prepare o arquivo delegado do aplicativo

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. Crie uma classe chamada Usuário.

User.h

#import <Foundation/Foundation.h>

@interface User : NSObject

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

@end

Usuário.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. Crie um novo controlador RootViewController e conectado à primeira visualização, onde o botão de login está ativo. Adicione também um ID do Storyboard: "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. Crie um novo controlador LoginViewController e conectado à visualização de login.

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. No final, adicione um novo controlador ProfileViewController e conectado à visualização de perfil na guia ViewController.

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 é um projeto de amostra para ajuda extra.


3
projeto de exemplo me ajudou muito a entender o conceito de login do n Sair .. muito obrigado :)
Dave

16

Não gostei da resposta de bhavya por usar AppDelegatedentro dos Controladores de exibição e a configuração rootViewControllernão tem animação. E a resposta de Trevor tem problemas com o controlador de exibição intermitente no iOS8.

UPD 18/07/2015

AppDelegate dentro dos View Controllers:

Alterar o estado AppDelegate (propriedades) dentro do controlador de exibição interrompe o encapsulamento.

Hierarquia muito simples de objetos em todos os projetos iOS:

AppDelegate (possui windowe rootViewController)

ViewController (proprietário view)

Tudo bem que os objetos do topo alterem os objetos na parte inferior, porque os estão criando. Mas não está tudo bem se os objetos na parte inferior alteram os objetos em cima deles (descrevi alguns princípios básicos de programação / OOP: DIP (Princípio de Inversão da Dependência: o módulo de alto nível não deve depender do módulo de baixo nível, mas de abstrações) )

Se algum objeto alterar qualquer objeto nesta hierarquia, mais cedo ou mais tarde haverá uma confusão no código. Pode ser bom nos pequenos projetos, mas não é divertido vasculhar essa bagunça nos pequenos projetos =]

UPD 18/07/2015

Eu replico animações de controlador modal usando UINavigationController(tl; dr: verifique o projeto ).

Estou usando UINavigationControllerpara apresentar todos os controladores no meu aplicativo. Inicialmente, exibi o controlador de exibição de login na pilha de navegação com animação simples push / pop. Decidi alterá-lo para modal com o mínimo de alterações.

Como funciona:

  1. O controlador de exibição inicial (ou self.window.rootViewController) é UINavigationController com ProgressViewController como um rootViewController. Estou mostrando o ProgressViewController porque o DataModel pode levar algum tempo para inicializar porque ele possui uma pilha de dados principal, como neste artigo (eu realmente gosto dessa abordagem).

  2. O AppDelegate é responsável por obter atualizações de status de login.

  3. O DataModel manipula o login / logout do usuário e o AppDelegate está observando sua userLoggedInpropriedade via KVO. Indiscutivelmente, não é o melhor método para fazer isso, mas funciona para mim. (Por que o KVO é ruim, você pode fazer check-in neste ou neste artigo (parte Por que não usar notificações?).

  4. ModalDismissAnimator e ModalPresentAnimator são usados ​​para personalizar a animação por push padrão.

Como a lógica dos animadores funciona:

  1. AppDelegate se define como um delegado de self.window.rootViewController(que é UINavigationController).

  2. AppDelegate retorna um dos animadores, -[AppDelegate navigationController:animationControllerForOperation:fromViewController:toViewController:]se necessário.

  3. Animadores implementam -transitionDuration:e -animateTransition:métodos. -[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]];
         }];
    }

O projeto de teste está aqui .


3
Pessoalmente, não tenho nenhum problema com o View Controllers AppDelegate(eu estaria interessado em entender por que você faz isso) - mas seu comentário sobre a falta de animação é muito válido. Isso pode ser resolvido por esta resposta: stackoverflow.com/questions/8053832/...
HughHughTeotl

2
@HughHughTeotl Obrigado pelo comentário e pelo link. Eu atualizei minha resposta.
Derpoliuk

1
@derpoliuk e se o meu controlador de vista base for um UITabBarController? Não consigo enviá-lo em um UINavigationController.
Giorgio

@Giorgio, é uma pergunta interessante, não uso UITabBarControllerhá muito tempo. Eu provavelmente começaria com a abordagem de janela em vez de manipular os controladores de exibição.
21417 Derpoliuk

11

Aqui está a minha solução Swifty para futuros espectadores.

1) Crie um protocolo para lidar com as funções de login e logout:

protocol LoginFlowHandler {
    func handleLogin(withWindow window: UIWindow?)
    func handleLogout(withWindow window: UIWindow?)
}

2) Estenda o protocolo e forneça aqui a funcionalidade para efetuar logout:

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) Em seguida, posso adaptar meu AppDelegate ao protocolo LoginFlowHandler e chamar handleLoginna inicialização:

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
    }

}

A partir daqui, minha extensão de protocolo manipulará a lógica ou determinará se o usuário efetuou login / logout e alterará o rootViewController do Windows de acordo!


Não tenho certeza se estou sendo estúpido, mas o AppDelegate não está em conformidade LoginFlowHandler. Estou esquecendo de algo? Além disso, acho que esse código apenas gerencia o logon na inicialização. Como gerencio o logout de um controlador de exibição?
Lucas3

@luke, já que toda a lógica está implementada na extensão, não há necessidade de implementá-la no AppDelegate. Isso é tão bom em Extensões de Protocolo.
Shannoga

1
Desculpe, @sirFunkenstine, essa foi uma classe personalizada que eu criei para mostrar um exemplo de como verificar o cache de aplicativos para verificar se um usuário efetuou login ou não anteriormente. Essa AppStateimplementação, portanto, dependeria de como você está salvando seus dados do usuário em disco.
Harry Bloom

@HarryBloom, como alguém usaria a handleLogoutfuncionalidade?
nithinisreddy

1
Oi @nithinisreddy - para chamar a funcionalidade handleLogout, você precisará conformar a classe da qual está chamando no LoginFlowHandlerprotocolo. Então você terá escopo para poder chamar o método handleLogout. Veja minha etapa 3 para um exemplo de como eu fiz isso na classe AppDelegate.
Harry Bloom

8

Fazer isso com o delegado do aplicativo NÃO é recomendado. O AppDelegate gerencia o ciclo de vida do aplicativo relacionado ao lançamento, suspensão, encerramento e assim por diante. Sugiro fazer isso no seu controlador de exibição inicial no viewDidAppear. Você pode self.presentViewControllere self.dismissViewControllerno controlador de visualização de login. Armazene uma boolchave NSUserDefaultspara ver se está iniciando pela primeira vez.


2
A visualização deve aparecer (ser visível ao usuário) em `viewDidAppear '? Isso ainda criará uma tremulação.
precisa saber é o seguinte

2
Não é uma resposta. E "Armazenar uma chave bool no NSUserDefaults para ver se está sendo iniciado pela primeira vez" é muito muito perigoso para esse tipo de dados.
skywinder

6

Crie ** LoginViewController ** e ** TabBarController **.

Depois de criar o LoginViewController e o TabBarController , precisamos adicionar um StoryboardID como " loginViewController " e " tabBarController ", respectivamente.

Então eu prefiro criar a estrutura Constant :

struct Constants {
    struct StoryboardID {
        static let signInViewController = "SignInViewController"
        static let mainTabBarController = "MainTabBarController"
    }

    struct kUserDefaults {
        static let isSignIn = "isSignIn"
    }
}

No LoginViewController, adicione IBAction :

@IBAction func tapSignInButton(_ sender: UIButton) {
    UserDefaults.standard.set(true, forKey: Constants.kUserDefaults.isSignIn)
    Switcher.updateRootViewController()
}

No ProfileViewController, adicione IBAction :

@IBAction func tapSignOutButton(_ sender: UIButton) {
    UserDefaults.standard.set(false, forKey: Constants.kUserDefaults.isSignIn)
    Switcher.updateRootViewController()
}

No AppDelegate, adicione a linha de código em didFinishLaunchingWithOptions :

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

    Switcher.updateRootViewController()

    return true
}

Por fim, crie a classe 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

    }

}

Isso é tudo!


Existe alguma diferença em qual controlador de exibição é inicial nos storyboards? Na sua foto adicionada, posso ver que você tem a opção "is Initial View Controller" marcada no Tab Bar Controller. No AppDelegate, você alterna o controlador principal de visualização raiz, então acho que não importa, não é?
ShadeToD 24/05/19

@iAleksandr Atualize a resposta para o iOS 13. Porque a resposta atual do SceneDelegate não está funcionando.
Nitesh

5

No Xcode 7, você pode ter vários storyBoards. Será melhor se você puder manter o fluxo de Login em um storyboard separado.

Isso pode ser feito usando SELECT VIEWCONTROLLER> Editor> Refactor to Storyboard

E aqui está a versão Swift para definir uma exibição como o RootViewContoller-

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

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

3

Eu uso isso para verificar o primeiro lançamento:

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

(se o usuário excluir o aplicativo e reinstalá-lo, será considerado como um primeiro lançamento)

No AppDelegate, verifico o primeiro lançamento e crio um controlador de navegação com as telas de login (login e registro), que coloco no topo da janela principal atual:

[self.window makeKeyAndVisible];

if (firstLaunch == 1) {
    UINavigationController *_login = [[UINavigationController alloc] initWithRootViewController:loginController];
    [self.window.rootViewController presentViewController:_login animated:NO completion:nil];
}

Como ele está no topo do controlador de exibição comum, ele é independente do resto do seu aplicativo e você pode simplesmente ignorar o controlador de exibição, se não precisar mais dele. E você também pode apresentar a visualização dessa maneira, se o usuário pressionar um botão manualmente.

BTW: eu salvo os dados de login dos meus usuários assim:

KeychainItemWrapper *keychainItem = [[KeychainItemWrapper alloc] initWithIdentifier:@"com.youridentifier" accessGroup:nil];
[keychainItem setObject:password forKey:(__bridge id)(kSecValueData)];
[keychainItem setObject:email forKey:(__bridge id)(kSecAttrAccount)];

Para o logout: mudei do CoreData (muito lento) e usei o NSArrays e o NSDictionaries para gerenciar meus dados agora. Logout significa apenas esvaziar essas matrizes e dicionários. Além disso, certifique-se de definir meus dados no viewWillAppear.

É isso aí.


0

Estou na mesma situação que você e a solução que encontrei para limpar os dados está excluindo todo o material CoreData em que meus controladores de exibição se baseiam para desenhar suas informações. Mas ainda achei essa abordagem muito ruim, acho que uma maneira mais elegante de fazer isso pode ser realizada sem storyboards e usando apenas código para gerenciar as transições entre os controladores de exibição.

Encontrei este projeto no Github que faz tudo isso apenas por código e é bastante fácil de entender. Eles usam um menu lateral semelhante ao Facebook e o que fazem é alterar o controlador da visualização central, dependendo se o usuário está logado ou não. Quando o usuário efetua logout, appDelegateremove os dados do CoreData e configura o controlador de exibição principal para a tela de login novamente.


0

Eu tive um problema semelhante para resolver em um aplicativo e usei o seguinte método. Não usei notificações para lidar com a navegação.

Eu tenho três storyboards no aplicativo.

  1. Storyboard da tela inicial - para inicializar o aplicativo e verificar se o usuário já está logado
  2. Storyboard de login - para lidar com o fluxo de login do usuário
  3. Storyboard da barra de guias - para exibir o conteúdo do aplicativo

Meu storyboard inicial no aplicativo é o storyboard da tela Splash. Eu tenho o controlador de navegação como raiz do storyboard de login e barra de guias para lidar com as visualizações do controlador.

Eu criei uma classe Navigator para lidar com a navegação do aplicativo e fica assim:

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)

           }

       }

   }

}

Vejamos os cenários possíveis:

  • Primeiro lançamento do aplicativo; A tela inicial será carregada onde eu verifico se o usuário já está conectado. A tela de login será carregada usando a classe Navigator da seguinte forma;

Como tenho o controlador de navegação como raiz, instancio o controlador de navegação como controlador de exibição inicial.

let loginSB = UIStoryboard(name: "splash", bundle: nil)

let loginNav = loginSB.instantiateInitialViewcontroller() as! UINavigationController

Navigator.moveTo(loginNav, from: self)

Isso remove o storyboard slpash da raiz da janela do aplicativo e o substitui pelo storyboard de login.

No storyboard de login, quando o usuário faz login com êxito, eu salvo os dados do usuário em Padrões do Usuário e inicializo um singleton UserData para acessar os detalhes do usuário. O storyboard da barra de guias é carregado usando o método navigator.

Let tabBarSB = UIStoryboard(name: "tabBar", bundle: nil)
let tabBarNav = tabBarSB.instantiateInitialViewcontroller() as! UINavigationController

Navigator.moveTo(tabBarNav, from: self)

Agora o usuário sai da tela de configurações na barra de guias. Limpo todos os dados do usuário salvos e navego para a tela de login.

let loginSB = UIStoryboard(name: "splash", bundle: nil)

let loginNav = loginSB.instantiateInitialViewcontroller() as! UINavigationController

Navigator.moveTo(loginNav, from: self)
  • O usuário está logado e força mata o aplicativo

Quando o usuário inicia o aplicativo, a tela inicial é carregada. Verifico se o usuário está logado e acesso os dados do usuário em Padrões do usuário. Em seguida, inicialize o singleton UserData e mostre a barra de guias em vez da tela de login.


-1

Graças à solução de bhavya. Houve duas respostas sobre o swift, mas essas não estão muito intactas. Eu fiz isso no swift3.Below é o código principal.

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

Em 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")
}

Na função logOutAction

@IBAction func logOutAction(_ sender: UIButton) {
    UserDefaults.standard.setValue(false, forKey: Common.isLoginKey)
    UIApplication.shared.delegate?.window!?.rootViewController = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController()
}

Oi Eli. A pergunta que você respondeu já tem algumas respostas realmente boas. Quando você decidir responder a essa pergunta, explique por que sua resposta é melhor do que as muito boas que já foram publicadas.
28617 Noel Widmer

Oi Noel. Eu notei as outras respostas para rápido. Mas considerei que as respostas não estão muito intactas. Então, eu envio minha resposta sobre a versão swift3. É uma ajuda para o novo programador rápido. Obrigado! Noel Widmer.
21817 WangYang

Você pode adicionar essa explicação na parte superior da sua postagem? Dessa forma, todos poderão ver imediatamente o benefício de sua resposta. Tenha um bom tempo em SO! :)
Noel Widmer

1
Os comentários são de responsabilidade exclusiva de seus autores e não representam a opinião deste site.
Wangyang

Solução vaga que não destaca o uso da palavra-chave 'Comum'.
Samarey 27/12/19

-3

insira a descrição da imagem aqui

No aplicativo 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;

}

ver controller.m Em vista, o carregamento

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

}

Ação do botão Logout

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

Por que há a necessidade de adicionar alguma funcionalidade ao arquivo ViewController.m ??
Eesha

@Eesha Ele adicionou um item do botão TabBar "logout" ao TabBar. Eu acho que a imagem está faltando mais você poderia ter visto.
HelloWorld 5/05

A chave de login da loja NSUserDefaultsé muito insegura para esse tipo de dados!
skywinder
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.