Meilleures pratiques pour l'écran de connexion Storyboard, gestion de la suppression des données lors de la déconnexion

290

Je crée une application iOS à l'aide d'un Storyboard. Le contrôleur de vue racine est un contrôleur de barre d'onglets. Je crée le processus de connexion / déconnexion, et cela fonctionne généralement bien, mais j'ai quelques problèmes. J'ai besoin de savoir la MEILLEURE façon de mettre tout cela en place.

Je veux accomplir ce qui suit:

  1. Afficher un écran de connexion lors du premier lancement de l'application. Une fois connecté, accédez au premier onglet du contrôleur de la barre d'onglets.
  2. Chaque fois qu'ils lancent l'application après cela, vérifiez s'ils sont connectés et passez directement au premier onglet du contrôleur de barre d'onglets racine.
  3. Lorsqu'ils cliquent manuellement sur un bouton de déconnexion, affichent l'écran de connexion et effacent toutes les données des contrôleurs de vue.

Ce que j'ai fait jusqu'à présent est de définir le contrôleur de vue racine sur le contrôleur de barre d'onglets et de créer un enchaînement personnalisé sur mon contrôleur de vue de connexion. Dans ma classe de contrôleur de barre d'onglets, je vérifie s'ils sont connectés dans la viewDidAppearméthode et j'effectue la séquence:[self performSegueWithIdentifier:@"pushLogin" sender:self];

J'ai également configuré une notification lorsque l'action de déconnexion doit être effectuée: [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(logoutAccount) name:@"logoutAccount" object:nil];

Lors de la déconnexion, j'efface les informations d'identification du trousseau, exécute [self setSelectedIndex:0]et effectue la séquence pour afficher à nouveau le contrôleur de vue de connexion.

Tout cela fonctionne bien, mais je me demande: cette logique devrait-elle être dans l'AppDelegate? J'ai également deux problèmes:

  • La première fois qu'ils lancent l'application , le contrôleur de la barre d'onglets s'affiche brièvement avant la transition. J'ai essayé de déplacer le code vers viewWillAppearmais la séquence ne fonctionnera pas si tôt.
  • Lorsqu'ils se déconnectent, toutes les données se trouvent toujours dans tous les contrôleurs de vue. S'ils se connectent à un nouveau compte, les anciennes données de compte sont toujours affichées jusqu'à ce qu'elles soient actualisées. J'ai besoin d'un moyen d'effacer cela facilement lors de la déconnexion.

Je suis prêt à retravailler cela. J'ai envisagé de faire de l'écran de connexion le contrôleur de vue racine ou de créer un contrôleur de navigation dans l'AppDelegate pour tout gérer ... Je ne suis tout simplement pas sûr de la meilleure méthode à ce stade.

Trevor Gehman
la source
Présentez-vous le contrôleur de vue de connexion comme modal?
vokilam
@TrevorGehman - peut ajouter votre photo de storyboard
rohan k shah
J'ai soumis une réponse avec les détails de ce que j'ai fini par faire. C'est similaire à certaines des autres réponses fournies, en particulier @bhavya kothari.
Trevor Gehman
Pour la présentation de l'écran de connexion, AuthNavigation peut être utile. Il organise la présentation d'un écran de connexion si nécessaire et prend également en charge la connexion automatique.
Codey
L'un des problèmes très basiques qui est presque toujours résolu mais qui semble en même temps avoir pu être mieux fait
amar

Réponses:

311

Votre storyboard devrait ressembler à ceci

Dans votre appDelegate.m dans votre 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;
}

Dans le fichier SignUpViewController.m

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

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

Dans le fichier 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;

}

Version Swift 4

didFinishLaunchingWithOptions dans le délégué d'application en supposant que votre contrôleur de vue initial est le TabbarController signé.

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

    return true

Dans le contrôleur de vue d'inscription:

