Mejores prácticas para la pantalla de inicio de sesión de Storyboard, manejo del borrado de datos al cerrar sesión

290

Estoy creando una aplicación para iOS usando un Storyboard. El controlador de vista raíz es un controlador de barra de pestañas. Estoy creando el proceso de inicio de sesión / cierre de sesión, y en su mayoría funciona bien, pero tengo algunos problemas. Necesito saber la MEJOR forma de configurar todo esto.

Quiero lograr lo siguiente:

  1. Mostrar una pantalla de inicio de sesión la primera vez que se inicia la aplicación. Cuando inicien sesión, vaya a la primera pestaña del Controlador de barra de pestañas.
  2. Cada vez que inicien la aplicación después de eso, verifique si han iniciado sesión y salte directamente a la primera pestaña del Controlador de barra de pestañas raíz.
  3. Cuando hacen clic manualmente en un botón de cierre de sesión, muestran la pantalla de inicio de sesión y borran todos los datos de los controladores de vista.

Lo que he hecho hasta ahora es configurar el controlador de vista raíz en el Controlador de barra de pestañas y crear un seguimiento personalizado para mi controlador de vista de inicio de sesión. Dentro de mi clase Tab Bar Controller, verifico si están conectados dentro del viewDidAppearmétodo y realizo el seguimiento:[self performSegueWithIdentifier:@"pushLogin" sender:self];

También configuré una notificación para cuando la acción de cierre de sesión debe realizarse: [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(logoutAccount) name:@"logoutAccount" object:nil];

Al cerrar sesión, borro las credenciales del llavero, ejecuto [self setSelectedIndex:0]y realizo el seguimiento para mostrar nuevamente el controlador de vista de inicio de sesión.

Todo esto funciona bien, pero me pregunto: ¿ debería esta lógica estar en el AppDelegate? También tengo dos problemas:

  • La primera vez que inician la aplicación , el Controlador de barra de pestañas se muestra brevemente antes de realizar el seguimiento. He intentado mover el código viewWillAppearpero el segue no funcionará tan temprano.
  • Cuando cierran la sesión, todos los datos todavía están dentro de todos los controladores de vista. Si inician sesión en una cuenta nueva, los datos de la cuenta anterior todavía se muestran hasta que se actualizan. Necesito una manera de borrar esto fácilmente al cerrar sesión.

Estoy abierto a reelaborar esto. He considerado hacer que la pantalla de inicio de sesión sea el controlador de vista raíz, o crear un controlador de navegación en AppDelegate para manejar todo ... Simplemente no estoy seguro de cuál es el mejor método en este momento.

Trevor Gehman
fuente
¿Presentan el controlador de vista de inicio de sesión como modal?
vokilam
@TrevorGehman - puede agregar su foto del guión gráfico
rohan k shah
Envié una respuesta con los detalles de lo que terminé haciendo. Es similar a algunas de las otras respuestas proporcionadas, especialmente @bhavya kothari.
Trevor Gehman
Para presentar la pantalla de inicio de sesión, AuthNavigation puede ser útil. Organiza la presentación de una pantalla de inicio de sesión si es necesario y también admite el inicio de sesión automático.
Codey
Uno de los problemas más básicos, que casi siempre se resuelve, pero al mismo tiempo parece que podría haberse hecho mejor
amar

Respuestas:

311

Tu guión gráfico debería verse así

En su appDelegate.m dentro de 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;
}

En el archivo SignUpViewController.m

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

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

En el archivo 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;

}

Versión Swift 4

didFinishLaunchingWithOptions en el delegado de la aplicación, suponiendo que su controlador de vista inicial sea el TabbarController firmado.

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

    return true

En el controlador de vista de registro:

@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
bhavya kothari
fuente
Olvidó eliminar la autenticación bool de los valores predeterminados del usuario después de cerrar sesión
CodeLover
28
-1 para usar AppDelegatedentro UIViewControllery configurar window.rootViewControllerallí. No considero esto como una "mejor práctica".
derpoliuk
2
No quería dar -1sin publicar una respuesta: stackoverflow.com/a/30664935/1226304
derpoliuk
1
Estoy tratando de hacer esto rápidamente en IOS8, pero aparece el siguiente error cuando se inicia la aplicación y la pantalla de inicio de sesión muestra: "Llamadas no balanceadas para iniciar / finalizar transiciones de apariencia". Me di cuenta de que cuando la aplicación se carga, se muestra la pantalla de inicio de sesión, pero también se carga la primera pestaña del controlador de la barra de pestañas. Confirmado esto a través de println () en viewdidload. Sugerencias?
Alex Lacayo
1
¡bingo! -2. -1 para AppDelegatedentro UIViewController-1 para almacenar la clave de inicio de sesión NSUserDefaults. ¡Es muy, muy inseguro para ese tipo de datos!
skywinder
97

