Best practice per la schermata di accesso dello Storyboard, gestione della cancellazione dei dati al logout


290

Sto creando un'app iOS usando uno Storyboard. Il controller della vista principale è un controller della barra delle schede. Sto creando il processo di accesso / disconnessione e funziona principalmente bene, ma ho alcuni problemi. Devo conoscere il modo MIGLIORE per impostare tutto questo.

Voglio realizzare quanto segue:

  1. Mostra una schermata di accesso al primo avvio dell'app. Quando effettuano l'accesso, vai alla prima scheda del controller della barra delle schede.
  2. Ogni volta che avviano l'app dopo, controlla se sono connessi e passa direttamente alla prima scheda del controller della barra delle schede principale.
  3. Quando fanno clic manualmente su un pulsante di logout, mostrano la schermata di accesso e cancellano tutti i dati dai controller di visualizzazione.

Quello che ho fatto finora è impostare il controller della vista radice sul controller della barra delle schede e creare un seguito personalizzato al mio controller della vista di accesso. All'interno della mia classe Controller della barra delle schede, controllo se sono connessi all'interno del viewDidAppearmetodo ed eseguo quanto segue:[self performSegueWithIdentifier:@"pushLogin" sender:self];

Ho anche impostato una notifica per quando è necessario eseguire l'azione di disconnessione: [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(logoutAccount) name:@"logoutAccount" object:nil];

Dopo la disconnessione, deseleziono le credenziali dal Portachiavi, eseguo [self setSelectedIndex:0]ed eseguo i seguenti per mostrare nuovamente il controller della vista di accesso.

Funziona tutto bene, ma mi chiedo: questa logica dovrebbe essere in AppDelegate? Ho anche due problemi:

  • La prima volta che lanciano l'app , il controller della barra delle schede viene mostrato brevemente prima dell'esecuzione del seguito. Ho provato a spostare il codice in viewWillAppearma il seguito non funzionerà così presto.
  • Quando si disconnettono, tutti i dati sono ancora all'interno di tutti i controller di visualizzazione. Se accedono a un nuovo account, i dati del vecchio account verranno comunque visualizzati fino all'aggiornamento. Ho bisogno di un modo per cancellarlo facilmente al logout.

Sono aperto a rielaborare questo. Ho preso in considerazione l'idea di rendere la schermata di accesso il controller di visualizzazione principale o di creare un controller di navigazione in AppDelegate per gestire tutto ... Non sono sicuro di quale sia il metodo migliore a questo punto.


Presentate il controller di visualizzazione dell'accesso come modale?
Vokilam,

@TrevorGehman - puoi aggiungere la tua foto dello storyboard
rohan k shah,

Ho inviato una risposta con i dettagli di ciò che ho finito per fare. È simile ad alcune delle altre risposte fornite, in particolare @bhavya kothari.
Trevor Gehman,

Per la presentazione della schermata di accesso, AuthNavigation può essere utile. Organizza la presentazione di una schermata di accesso, se necessario, e supporta anche l'accesso automatico.
Codey,

Uno dei problemi di base che è quasi sempre risolto ma allo stesso tempo sembra che avrebbe potuto essere fatto meglio
Amar

Risposte:


311

Lo storyboard dovrebbe apparire così

Nella tua appDelegate.m nella tua 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;
}

Nel file SignUpViewController.m

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

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

Nel file 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;

}

Versione Swift 4

didFinishLaunchingWithOptions nel delegato dell'app supponendo che il controller della vista iniziale sia il TabbarController connesso.

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

    return true

In Iscriviti 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

Hai dimenticato di eliminare l'autenticazione bool da userDefaults dopo il logout
CodeLover

28
-1 per l'utilizzo AppDelegateall'interno UIViewControllere l'impostazione window.rootViewControllerlì. Non lo considero una "best practice".
derpoliuk,

2
Non voleva dare -1senza inviare una risposta: stackoverflow.com/a/30664935/1226304
derpoliuk

1
Sto provando a farlo rapidamente su IOS8 ma ricevo il seguente errore quando l'app si avvia e la schermata di accesso mostra: "Chiamate non bilanciate per iniziare / terminare le transizioni di presenza". Ho notato che quando l'app viene caricata viene visualizzata la schermata di accesso, ma viene caricata anche la prima scheda sul controller della barra delle schede. Confermato tramite println () in viewdidload. Suggerimenti?
Alex Lacayo,