@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
la source
Vous avez oublié de supprimer l'authentification bool de userDefaults après la déconnexion
CodeLover
28
-1 pour utiliser à l' AppDelegateintérieur UIViewControlleret window.rootViewControllery installer. Je ne considère pas cela comme une "meilleure pratique".
derpoliuk
2
Je ne voulais pas donner -1sans poster de réponse: stackoverflow.com/a/30664935/1226304
derpoliuk
1
Im essayant de le faire rapidement sur IOS8 mais j'obtiens l'erreur suivante lorsque l'application se déclenche et l'écran de connexion affiche: "Appels déséquilibrés pour commencer / terminer les transitions d'apparence". J'ai remarqué que lorsque l'application se charge, l'écran de connexion s'affiche, mais que le premier onglet du contrôleur de la barre d'onglets est également chargé. Confirmé via println () dans viewdidload. Suggestions?
Alex Lacayo
1
bingo! -2. -1 pour l' AppDelegateintérieur UIViewController-1 pour le stockage de la clé de connexion NSUserDefaults. C'est très-très peu sûr pour ce genre de données!
skywinder
97

Voici ce que j'ai fini par faire pour tout accomplir. La seule chose que vous devez considérer en plus de cela est (a) le processus de connexion et (b) où vous stockez les données de votre application (dans ce cas, j'ai utilisé un singleton).

Storyboard montrant le contrôleur de vue de connexion et le contrôleur d'onglet principal

Comme vous pouvez le voir, le contrôleur de vue racine est mon contrôleur de l'onglet principal . J'ai fait cela car après que l'utilisateur s'est connecté, je veux que l'application se lance directement dans le premier onglet. (Cela évite tout "scintillement" lorsque la vue de connexion s'affiche temporairement.)

AppDelegate.m

Dans ce fichier, je vérifie si l'utilisateur est déjà connecté. Sinon, je pousse le contrôleur d'affichage de connexion. Je gère également le processus de déconnexion, où j'efface les données et affiche la vue de connexion.

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

Ici, si la connexion réussit, je rejette simplement la vue et envoie une notification.