Esto es lo que terminé haciendo para lograr todo. Lo único que debe tener en cuenta además de esto es (a) el proceso de inicio de sesión y (b) dónde está almacenando los datos de su aplicación (en este caso, utilicé un singleton).

Storyboard que muestra el controlador de vista de inicio de sesión y el controlador de la pestaña principal

Como puede ver, el controlador de vista raíz es mi controlador de pestaña principal . Hice esto porque después de que el usuario ha iniciado sesión, quiero que la aplicación se inicie directamente en la primera pestaña. (Esto evita cualquier "parpadeo" donde la vista de inicio de sesión se muestra temporalmente).

AppDelegate.m

En este archivo, verifico si el usuario ya inició sesión. Si no, presiono el controlador de vista de inicio de sesión. También manejo el proceso de cierre de sesión, donde borro los datos y muestro la vista de inicio de sesión.

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

Aquí, si el inicio de sesión es exitoso, simplemente descarto la vista y envío una notificación.

-(void) loginWasSuccessful
{

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

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

}
Trevor Gehman
fuente
2
¿Para qué utiliza la notificación?
rebelión
1
@BFeher tiene razón. Usé la notificación para desencadenar una nueva extracción de datos. Puede usarlo para hacer lo que quiera, pero en mi caso, necesitaba que me notificaran que el inicio de sesión fue exitoso y que se necesitaban datos nuevos.
Trevor Gehman
24
En iOS 8.1 (y quizás 8.0, no lo he probado) esto ya no funciona sin problemas. El controlador de vista inicial parpadea por un breve momento.
BFeher
77
¿Existe una versión rápida de este enfoque?
Seano
99
@Julian En iOS 8, que reemplace las dos líneas [self.window makeKeyAndVisible]; [self.window.rootViewController presentViewController:viewController animated:animated completion:nil];con self.window.rootViewController = viewController;para evitar el parpadeo. Para animar eso, simplemente envuélvelo en un[UIView transitionWithView...];
BFeher
20

EDITAR: Agregar acción de cierre de sesión.

ingrese la descripción de la imagen aquí

1. En primer lugar, prepare el archivo delegado de la aplicación

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. Cree una clase llamada Usuario.

Usuario.h

#import <Foundation/Foundation.h>

@interface User : NSObject

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

@end

Usuario.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. Cree un nuevo controlador RootViewController y conéctese con la primera vista, donde vive el botón de inicio de sesión. Agregue también una ID de 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) Cree un nuevo controlador LoginViewController y conéctese con la vista de inicio de sesión.

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. Al final, agregue un nuevo controlador ProfileViewController y conéctese con la vista de perfil en la pestaña 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 es un proyecto de muestra para obtener ayuda adicional.

Dimitris Bouzikas
fuente
3
proyecto de muestra me ayudó mucho a entender el concepto de inicio de sesión n cierre de sesión ... muchas gracias :)
Dave
16

No me gustó la respuesta de bhavya por usar AppDelegatedentro de View Controllers y configurarrootViewController no tiene animación. Y la respuesta de Trevor tiene problemas con el controlador de vista intermitente en iOS8.

UPD 18/07/2015

AppDelegate dentro de View Controllers:

Cambiar el estado de AppDelegate (propiedades) dentro del controlador de vista interrumpe la encapsulación.

Jerarquía muy simple de objetos en cada proyecto de iOS:

AppDelegate (posee windowy rootViewController)

ViewController (posee view)

Está bien que los objetos de arriba cambien los objetos de abajo, porque los están creando. Pero no está bien si los objetos en la parte inferior cambian los objetos en la parte superior de ellos (describí algunos principios básicos de programación / OOP: DIP (Principio de inversión de dependencia: el módulo de alto nivel no debe depender del módulo de bajo nivel, pero deben depender de abstracciones) )