1
Bingo! -2. -1 per AppDelegateall'interno UIViewController-1 per la memorizzazione della chiave di accesso NSUserDefaults. È molto insicuro per quel tipo di dati!
skywinder

97

Ecco cosa ho finito per fare tutto. L'unica cosa che devi considerare in aggiunta a questo è (a) il processo di accesso e (b) dove stai memorizzando i dati della tua app (in questo caso, ho usato un singleton).

Storyboard che mostra il controller della vista di accesso e il controller della scheda principale

Come puoi vedere, il controller della vista principale è il mio controller della scheda principale . L'ho fatto perché dopo che l'utente ha effettuato l'accesso, voglio che l'app si avvii direttamente nella prima scheda. (Questo evita qualsiasi "sfarfallio" in cui la vista di accesso mostra temporaneamente.)

AppDelegate.m

In questo file, controllo se l'utente ha già effettuato l'accesso. In caso contrario, invio il controller della vista di accesso. Gestisco anche il processo di disconnessione, in cui desidero cancellare i dati e mostrare la vista di accesso.

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

Qui, se l'accesso ha esito positivo, chiudo semplicemente la vista e invio una notifica.

-(void) loginWasSuccessful
{

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

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

}

2
Per cosa usi la notifica?
ribellione del

1
@BFeher ha ragione. Ho usato la notifica per attivare un nuovo pull di dati. Puoi usarlo per fare quello che vuoi, ma nel mio caso, dovevo essere avvisato che il login era andato a buon fine e che erano necessari nuovi dati.
Trevor Gehman,

24
In iOS 8.1 (e forse 8.0, non sono stati testati) questo non funziona più correttamente. Il View Controller iniziale lampeggia per un breve momento.
BFeher,

7
Esiste una versione Swift di questo approccio?
Seano,

9
@Julian In iOS 8, sostituisco le due linee [self.window makeKeyAndVisible]; [self.window.rootViewController presentViewController:viewController animated:animated completion:nil];con self.window.rootViewController = viewController;per evitare lo sfarfallio. Per animarlo basta avvolgerlo in un[UIView transitionWithView...];
BFeher

20

EDIT: Aggiungi azione di logout.

inserisci qui la descrizione dell'immagine

1. Prima di tutto preparare il file delegato dell'app

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. Creare una classe denominata Utente.

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. Crea un nuovo controller RootViewController e collegati con la prima vista, dove si trova il pulsante di accesso. Aggiungi anche un ID 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. Creare un nuovo controller LoginViewController e connesso con la vista di accesso.

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. Alla fine aggiungere un nuovo controller ProfileViewController e collegato con la vista profilo nella scheda 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 è un progetto di esempio per ulteriore aiuto.


3
Il progetto di esempio mi ha aiutato moltissimo a comprendere il concetto di accesso e disconnessione. Mille grazie :)
Dave,

16

Non mi è piaciuta la risposta di bhavya a causa dell'uso AppDelegateall'interno di View Controller e l'impostazione rootViewControllernon ha animazione. E la risposta di Trevor ha problemi con il controller di visualizzazione lampeggiante su iOS8.

UPD 18/07/2015

AppDelegate in View Controller:

La modifica dello stato (proprietà) AppDelegate all'interno del controller della vista interrompe l'incapsulamento.

Gerarchia di oggetti molto semplice in ogni progetto iOS:

AppDelegate (possiede windowe rootViewController)

ViewController (possiede view)

Va bene che gli oggetti dall'alto cambiano oggetti in basso, perché li stanno creando. Ma non va bene se gli oggetti in basso cambiano oggetti al di sopra di essi (ho descritto alcuni principi di programmazione / OOP di base: DIP (principio di inversione di dipendenza: il modulo di alto livello non deve dipendere dal modulo di basso livello, ma dovrebbe dipendere da astrazioni) ).

Se un oggetto cambia qualsiasi oggetto in questa gerarchia, prima o poi ci sarà un pasticcio nel codice. Potrebbe essere ok su piccoli progetti, ma non è divertente scavare in questo pasticcio sui progetti bit =]