-(void) loginWasSuccessful
{

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

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

}
Trevor Gehman
la source
2
Pourquoi utilisez-vous la notification?
rébellion
1
@BFeher a raison. J'ai utilisé la notification pour déclencher une nouvelle extraction de données. Vous pouvez l'utiliser pour faire ce que vous voulez, mais dans mon cas, j'avais besoin d'être informé que la connexion avait réussi et que de nouvelles données étaient nécessaires.
Trevor Gehman
24
Dans iOS 8.1 (et peut-être que 8.0 n'a pas été testé), cela ne fonctionne plus correctement. Le contrôleur de vue initial clignote pendant un bref instant.
BFeher
7
Existe-t-il une version Swift de cette approche?
Seano
9
@Julian Dans iOS 8, je remplace les deux lignes [self.window makeKeyAndVisible]; [self.window.rootViewController presentViewController:viewController animated:animated completion:nil];par self.window.rootViewController = viewController;pour éviter le scintillement. Pour l'animer, il suffit de l'envelopper dans un[UIView transitionWithView...];
BFeher
20

EDIT: ajouter une action de déconnexion.

entrez la description de l'image ici

1. Préparez tout d'abord le dossier du délégué de l'application

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. Créez une classe nommée User.

User.h

#import <Foundation/Foundation.h>

@interface User : NSObject

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

@end

User.m

#import "User.h"

@implementation User

- (void)loginWithUsername:(NSString *)username andPassword:(NSString *)password{

    // Validate user here with your implementation
    // and notify the root controller
    [[NSNotificationCenter defaultCenter] postNotificationName:@"loginActionFinished" object:self userInfo:nil];
}

- (void)logout{
    // Here you can delete the account
}

- (BOOL)userAuthenticated {

    // This variable is only for testing
    // Here you have to implement a mechanism to manipulate this
    BOOL auth = NO;

    if (auth) {
        return YES;
    }

    return NO;
}

3. Créez un nouveau contrôleur RootViewController et connecté avec la première vue, où se trouve le bouton de connexion. Ajoutez également un 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. Créez un nouveau contrôleur LoginViewController et connecté avec la vue de connexion.

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. À la fin, ajoutez un nouveau contrôleur ProfileViewController et connecté à la vue de profil dans l'onglet 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 est un exemple de projet pour une aide supplémentaire.

Dimitris Bouzikas
la source
3
exemple de projet m'a beaucoup aidé à comprendre le concept de connexion n déconnexion .. merci beaucoup :)
Dave
16

Je n'ai pas aimé la réponse de bhavya à cause de l'utilisation de l' AppDelegateintérieur des contrôleurs de vue et le réglage rootViewControllern'a pas d'animation. Et la réponse de Trevor a un problème avec le contrôleur de vue clignotant sur iOS8.

UPD 18/07/2015

AppDelegate dans les contrôleurs de vue:

La modification de l'état (propriétés) d'AppDelegate à l'intérieur du contrôleur de vue interrompt l'encapsulation.

Hiérarchie très simple des objets dans chaque projet iOS:

AppDelegate (possède windowet rootViewController)

ViewController (propriétaire view)

Il est normal que les objets du haut changent les objets du bas, car ils les créent. Mais ce n'est pas correct si des objets en bas changent des objets au-dessus d'eux (j'ai décrit un principe de programmation / POO de base: DIP (Dependency Inversion Principle: le module de haut niveau ne doit pas dépendre du module de bas niveau, mais ils doivent dépendre des abstractions) ).

Si un objet change un objet de cette hiérarchie, tôt ou tard, il y aura un gâchis dans le code. Cela pourrait être correct sur les petits projets, mais ce n'est pas amusant de creuser ce gâchis sur les projets de bits =]

UPD 18/07/2015

Je réplique les animations du contrôleur modal en utilisant UINavigationController(tl; dr: vérifiez le projet ).

j'utilise UINavigationController pour présenter tous les contrôleurs de mon application. Initialement, j'ai affiché le contrôleur de vue de connexion dans la pile de navigation avec une animation push / pop simple. J'ai alors décidé de le changer en modal avec un minimum de changements.

Comment ça fonctionne:

  1. Le contrôleur de vue initial (ou self.window.rootViewController) est UINavigationController avec ProgressViewController en tant que rootViewController. Je montre ProgressViewController parce que DataModel peut prendre un certain temps pour s'initialiser car il contient sa pile de données de base comme dans cet article (j'aime vraiment cette approche).

  2. AppDelegate est responsable de l'obtention des mises à jour du statut de connexion.

  3. DataModel gère la connexion / déconnexion des utilisateurs et AppDelegate observe sa userLoggedInpropriété via KVO. Sans doute pas la meilleure méthode pour le faire, mais cela fonctionne pour moi. (Pourquoi KVO est mauvais, vous pouvez vérifier dans tel ou tel article (Pourquoi ne pas utiliser les notifications?).

  4. ModalDismissAnimator et ModalPresentAnimator sont utilisés pour personnaliser l'animation push par défaut.

Comment fonctionne la logique des animateurs:

  1. AppDelegate se définit comme un délégué de self.window.rootViewController(qui est UINavigationController).

  2. AppDelegate renvoie l'un des animateurs -[AppDelegate navigationController:animationControllerForOperation:fromViewController:toViewController:]si nécessaire.

  3. Les animateurs mettent en œuvre -transitionDuration: et -animateTransition:méthodes. -[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]];
         }];
    }

Le projet de test est ici .

derpoliuk
la source
3
Personnellement, je n'ai aucun problème avec les contrôleurs de vue AppDelegate(je serais intéressé de comprendre pourquoi vous le faites) - mais votre commentaire sur le manque d'animation est très valable. Cela peut être résolu par cette réponse: stackoverflow.com/questions/8053832/…
HughHughTeotl
2
@HughHughTeotl Merci pour le commentaire et pour le lien. J'ai mis à jour ma réponse.
derpoliuk
1
@derpoliuk et si mon contrôleur de vue de base est un UITabBarController? Je ne peux pas le pousser dans un UINavigationController.
Giorgio
@Giorgio, c'est une question intéressante, je n'ai pas utilisé UITabBarControllerdepuis très longtemps. Je commencerais probablement par l' approche de la fenêtre au lieu de manipuler les contrôleurs de vue.
derpoliuk
11

