ios - Best Practices für den Storyboard-Anmeldebildschirm, der das Löschen von Daten beim Abmelden behandelt




login uistoryboard (8)

Ich baue eine iOS App mit einem Storyboard. Der Stammansicht-Controller ist ein Tab-Controller. Ich erstelle den Login / Logout-Prozess, und es funktioniert meistens gut, aber ich habe ein paar Probleme. Ich muss den besten Weg kennen, all das aufzustellen.

Ich möchte Folgendes erreichen:

  1. Zeigen Sie beim ersten Start der App einen Anmeldebildschirm an. Wenn sie sich einloggen, gehen Sie zum ersten Tab des Tab Bar Controllers.
  2. Jedes Mal, wenn sie die App danach starten, prüfen Sie, ob sie angemeldet sind, und wechseln Sie direkt zum ersten Tab des Stamm-Tab-Controllers.
  3. Wenn Sie manuell auf eine Schaltfläche zum Abmelden klicken, zeigen Sie den Anmeldebildschirm an und löschen Sie alle Daten von den View-Controllern.

Was ich bisher gemacht habe, ist, den Root-View-Controller auf den Tab-Bar-Controller zu setzen und einen eigenen Übergang zu meinem Login-View-Controller zu erstellen. In meiner Tab-Bar-Controller-Klasse überprüfe ich, ob sie in der viewDidAppear Methode angemeldet sind, und viewDidAppear den folgenden viewDidAppear : [self performSegueWithIdentifier:@"pushLogin" sender:self];

Ich habe auch eine Benachrichtigung eingerichtet, wann die [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(logoutAccount) name:@"logoutAccount" object:nil]; werden muss: [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(logoutAccount) name:@"logoutAccount" object:nil];

Beim Abmelden lösche ich die Anmeldeinformationen vom Schlüsselbund, führe [self setSelectedIndex:0] und führe den Übergang durch, um den Login-View-Controller wieder anzuzeigen.

Das alles funktioniert gut, aber ich frage mich: sollte diese Logik in AppDelegate sein? Ich habe auch zwei Probleme:

  • Beim ersten Start der App wird der Tab Bar Controller kurz angezeigt, bevor der Übergang ausgeführt wird. Ich habe versucht, den Code zu viewWillAppear zu viewWillAppear aber das Segment wird nicht so früh arbeiten.
  • Wenn sie sich abmelden, befinden sich alle Daten immer noch in allen View-Controllern. Wenn sie sich bei einem neuen Konto anmelden, werden die alten Kontodaten weiterhin angezeigt, bis sie aktualisiert werden. Ich brauche eine Möglichkeit, dies beim Abmelden einfach zu löschen.

Ich bin offen dafür, dies zu überarbeiten. Ich habe darüber nachgedacht, den Anmeldebildschirm zum Stammansicht-Controller zu machen oder einen Navigations-Controller im AppDelegate zu erstellen, um alles zu verarbeiten ... Ich bin mir nicht sicher, was die beste Methode zu diesem Zeitpunkt ist.


Danke, Bhavyas Lösung. Es gab zwei Antworten auf schnelle, aber die sind nicht sehr intakt. Ich habe das im swift3 getan. Unten ist der Hauptcode.

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

In LogOutAction-Funktion

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

Dies wird vom App-Delegaten aus nicht empfohlen. AppDelegate verwaltet den App-Lebenszyklus, der sich auf das Starten, Unterbrechen, Beenden usw. bezieht. Ich schlage vor, dies von Ihrem ursprünglichen View-Controller in der viewDidAppear . Sie können self.presentViewController und self.dismissViewController vom Login-View-Controller aus self.dismissViewController . Speichern Sie einen bool Schlüssel in NSUserDefaults zu sehen, ob er zum ersten Mal gestartet wird.


Hier ist, was ich getan habe, um alles zu erreichen. Das einzige, was Sie zusätzlich beachten müssen, ist (a) der Login-Prozess und (b) wo Sie Ihre App-Daten speichern (in diesem Fall habe ich einen Singleton verwendet).

Wie Sie sehen können, ist der Root View Controller mein Main Tab Controller . Ich habe dies getan, weil ich nach dem Anmelden des Benutzers die App direkt auf der ersten Registerkarte starten möchte. (Dadurch wird "Flimmern" vermieden, wenn die Anmeldeansicht vorübergehend angezeigt wird.)

AppDelegate.m

In dieser Datei überprüfe ich, ob der Benutzer bereits angemeldet ist. Wenn nicht, schiebe ich den Login-View-Controller. Ich kümmere mich auch um den Logout-Prozess, wo ich Daten lösche und die Login-Ansicht zeige.

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

Wenn die Anmeldung erfolgreich ist, werde ich die Ansicht einfach schließen und eine Benachrichtigung senden.

-(void) loginWasSuccessful
{

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

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

}

Ich benutze dies, um nach dem ersten Start zu suchen:

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

(Wenn der Nutzer die App löscht und neu installiert, zählt das wie ein erster Start)

Im AppDelegate suche ich nach dem ersten Start und erstelle einen Navigations-Controller mit den Anmeldefenstern (Login und Registrierung), die ich über das aktuelle Hauptfenster setze:

[self.window makeKeyAndVisible];

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

Da dies oben auf dem regulären Ansichts-Controller ist, ist es unabhängig vom Rest Ihrer App und Sie können den View-Controller einfach ablehnen, wenn Sie ihn nicht mehr benötigen. Und Sie können die Ansicht auch so darstellen, wenn der Benutzer einen Knopf manuell drückt.