UPD 18/07/2015

Replico le animazioni del controller modale usando UINavigationController(tl; dr: controlla il progetto ).

Sto usando UINavigationControllerper presentare tutti i controller nella mia app. Inizialmente ho visualizzato il controller della vista di accesso nello stack di navigazione con una semplice animazione push / pop. Quindi ho deciso di cambiarlo in modale con modifiche minime.

Come funziona:

  1. Il controller della vista iniziale (o self.window.rootViewController) è UINavigationController con ProgressViewController come a rootViewController. Sto mostrando ProgressViewController perché DataModel può impiegare un po 'di tempo a inizializzarsi perché contiene lo stack di dati di base come in questo articolo (mi piace molto questo approccio).

  2. AppDelegate è responsabile per ottenere gli aggiornamenti dello stato di accesso.

  3. DataModel gestisce il login / logout dell'utente e AppDelegate sta osservando la sua userLoggedInproprietà tramite KVO. Probabilmente non è il metodo migliore per farlo, ma funziona per me. (Perché KVO è male, puoi controllare in questo o in questo articolo (parte Perché non usare le notifiche?).

  4. ModalDismissAnimator e ModalPresentAnimator sono utilizzati per personalizzare l'animazione push predefinita.

Come funziona la logica degli animatori:

  1. AppDelegate si imposta come delegato di self.window.rootViewController(che è UINavigationController).

  2. AppDelegate restituisce uno degli animatori, -[AppDelegate navigationController:animationControllerForOperation:fromViewController:toViewController:]se necessario.

  3. Attuatori -transitionDuration:e -animateTransition:metodi degli animatori . -[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]];
         }];
    }

Il progetto di test è qui .


3
Personalmente non ho problemi con View Controller AppDelegate(sarei interessato a capire perché lo fai), ma il tuo commento sulla mancanza di animazione è molto valido. Che può essere risolto da questa risposta: stackoverflow.com/questions/8053832/...
HughHughTeotl

2
@HughHughTeotl Grazie per il commento e per il link. Ho aggiornato la mia risposta.
derpoliuk,

1
@derpoliuk cosa succede se il mio controller di visualizzazione di base è un UITabBarController? Non riesco a inserirlo in un UINavigationController.
Giorgio,

@Giorgio, è una domanda interessante, non l'ho usata UITabBarControllerper molto tempo. Probabilmente inizierei con l' approccio a finestra invece di manipolare i controller di visualizzazione.
derpoliuk,

11

Ecco la mia soluzione Swifty per tutti gli spettatori futuri.

1) Creare un protocollo per gestire entrambe le funzioni di accesso e disconnessione:

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

2) Estendere detto protocollo e fornire qui le funzionalità per disconnettersi:

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) Quindi posso conformare il mio AppDelegate al protocollo LoginFlowHandler e chiamare handleLoginall'avvio:

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
    }

}

Da qui, la mia estensione di protocollo gestirà la logica o determinerà se l'utente ha effettuato l'accesso / disconnessione, quindi cambia Windows rootViewController di conseguenza!


Non sono sicuro che io sia stupido, ma AppDelegate non è conforme LoginFlowHandler. Mi sto perdendo qualcosa? Inoltre, suppongo che questo codice gestisca l'accesso solo all'avvio. Come gestisco il logout da un controller di visualizzazione?
Luca,

@luke poiché tutta la logica è implementata nell'estensione non è necessario implementarla in AppDelegate. Questo è ciò che è eccezionale nelle estensioni di protocollo.
Shannoga,

1
Spiacente @sirFunkenstine, quella era una classe personalizzata che ho creato per mostrare un esempio di come si controlla la cache dell'app per verificare che un utente abbia effettuato l'accesso o meno in precedenza. Questa AppStateimplementazione dipenderà quindi da come si stanno salvando i dati dell'utente su disco.
Harry Bloom,

@HarryBloom come si usa la handleLogoutfunzionalità?
nithinisreddy,