Voici ma solution Swifty pour tous les futurs spectateurs.

1) Créez un protocole pour gérer les fonctions de connexion et de déconnexion:

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

2) Étendez ledit protocole et fournissez ici la fonctionnalité de déconnexion:

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) Ensuite, je peux conformer mon AppDelegate au protocole LoginFlowHandler et appeler handleLoginau démarrage:

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
    }

}

À partir de là, mon extension de protocole gérera la logique ou déterminera si l'utilisateur est connecté / déconnecté, puis modifie le rootViewController de Windows en conséquence!

Harry Bloom
la source
Je ne sais pas si je suis stupide, mais AppDelegate n'est pas conforme LoginFlowHandler. Suis-je en train de manquer quelque chose? En outre, je suppose que ce code gère uniquement la connexion au démarrage. Comment gérer la déconnexion d'un contrôleur de vue?
luke
@luke puisque toute la logique est implémentée dans l'extension, il n'est pas nécessaire de l'implémenter dans l'AppDelegate. C'est ce qui est génial dans les extensions de protocole.
shannoga
1
Désolé @sirFunkenstine, c'était une classe personnalisée que j'ai créée pour montrer un exemple de la façon dont on vérifierait le cache de son application pour vérifier qu'un utilisateur s'est déjà connecté ou non. Cette AppStateimplémentation dépendrait donc de la façon dont vous enregistrez vos données utilisateur sur le disque.
Harry Bloom
@HarryBloom comment utiliserait-on la handleLogoutfonctionnalité?
nithinisreddy
1
Salut @nithinisreddy - pour appeler la fonctionnalité handleLogout, vous devrez conformer la classe à partir de laquelle vous appelez au LoginFlowHandlerprotocole. Ensuite, vous obtiendrez la portée pour pouvoir appeler la méthode handleLogout. Voir mon étape 3 pour un exemple de la façon dont j'ai fait cela pour la classe AppDelegate.
Harry Bloom
8

Faire cela à partir du délégué de l'application n'est PAS recommandé. AppDelegate gère le cycle de vie de l'application lié au lancement, à la suspension, à l'arrêt, etc. Je suggère de le faire à partir de votre contrôleur de vue initial dans leviewDidAppear . Vous pouvez self.presentViewControlleret self.dismissViewControllerdepuis le contrôleur de vue de connexion. Enregistrez une boolclé NSUserDefaultspour voir si elle est lancée pour la première fois.

Mihado
la source
2
La vue doit-elle apparaître (être visible par l'utilisateur) dans `viewDidAppear '? Cela créera toujours un scintillement.
Mark13426
2
Pas une réponse. Et "Stocker une clé booléenne dans NSUserDefaults pour voir si elle se lance pour la première fois." Est très très dangereux pour ce type de données.
skywinder
6

Créez ** LoginViewController ** et ** TabBarController **.

Après avoir créé LoginViewController et TabBarController , nous devons ajouter un StoryboardID comme « loginViewController » et « tabBarController » respectivement.

Ensuite, je préfère créer la structure Constant :

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

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

Dans LoginViewController, ajoutez IBAction :

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

Dans ProfileViewController, ajoutez IBAction :

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

Dans AppDelegate, ajoutez une ligne de code dans didFinishLaunchingWithOptions :

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

    Switcher.updateRootViewController()

    return true
}

Enfin créer 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

    }

}

C'est tout!