Si algún objeto cambiara cualquier objeto en esta jerarquía, tarde o temprano habrá un desorden en el código. Puede estar bien en los proyectos pequeños, pero no es divertido profundizar en este desastre en los proyectos de bits =]

UPD 18/07/2015

Repito las animaciones del controlador modal usando UINavigationController(tl; dr: verifique el proyecto ).

Estoy usando UINavigationControllerpara presentar todos los controladores en mi aplicación. Inicialmente, mostré el controlador de vista de inicio de sesión en la pila de navegación con animación push / pop simple. Entonces decidí cambiarlo a modal con cambios mínimos.

Cómo funciona:

  1. El controlador de vista inicial (o self.window.rootViewController) es UINavigationController con ProgressViewController como a rootViewController. Estoy mostrando ProgressViewController porque DataModel puede tardar un tiempo en inicializarse porque se encuentra en la pila de datos principales como en este artículo (realmente me gusta este enfoque).

  2. AppDelegate es responsable de obtener actualizaciones del estado de inicio de sesión.

  3. DataModel maneja el inicio / cierre de sesión del usuario y AppDelegate está observando su userLoggedInpropiedad a través de KVO. Podría decirse que no es el mejor método para hacer esto, pero funciona para mí. (Por qué KVO es malo, puede consultar este o este artículo (parte ¿Por qué no usar notificaciones?)).

  4. ModalDismissAnimator y ModalPresentAnimator se utilizan para personalizar la animación push predeterminada.

Cómo funciona la lógica de los animadores:

  1. AppDelegate se establece como un delegado de self.window.rootViewController(que es UINavigationController).

  2. AppDelegate devuelve uno de los animadores -[AppDelegate navigationController:animationControllerForOperation:fromViewController:toViewController:]si es necesario.

  3. Implementación de animadores -transitionDuration:y -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]];
         }];
    }

El proyecto de prueba está aquí .

derpoliuk
fuente
3
Personalmente, no tengo ningún problema con que los Controladores de Vista lo sepan AppDelegate(me gustaría entender por qué lo hace), pero su comentario sobre la falta de animación es muy válido. Esto se puede resolver con esta respuesta: stackoverflow.com/questions/8053832/…
HughHughTeotl
2
@HughHughTeotl Gracias por el comentario y por el enlace. Actualicé mi respuesta.
derpoliuk
1
@derpoliuk ¿y si mi controlador de vista base es un UITabBarController? No puedo empujarlo en un UINavigationController.
Giorgio
@Giorgio, es una pregunta interesante, no la usé UITabBarControllerdurante mucho tiempo. Probablemente comenzaría con un enfoque de ventana en lugar de manipular los controladores de vista.
derpoliuk
11

Aquí está mi solución Swifty para futuros espectadores.

1) Cree un protocolo para manejar las funciones de inicio y cierre de sesión:

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

2) Amplíe dicho protocolo y proporcione la funcionalidad aquí para cerrar sesión:

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) Entonces puedo conformar mi AppDelegate al protocolo LoginFlowHandler y llamar handleLoginal inicio:

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 de aquí, mi extensión de protocolo manejará la lógica o determinará si el usuario está conectado / desconectado, y luego cambiará el rootViewController de Windows en consecuencia.

Harry Bloom
fuente
No estoy seguro si estoy siendo estúpido, pero AppDelegate no se ajusta LoginFlowHandler. ¿Me estoy perdiendo de algo? Además, supongo que este código solo administra el inicio de sesión en el inicio. ¿Cómo gestiono el cierre de sesión desde un controlador de vista?
luke
@luke ya que toda la lógica se implementa en la extensión, no es necesario implementarla en AppDelegate. Eso es lo genial de las Extensiones de protocolo.
shannoga
1
Lo siento @sirFunkenstine, esa fue una clase personalizada que creé para mostrar un ejemplo de cómo se verificaría el caché de su aplicación para verificar que un usuario haya iniciado sesión previamente o no. AppStatePor lo tanto, esta implementación dependerá de cómo esté guardando sus datos de usuario en el disco.
Harry Bloom
@HarryBloom ¿cómo se usaría la handleLogoutfuncionalidad?
nithinisreddy
1
Hola @nithinisreddy: para llamar a la funcionalidad handleLogout, deberá conformar la clase desde la que está llamando al LoginFlowHandlerprotocolo. Entonces obtendrá alcance para poder llamar al método handleLogout. Vea mi paso 3 para ver un ejemplo de cómo hice eso para la clase AppDelegate.
Harry Bloom
8