1
Ciao @nithinisreddy - per chiamare la funzionalità handleLogout, dovrai conformare la classe da cui stai chiamando al LoginFlowHandlerprotocollo. Quindi otterrai l'ambito per poter chiamare il metodo handleLogout. Vedi il mio passaggio 3 per un esempio di come l'ho fatto per la classe AppDelegate.
Harry Bloom il

8

Non è consigliabile farlo dal delegato dell'app. AppDelegate gestisce il ciclo di vita dell'app relativo all'avvio, alla sospensione, alla chiusura e così via. Suggerisco di farlo dal controller della vista iniziale in viewDidAppear. È possibile self.presentViewControllere self.dismissViewControllerdal controller della vista di accesso. Conservare una boolchiave NSUserDefaultsper vedere se si sta avviando per la prima volta.


2
La vista dovrebbe apparire (essere visibile all'utente) in `viewDidAppear '? Ciò creerà comunque uno sfarfallio.
Mark13426

2
Non una risposta E "Archivia una chiave bool in NSUserDefaults per vedere se si sta avviando per la prima volta." È molto pericoloso per quel tipo di dati.
skywinder

6

Crea ** LoginViewController ** e ** TabBarController **.

Dopo aver creato LoginViewController e TabBarController , dobbiamo aggiungere uno StoryboardID rispettivamente come " loginViewController " e " tabBarController ".

Quindi preferisco creare la struttura Costante :

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

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

In LoginViewController aggiungi IBAction :

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

In ProfileViewController aggiungi IBAction :

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

In AppDelegate aggiungi la riga di codice in didFinishLaunchingWithOptions :

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

    Switcher.updateRootViewController()

    return true
}

Infine crea la 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

    }

}

Questo è tutto!


C'è qualche differenza quale controller di visualizzazione è iniziale negli storyboard? Nella tua foto aggiunta posso vedere che hai l'opzione "is Initial View Controller" selezionata su Tab Bar Controller. In AppDelegate si passa al controller principale della vista principale, quindi suppongo che non abbia importanza, vero?
ShadeToD

@iAleksandr Aggiorna la risposta per iOS 13. La risposta attuale di Coz of SceneDelegate non funziona.
Nitesh,

5

In Xcode 7 puoi avere più storyboard. Sarà meglio se riesci a mantenere il flusso di accesso in uno storyboard separato.

Questo può essere fatto usando SELECT VIEWCONTROLLER> Editor> Refactor to Storyboard

Ed ecco la versione Swift per impostare una vista come RootViewContoller-

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

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

3

Lo uso per verificare il primo avvio:

- (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 l'utente elimina l'app e la reinstalla, conta come un primo avvio)

In AppDelegate controllo il primo avvio e creo un controller di navigazione con le schermate di accesso (login e registrazione), che ho inserito nella finestra principale corrente:

[self.window makeKeyAndVisible];

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

Poiché questo è in cima al controller di visualizzazione normale, è indipendente dal resto della tua app e puoi semplicemente chiudere il controller di visualizzazione, se non ti serve più. E puoi anche presentare la vista in questo modo, se l'utente preme un pulsante manualmente.

A proposito: salvo i dati di accesso dai miei utenti in questo modo:

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

Per il logout: sono passato da CoreData (troppo lento) e uso NSArrays e NSDictionaries per gestire i miei dati ora. Disconnettersi significa solo svuotare quelle matrici e dizionari. Inoltre mi assicuro di impostare i miei dati in viewWillAppear.

Questo è tutto.


0

Sono nella tua stessa situazione e la soluzione che ho trovato per la pulizia dei dati sta eliminando tutte le cose CoreData su cui i miei controller di visualizzazione fanno affidamento per trarne informazioni. Ma ho ancora trovato questo approccio molto cattivo, penso che un modo più elegante per farlo possa essere realizzato senza storyboard e usando solo il codice per gestire le transizioni tra i controller di visualizzazione.

Ho trovato questo progetto su Github che fa tutto questo solo tramite codice ed è abbastanza facile da capire. Usano un menu laterale simile a Facebook e ciò che fanno è cambiare il controller della vista centrale a seconda che l'utente abbia effettuato l'accesso o meno. Quando l'utente si disconnette, appDelegaterimuove i dati da CoreData e imposta nuovamente il controller della vista principale sulla schermata di accesso.