iAleksandr
la source
Y a-t-il une différence entre le contrôleur de vue initial dans les storyboards? Sur votre photo ajoutée, je peux voir que l'option "is Initial View Controller" est cochée sur Tab Bar Controller. Dans AppDelegate, passez le contrôleur principal de la vue racine, donc je suppose que cela n'a pas d'importance, n'est-ce pas?
ShadeToD
@iAleksandr Veuillez mettre à jour la réponse pour iOS 13. La réponse actuelle de SceneDelegate ne fonctionne pas.
Nitesh
5

Dans Xcode 7, vous pouvez avoir plusieurs StoryBoards. Il sera préférable de conserver le flux de connexion dans un storyboard séparé.

Cela peut être fait en utilisant SELECT VIEWCONTROLLER> Editor> Refactor to Storyboard

Et voici la version Swift pour définir une vue comme RootViewContoller-

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

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

J'utilise ceci pour vérifier le premier lancement:

- (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 l'utilisateur supprime l'application et la réinstalle, cela compte comme un premier lancement)

Dans l'AppDelegate, je vérifie le premier lancement et crée un contrôleur de navigation avec les écrans de connexion (connexion et enregistrement), que je mets en haut de la fenêtre principale actuelle:

[self.window makeKeyAndVisible];

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

Comme il se trouve au-dessus du contrôleur de vue normal, il est indépendant du reste de votre application et vous pouvez simplement fermer le contrôleur de vue si vous n'en avez plus besoin. Et vous pouvez également présenter la vue de cette façon, si l'utilisateur appuie manuellement sur un bouton.

BTW: J'enregistre les données de connexion de mes utilisateurs comme ceci:

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

Pour la déconnexion: je me suis éloigné de CoreData (trop lent) et j'utilise NSArrays et NSDictionaries pour gérer mes données maintenant. La déconnexion signifie simplement de vider ces tableaux et dictionnaires. De plus, je m'assure de définir mes données dans viewWillAppear.

C'est tout.

Thorsten
la source
0

Je suis dans la même situation que vous et la solution que j'ai trouvée pour nettoyer les données supprime tous les trucs CoreData sur lesquels mes contrôleurs de vue s'appuient pour dessiner leurs informations. Mais j'ai toujours trouvé cette approche très mauvaise, je pense qu'une façon plus élégante de le faire peut être accomplie sans story-boards et en utilisant uniquement du code pour gérer les transitions entre les contrôleurs de vue.

J'ai trouvé ce projet chez Github qui ne fait tout cela que par code et il est assez facile à comprendre. Ils utilisent un menu latéral de type Facebook et ce qu'ils font, c'est changer le contrôleur de vue centrale selon que l'utilisateur est connecté ou non. Lorsque l'utilisateur se déconnecte, le appDelegatesupprime les données de CoreData et redéfinit le contrôleur de vue principal sur l'écran de connexion.

amb
la source
0

J'ai eu un problème similaire à résoudre dans une application et j'ai utilisé la méthode suivante. Je n'ai pas utilisé de notifications pour gérer la navigation.

J'ai trois storyboards dans l'application.

  1. Storyboard de l'écran de démarrage - pour l'initialisation de l'application et vérifier si l'utilisateur est déjà connecté
  2. Storyboard de connexion - pour gérer le flux de connexion des utilisateurs
  3. Storyboard de la barre d'onglets - pour afficher le contenu de l'application

Mon storyboard initial dans l'application est le storyboard de l'écran Splash. J'ai le contrôleur de navigation comme racine de la table de connexion et de la barre d'onglets pour gérer les navigations du contrôleur de vue.

J'ai créé une classe Navigator pour gérer la navigation de l'application et cela ressemble à ceci:

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)

           }

       }

   }

}

Examinons les scénarios possibles:

  • Premier lancement d'application; L'écran de démarrage sera chargé où je vérifie si l'utilisateur est déjà connecté. Ensuite, l'écran de connexion sera chargé en utilisant la classe Navigator comme suit;

Puisque j'ai le contrôleur de navigation comme racine, j'instancie le contrôleur de navigation comme contrôleur de vue initial.

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