BTW: Ich speichere die Login-Daten meiner Benutzer wie folgt:

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

Für die Abmeldung: Ich habe mich von CoreData abgewandt (zu langsam) und benutze NSArrays und NSDictionaries, um meine Daten jetzt zu verwalten. Logout bedeutet nur, diese Arrays und Wörterbücher zu leeren. Außerdem stelle ich sicher, dass meine Daten in viewWillAppear gesetzt sind.

Das ist es.


Ich mochte die Antwort von AppDelegate nicht, weil ich AppDelegate innerhalb der View-Controller verwende und rootViewController keine Animation eingestellt habe. Und Trevors Antwort hat Probleme mit dem blinkenden View-Controller auf iOS8.

UPD 07/18/2015

AppDelegate in View-Controllern:

Das Ändern des AppDelegate-Status (Eigenschaften) im View Controller unterbricht die Kapselung.

Sehr einfache Hierarchie von Objekten in jedem iOS-Projekt:

AppDelegate (besitzt window und rootViewController )

ViewController (besitzt view )

Es ist in Ordnung, dass Objekte von oben Objekte am unteren Rand ändern, weil sie sie erstellen. Aber es ist nicht in Ordnung, wenn Objekte auf der Unterseite Objekte auf ihnen ändern (ich beschrieb einige grundlegende Programmierung / OOP-Prinzip: DIP (Dependency Inversion Prinzip: High-Level-Modul darf nicht von der Low-Level-Modul abhängen, aber sie sollten auf Abstraktionen abhängen) ).

Wenn irgendein Objekt irgendein Objekt in dieser Hierarchie ändert, wird es früher oder später eine Unordnung im Code geben. Es mag bei den kleinen Projekten in Ordnung sein, aber es macht keinen Spaß, dieses Durcheinander auf den Bit-Projekten zu durchforsten =]

UPD 07/18/2015

Ich repliziere modale Controller-Animationen mit UINavigationController (tl; dr: Überprüfen Sie das project ).

Ich verwende UINavigationController , um alle Controller in meiner App zu präsentieren. Anfangs habe ich den Login-View-Controller im Navigations-Stack mit einfacher Push / Pop-Animation angezeigt. Dann habe ich beschlossen, es mit minimalen Änderungen in modal zu ändern.

Wie es funktioniert:

  1. Initial View Controller (oder self.window.rootViewController ) ist UINavigationController mit ProgressViewController als rootViewController . Ich zeige ProgressViewController, weil DataModel einige Zeit zum Initialisieren braucht, weil es seinen Kerndatenstapel wie in diesem article (ich mag diesen Ansatz wirklich).

  2. AppDelegate ist verantwortlich für die Aktualisierung des Login-Status.

  3. DataModel behandelt Benutzeranmeldung / -abmeldung und AppDelegate beobachtet userLoggedIn Eigenschaft userLoggedIn über KVO. Wohl nicht die beste Methode, um das zu tun, aber es funktioniert für mich. (Warum KVO ist schlecht, können Sie in this oder diesem Artikel (Warum nicht Benachrichtigungen verwenden? Teil).

  4. ModalDismissAnimator und ModalPresentAnimator werden zum Anpassen der Standard-Push-Animation verwendet.

Wie die Logik der Animatoren funktioniert:

  1. AppDelegate setzt sich selbst als Delegat von self.window.rootViewController (was UINavigationController ist).

  2. AppDelegate gibt einen der Animatoren in -[AppDelegate navigationController:animationControllerForOperation:fromViewController:toViewController:] falls erforderlich.

  3. Animatoren implementieren- -transitionDuration: und -animateTransition: Methoden. -[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]];
         }];
    }
    

Testprojekt ist project .


In Xcode 7 können Sie mehrere StoryBoards haben. Es ist besser, wenn Sie den Login-Flow in einem separaten Storyboard halten können.

Dies kann mit SELECT VIEWCONTROLLER> Editor> Refactor to Storyboard erfolgen

Und hier ist die Swift-Version zum Einstellen einer Ansicht als RootViewContoller-

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

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

BEARBEITEN: Abmeldeaktion hinzufügen.

1. Bereiten Sie zuerst die App-Delegatendatei vor

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. Erstellen Sie eine Klasse namens Benutzer.

Benutzer.h

#import <Foundation/Foundation.h>

@interface User : NSObject

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

@end

Benutzer.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. Erstellen Sie einen neuen Controller RootViewController und verbunden mit der ersten Ansicht, wo Login-Taste leben. Fügen Sie auch eine Storyboard-ID hinzu: "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. Erstellen Sie einen neuen Controller LoginViewController und verbunden mit der Login-Ansicht.

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. Fügen Sie am Ende einen neuen Controller ProfileViewController hinzu und verbinden Sie ihn mit der Profilansicht in der Registerkarte ViewController.

ProfilansichtController.h

#import <UIKit/UIKit.h>

@interface ProfileViewController : UIViewController

@end

ProfilansichtController.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 ist ein Beispielprojekt für zusätzliche Hilfe.


In Ihrer appDelegate.m innerhalb Ihrer 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;
}

In der Datei SignUpViewController.m

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

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

In der Datei 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;

}

Schnelle 4 Version

didFinishLaunchingWithOptions im App-Delegaten unter der Annahme, dass Ihr initialer View-Controller der in TabbarController signierte ist.

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

    return true

Im Anmeldeansicht-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




clear