NO se recomienda hacer esto desde el delegado de la aplicación. AppDelegate gestiona el ciclo de vida de la aplicación relacionado con el lanzamiento, la suspensión, la finalización, etc. Sugiero hacer esto desde su controlador de vista inicial en el viewDidAppear. Puede self.presentViewControllery self.dismissViewControllerdesde el controlador de vista de inicio de sesión. Guarde una boolclave NSUserDefaultspara ver si se inicia por primera vez.

Mihado
fuente
2
¿Debería aparecer la vista (ser visible para el usuario) en `viewDidAppear '? Esto seguirá creando un parpadeo.
Mark13426
2
No es una respuesta Y "Almacenar una clave bool en NSUserDefaults para ver si se está iniciando por primera vez" es muy peligroso para ese tipo de datos.
skywinder
6

Cree ** LoginViewController ** y ** TabBarController **.

Después de crear LoginViewController y TabBarController , necesitamos agregar un StoryboardID como " loginViewController " y " tabBarController " respectivamente.

Entonces prefiero crear la estructura constante :

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

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

En LoginViewController agregue IBAction :

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

En ProfileViewController agregue IBAction :

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

En AppDelegate agregue una línea de código en didFinishLaunchingWithOptions :

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

    Switcher.updateRootViewController()

    return true
}

Finalmente cree la clase 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

    }

}

¡Eso es todo!

iAleksandr
fuente
¿Hay alguna diferencia sobre qué controlador de vista es inicial en los guiones gráficos? En su foto agregada, puedo ver que tiene la opción "es Controlador de vista inicial" marcada en Controlador de barra de pestañas. En AppDelegate, cambie el controlador principal de vista de raíz, así que supongo que no importa, ¿verdad?
ShadeToD
@iAleksandr Actualice la respuesta para iOS 13. Porque la respuesta actual de SceneDelegate no funciona.
Nitesh el
5

En Xcode 7 puedes tener múltiples storyBoards. Será mejor si puede mantener el flujo de inicio de sesión en un guión gráfico independiente.

Esto se puede hacer usando SELECT VIEWCONTROLLER> Editor> Refactor to Storyboard

Y aquí está la versión Swift para configurar una vista como RootViewContoller-

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

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

Lo uso para verificar el primer lanzamiento:

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

(si el usuario elimina la aplicación y la vuelve a instalar, cuenta como un primer inicio)

En AppDelegate, compruebo el primer inicio y creo un controlador de navegación con las pantallas de inicio de sesión (inicio de sesión y registro), que coloco encima de la ventana principal actual:

[self.window makeKeyAndVisible];

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

Como esto está en la parte superior del controlador de vista normal, es independiente del resto de su aplicación y puede simplemente descartar el controlador de vista, si ya no lo necesita. Y también puede presentar la vista de esta manera, si el usuario presiona un botón manualmente.

Por cierto: guardo los datos de inicio de sesión de mis usuarios de esta manera:

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

Para cerrar sesión: me cambié de CoreData (demasiado lento) y uso NSArrays y NSDictionaries para administrar mis datos ahora. Cerrar sesión solo significa vaciar esas matrices y diccionarios. Además, me aseguro de configurar mis datos en viewWillAppear.

Eso es.

Thorsten
fuente
0

Estoy en la misma situación que usted y la solución que encontré para limpiar los datos es eliminar todas las cosas de CoreData en las que confían mis controladores de vista para dibujar su información. Pero todavía considero que este enfoque es muy malo, creo que se puede lograr una forma más elegante de hacerlo sin guiones gráficos y usando solo código para administrar las transiciones entre los controladores de vista.

Encontré este proyecto en Github que hace todo esto solo por código y es bastante fácil de entender. Utilizan un menú lateral similar a Facebook y lo que hacen es cambiar el controlador de vista central dependiendo de si el usuario ha iniciado sesión o no. Cuando el usuario appDelegatecierra sesión, elimina los datos de CoreData y vuelve a establecer el controlador de vista principal en la pantalla de inicio de sesión.

amb
fuente
0

Tuve un problema similar que resolver en una aplicación y utilicé el siguiente método. No utilicé notificaciones para manejar la navegación.

Tengo tres guiones gráficos en la aplicación.

  1. Guión gráfico de pantalla de inicio: para la inicialización de la aplicación y verificar si el usuario ya ha iniciado sesión
  2. Storyboard de inicio de sesión: para manejar el flujo de inicio de sesión del usuario
  3. Storyboard de la barra de pestañas: para mostrar el contenido de la aplicación

Mi guión gráfico inicial en la aplicación es guión gráfico de pantalla de bienvenida. Tengo el controlador de navegación como la raíz del inicio de sesión y el guión gráfico de la barra de pestañas para manejar las navegaciones del controlador de vista.

Creé una clase Navigator para manejar la navegación de la aplicación y se ve así:

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)

           }

       }

   }

}

Veamos los posibles escenarios:

  • Primer lanzamiento de la aplicación; La pantalla de inicio se cargará donde verifique si el usuario ya ha iniciado sesión. Luego, la pantalla de inicio de sesión se cargará utilizando la clase Navigator de la siguiente manera;

Como tengo el controlador de navegación como raíz, instanciaré el controlador de navegación como controlador de vista inicial.

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

let loginNav = loginSB.instantiateInitialViewcontroller() as! UINavigationController

Navigator.moveTo(loginNav, from: self)

Esto elimina el guión gráfico de slpash de la raíz de la ventana de la aplicación y lo reemplaza con el guión gráfico de inicio de sesión.

Desde el guión gráfico de inicio de sesión, cuando el usuario inicia sesión correctamente, guardo los datos del usuario en Valores predeterminados del usuario e inicializo un singleton de Datos de usuario para acceder a los detalles del usuario. Luego, el guión gráfico de la barra de pestañas se carga utilizando el método del navegador.

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

Navigator.moveTo(tabBarNav, from: self)

Ahora el usuario cierra sesión desde la pantalla de configuración en la barra de pestañas. Borro todos los datos de usuario guardados y navego a la pantalla de inicio de sesión.

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

let loginNav = loginSB.instantiateInitialViewcontroller() as! UINavigationController

Navigator.moveTo(loginNav, from: self)
  • El usuario inicia sesión y la fuerza mata la aplicación

Cuando el usuario inicia la aplicación, se cargará la pantalla de bienvenida. Compruebo si el usuario ha iniciado sesión y accedo a los datos del usuario desde los valores predeterminados del usuario. Luego inicialice el singleton UserData y muestra la barra de pestañas en lugar de la pantalla de inicio de sesión.

Jithin
fuente
-1

Gracias a la solución de bhavya. Ha habido dos respuestas sobre swift, pero esas no están muy intactas. Lo he hecho en swift3. Debajo está el código principal.

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

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

En la función logOutAction

@IBAction func logOutAction(_ sender: UIButton) {
    UserDefaults.standard.setValue(false, forKey: Common.isLoginKey)
    UIApplication.shared.delegate?.window!?.rootViewController = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController()
}
WangYang
fuente
Hola eli La pregunta que respondió ya tiene un par de respuestas realmente buenas. Cuando decida responder a esa pregunta, asegúrese de explicar por qué su respuesta es mejor que las muy buenas que ya se publicaron.
Noel Widmer
Hola noel Noté las otras respuestas para Swift. Pero consideré que las respuestas no están muy intactas. Así que envío mi respuesta sobre la versión swift3. Sería una ayuda para el nuevo programador rápido. ¡Gracias! @Noel Widmer.
WangYang
¿Puedes agregar esa explicación en la parte superior de tu publicación? De esa manera, todos pueden ver inmediatamente el beneficio de su respuesta. Diviértete en SO! :)
Noel Widmer
1
Tanques para su sugerencia. He agregado la explicación. Gracias de nuevo. @ Noel Widmer.
WangYang
Solución vaga que no destaca el uso de la palabra clave 'Común'.
Samarey
-3

ingrese la descripción de la imagen aquí

En la aplicación 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 controlador.m En vista se cargó

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

}

En la acción del botón de cierre de sesión

-(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];
});}
usuario5575941
fuente
¿Por qué es necesario agregar alguna funcionalidad al archivo ViewController.m ??
Eesha
@Eesha Agregó un elemento del botón TabBar "cerrar sesión" a la TabBar. Supongo que falta una imagen más, podrías haberla visto.
helloWorld
¡La clave de inicio de sesión en la tienda NSUserDefaultses muy, muy insegura para ese tipo de datos!
skywinder