let loginNav = loginSB.instantiateInitialViewcontroller() as! UINavigationController

Navigator.moveTo(loginNav, from: self)

Cela supprime le storyboard slpash de la racine de la fenêtre de l'application et le remplace par le storyboard de connexion.

À partir du storyboard de connexion, lorsque l'utilisateur est correctement connecté, j'enregistre les données utilisateur dans les valeurs par défaut de l'utilisateur et initialise un singleton UserData pour accéder aux détails de l'utilisateur. Le storyboard de la barre d'onglets est ensuite chargé à l'aide de la méthode du navigateur.

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

Navigator.moveTo(tabBarNav, from: self)

Maintenant, l'utilisateur se déconnecte de l'écran des paramètres dans la barre d'onglets. J'efface toutes les données utilisateur enregistrées et accède à l'écran de connexion.

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

let loginNav = loginSB.instantiateInitialViewcontroller() as! UINavigationController

Navigator.moveTo(loginNav, from: self)
  • L'utilisateur est connecté et force tue l'application

Lorsque l'utilisateur lance l'application, l'écran Splash est chargé. Je vérifie si l'utilisateur est connecté et accède aux données utilisateur à partir des valeurs par défaut de l'utilisateur. Ensuite, initialisez le singleton UserData et affiche la barre d'onglets au lieu de l'écran de connexion.

Jithin
la source
-1

Merci à la solution de bhavya. Il y a eu deux réponses sur swift, mais celles-ci ne sont pas très intactes. Je l'ai fait dans le swift3.Below est le code principal.

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

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

Dans la fonction 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
la source
Salut Eli. La question à laquelle vous avez répondu contient déjà de très bonnes réponses. Lorsque vous décidez de répondre à une telle question, assurez-vous d'expliquer pourquoi votre réponse est meilleure que les très bonnes qui ont déjà été publiées.
Noel Widmer
Salut Noël. J'ai remarqué les autres réponses pour rapide. Mais j'ai considéré que les réponses ne sont pas très intactes. Je soumets donc ma réponse sur la version swift3. Ce serait de l'aide pour un nouveau programmeur rapide.Merci! @Noel Widmer.
WangYang
Pouvez-vous ajouter cette explication en haut de votre message? De cette façon, tout le monde peut immédiatement voir l'avantage de votre réponse. Passez un bon moment sur SO! :)
Noel Widmer
1
Réservoirs pour votre suggestion.J'ai ajouté l'explication.Merci encore. @ Noel Widmer.
WangYang
Solution vague qui ne met pas en évidence l'utilisation du mot-clé «Commun».
Samarey
-3

entrez la description de l'image ici

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 view did load

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

}

Action du bouton de déconnexion

-(void)logoutButtonClicked:(id)sender{

UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Alert" message:@"Do you want to logout?" preferredStyle:UIAlertControllerStyleAlert];

    [alertController addAction:[UIAlertAction actionWithTitle:@"Logout" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
           NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults setBool:NO forKey:@"loginSaved"];
           [[NSUserDefaults standardUserDefaults] synchronize];
      AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
    UIStoryboard *    storyboardobj=[UIStoryboard storyboardWithName:@"Main" bundle:nil];
    UIViewController *screen = [storyboardobj instantiateViewControllerWithIdentifier:@"loginViewControllerId"];
    [appDelegate.window setRootViewController:screen];
}]];


[alertController addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
    [self dismissViewControllerAnimated:YES completion:nil];
}]];

dispatch_async(dispatch_get_main_queue(), ^ {
    [self presentViewController:alertController animated:YES completion:nil];
});}
user5575941
la source
Pourquoi il est nécessaire d'ajouter des fonctionnalités dans le fichier ViewController.m ??
Eesha
@Eesha Il a ajouté un élément de bouton TabBar "déconnexion" au TabBar. Je suppose que l'image manque sinon vous auriez pu la voir.
helloWorld
La clé de connexion au magasin NSUserDefaultsest très peu sécurisée pour ce type de données!
skywinder du