0

Ho avuto un problema simile da risolvere in un'app e ho usato il seguente metodo. Non ho usato le notifiche per gestire la navigazione.

Ho tre storyboard nell'app.

  1. Storyboard schermata iniziale - per l'inizializzazione dell'app e il controllo se l'utente è già connesso
  2. Storyboard di accesso: per gestire il flusso di accesso degli utenti
  3. Storyboard della barra delle schede: per visualizzare il contenuto dell'app

Il mio storyboard iniziale nell'app è lo storyboard dello schermo Splash. Ho il controller di navigazione come root di login e storyboard della barra delle schede per gestire le navigazioni del controller di visualizzazione.

Ho creato una classe Navigator per gestire la navigazione dell'app e si presenta così:

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)

           }

       }

   }

}

Diamo un'occhiata ai possibili scenari:

  • Primo lancio dell'app; La schermata iniziale verrà caricata dove controllo se l'utente ha già effettuato l'accesso. Quindi la schermata di accesso verrà caricata utilizzando la classe Navigator come segue;

Dal momento che ho il controller di navigazione come root, istanzio il controller di navigazione come controller della vista iniziale.

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

let loginNav = loginSB.instantiateInitialViewcontroller() as! UINavigationController

Navigator.moveTo(loginNav, from: self)

Questo rimuove lo storyboard slpash dalla radice della finestra dell'app e lo sostituisce con lo storyboard di accesso.

Dallo storyboard di accesso, quando l'utente ha effettuato correttamente l'accesso, salvo i dati dell'utente in Default utente e inizializzo un singleton UserData per accedere ai dettagli dell'utente. Quindi lo storyboard della barra delle schede viene caricato utilizzando il metodo navigator.

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

Navigator.moveTo(tabBarNav, from: self)

Ora l'utente si disconnette dalla schermata delle impostazioni nella barra delle schede. Cancella tutti i dati utente salvati e naviga alla schermata di accesso.

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

let loginNav = loginSB.instantiateInitialViewcontroller() as! UINavigationController

Navigator.moveTo(loginNav, from: self)
  • L'utente ha effettuato l'accesso e forza uccide l'app

Quando l'utente avvia l'app, viene caricata la schermata Splash. Controllo se l'utente ha effettuato l'accesso e accedo ai dati dell'utente da Default utente. Quindi inizializza il singleton UserData e mostra la barra delle schede invece della schermata di accesso.


-1

Grazie alla soluzione di bhavya. Ci sono state due risposte su swift, ma quelle non sono molto intatte. L'ho fatto in swift3. Sotto è il codice principale.

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

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

Nella funzione logOutAction

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

Ciao Eli La domanda a cui hai già risposto ha un paio di risposte davvero buone. Quando decidi di rispondere a una domanda del genere, assicurati di spiegare perché la tua risposta è migliore di quelle molto buone già pubblicate.
Noel Widmer,

Ciao Noel Ho notato le altre risposte per rapido. Ma ho considerato che le risposte non sono molto intatte. Quindi invio la mia risposta sulla versione di swift3. Sarebbe di aiuto per il nuovo programmatore rapido. Grazie! @Noel Widmer.
WangYang,

Puoi aggiungere quella spiegazione nella parte superiore del tuo post? In questo modo tutti possono vedere immediatamente i vantaggi della tua risposta. Buon divertimento su SO! :)
Noel Widmer,

1
Serbatoi per il tuo suggerimento. Ho aggiunto la spiegazione. Grazie ancora. @ Noel Widmer.
WangYang,

Soluzione vaga che non evidenzia l'uso della parola chiave "comune".
Samarey,

-3

inserisci qui la descrizione dell'immagine

In App 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 In vista ha caricato

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

}

Nell'azione del pulsante di disconnessione

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

Perché è necessario aggiungere alcune funzionalità nel file ViewController.m ??
Eesha,

@Eesha Ha aggiunto una voce del pulsante TabBar "logout" al TabBar. Immagino che manchi l'immagine, altrimenti avresti potuto vederla.
helloWorld

Memorizzare la chiave di accesso NSUserDefaultsè molto insicuro per quel tipo di dati!
skywinder,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.