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


290

Ich erstelle eine iOS-App mit einem Storyboard. Der Root View Controller ist ein Tab Bar Controller. Ich erstelle den Anmelde- / Abmeldevorgang und er funktioniert größtenteils einwandfrei, aber ich habe einige Probleme. Ich muss den BESTEN Weg kennen, um all dies einzurichten.

Ich möchte Folgendes erreichen:

  1. Zeigen Sie beim ersten Start der App einen Anmeldebildschirm an. Wenn sie sich anmelden, wechseln Sie zur ersten Registerkarte des Tab Bar Controllers.
  2. Überprüfen Sie jedes Mal, wenn sie die App danach starten, ob sie angemeldet sind, und springen Sie direkt zur ersten Registerkarte des Root-Registerkartenleisten-Controllers.
  3. Wenn sie manuell auf eine Abmeldeschaltfläche klicken, wird der Anmeldebildschirm angezeigt und alle Daten von den Ansichtssteuerungen gelöscht.

Was ich bisher getan habe, ist, den Root-View-Controller auf den Tab Bar Controller zu setzen und einen benutzerdefinierten Übergang zu meinem Login-View-Controller zu erstellen. In meiner Tab Bar Controller-Klasse überprüfe ich, ob sie innerhalb der viewDidAppearMethode angemeldet sind , und führe die folgenden Schritte aus:[self performSegueWithIdentifier:@"pushLogin" sender:self];

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

Nach dem Abmelden lösche ich die Anmeldeinformationen aus dem Schlüsselbund, führe sie aus [self setSelectedIndex:0]und führe den Übergang durch, um den Controller für die Anmeldeansicht erneut anzuzeigen.

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

  • Beim ersten Start der App wird der Tab Bar Controller kurz vor dem Segue angezeigt. Ich habe versucht, den Code zu verschieben, viewWillAppearaber der Übergang wird nicht so früh funktionieren.
  • Wenn sie sich abmelden, befinden sich alle Daten 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 überlegt, den Anmeldebildschirm zum Root-View-Controller zu machen oder einen Navigations-Controller im AppDelegate zu erstellen, um alles zu handhaben ... Ich bin mir nur nicht sicher, welche Methode derzeit die beste ist.


Präsentieren Sie den Login View Controller als modal?
Vokilam

@ TrevorGehman - kann Ihr Storyboard-Bild hinzufügen
rohan k shah

Ich reichte eine Antwort mit den Details meiner Arbeit ein. Es ähnelt einigen anderen Antworten, insbesondere @bhavya kothari.
Trevor Gehman

Für die Darstellung des Anmeldebildschirms kann AuthNavigation hilfreich sein. Es organisiert bei Bedarf die Darstellung eines Anmeldebildschirms und unterstützt auch die automatische Anmeldung.
Codey

Eines der sehr grundlegenden Probleme, das fast immer gelöst ist, sich aber gleichzeitig besser hätte lösen können
amar

Antworten:


311

Ihr Storyboard sollte so aussehen

In Ihrem appDelegate.m in Ihrem 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;

}

Swift 4 Version

didFinishLaunchingWithOptions im App-Delegaten unter der Annahme, dass Ihr anfänglicher Ansichts-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

In 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

Sie haben vergessen, die Bool-Authentifizierung aus userDefaults nach dem Abmelden zu
löschen

28
-1 für die Verwendung im AppDelegateInneren UIViewControllerund die Einstellung window.rootViewControllerdort. Ich betrachte dies nicht als "Best Practice".
derpoliuk

2
Ich wollte nicht geben, -1ohne eine Antwort zu schreiben
derpoliuk

1
Ich versuche dies schnell unter IOS8 zu tun, aber ich erhalte die folgende Fehlermeldung, wenn die App gestartet wird und der Anmeldebildschirm anzeigt: "Unausgeglichene Anrufe zum Beginn / Ende von Erscheinungsübergängen". Ich habe festgestellt, dass beim Laden der App der Anmeldebildschirm angezeigt wird, aber auch die erste Registerkarte auf dem Registerkartenleisten-Controller geladen wird. Bestätigt dies über println () in viewdidload. Vorschläge?
Alex Lacayo

1
Bingo! -2. -1 für AppDelegateinnen UIViewController-1 zum Speichern des Anmeldeschlüssels NSUserDefaults. Es ist sehr, sehr unsicher für diese Art von Daten!
Skywinder

97

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

Storyboard mit Controller für die Anmeldeansicht und Controller für die Hauptregisterkarte

Wie Sie sehen können, ist der Root-View-Controller mein Main Tab Controller . Ich habe dies getan, weil ich möchte, dass die App direkt nach dem Anmelden des Benutzers direkt auf der ersten Registerkarte gestartet wird. (Dies vermeidet jegliches "Flackern", bei dem die Anmeldeansicht vorübergehend angezeigt wird.)

AppDelegate.m

In dieser Datei überprüfe ich, ob der Benutzer bereits angemeldet ist. Wenn nicht, drücke ich den Anmeldeansichts-Controller. Ich kümmere mich auch um den Abmeldevorgang, bei dem ich Daten lösche und die Anmeldeansicht anzeige.

- (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, schließe ich hier einfach die Ansicht und sende eine Benachrichtigung.

-(void) loginWasSuccessful
{

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

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

}

2
Wofür verwenden Sie die Benachrichtigung?
Rebellion

1
@BFeher ist richtig. Ich habe die Benachrichtigung verwendet, um einen neuen Datenabruf auszulösen. Sie können damit tun, was Sie wollen, aber in meinem Fall musste ich benachrichtigt werden, dass die Anmeldung erfolgreich war und neue Daten benötigt wurden.
Trevor Gehman

24
In iOS 8.1 (und vielleicht 8.0, noch nicht getestet) funktioniert dies nicht mehr reibungslos. Der anfängliche View Controller blinkt für einen kurzen Moment.
BFeher

7
Gibt es eine Swift-Version dieses Ansatzes?
Seano

9
@Julian In iOS 8 ersetze ich die beiden Zeilen [self.window makeKeyAndVisible]; [self.window.rootViewController presentViewController:viewController animated:animated completion:nil];durch self.window.rootViewController = viewController;, um das Flackern zu verhindern. Um das zu animieren, wickeln Sie es einfach in ein[UIView transitionWithView...];
BFeher

20

BEARBEITEN: Abmeldeaktion hinzufügen.

Geben Sie hier die Bildbeschreibung ein

1. Bereiten Sie zunächst 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 mit dem Namen Benutzer.

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. Erstellen Sie einen neuen Controller RootViewController und verbinden Sie ihn mit der ersten Ansicht, in der sich die Anmeldeschaltfläche befindet. 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, der mit der Anmeldeansicht verbunden ist.

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, der mit der Profilansicht im tabViewController verbunden ist.

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


3
Beispielprojekt hat mir sehr geholfen, das Konzept der Anmeldung und Abmeldung zu verstehen. Vielen Dank :)
Dave

16

Ich mochte die Antwort von bhavya nicht, weil sie AppDelegatein View Controllern verwendet wurde und die Einstellung rootViewControllerkeine Animation enthält. Und Trevors Antwort hat ein Problem mit dem Flashing View Controller unter iOS8.

UPD 18.07.2015

AppDelegate in View Controllern:

Durch Ändern des AppDelegate-Status (Eigenschaften) im View Controller wird die Kapselung unterbrochen.

Sehr einfache Objekthierarchie in jedem iOS-Projekt:

AppDelegate (besitzt windowund rootViewController)

ViewController (besitzt view)

Es ist in Ordnung, dass Objekte von oben Objekte unten ändern, weil sie sie erstellen. Es ist jedoch nicht in Ordnung, wenn Objekte unten Objekte darüber ändern (ich habe einige grundlegende Programmier- / OOP-Prinzipien beschrieben: DIP (Prinzip der Abhängigkeitsinversion: Das Modul auf hoher Ebene darf nicht vom Modul auf niedriger Ebene abhängen, sondern sollte von Abstraktionen abhängen). ).

Wenn ein Objekt ein Objekt in dieser Hierarchie ändert, wird der Code früher oder später durcheinander geraten. Es mag bei kleinen Projekten in Ordnung sein, aber es macht keinen Spaß, dieses Durcheinander bei den Bit-Projekten zu durchforsten =]

UPD 18.07.2015

Ich repliziere Modal Controller Animationen mit UINavigationController(tl; dr: check the project ).

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

Wie es funktioniert:

  1. Der anfängliche View Controller (oder self.window.rootViewController) ist UINavigationController mit ProgressViewController als rootViewController. Ich zeige ProgressViewController, da die Initialisierung von DataModel einige Zeit in Anspruch nehmen kann, da es den Kerndatenstapel wie in diesem Artikel enthält (dieser Ansatz gefällt mir sehr gut).

  2. AppDelegate ist für das Abrufen von Anmeldestatusaktualisierungen verantwortlich.

  3. DataModel übernimmt die Benutzeranmeldung / -abmeldung und AppDelegate beobachtet seine userLoggedInEigenschaft über KVO. Wohl nicht die beste Methode, aber das funktioniert bei mir. (Warum KVO schlecht ist, können Sie in diesem oder diesem Artikel nachlesen (Warum nicht Benachrichtigungen verwenden? Teil).

  4. ModalDismissAnimator und ModalPresentAnimator werden verwendet, um die Standard-Push-Animation anzupassen.

So funktioniert die Animatorlogik:

  1. AppDelegate legt sich als Delegat von fest self.window.rootViewController(dies ist UINavigationController).

  2. AppDelegate gibt -[AppDelegate navigationController:animationControllerForOperation:fromViewController:toViewController:]bei Bedarf einen der Animatoren zurück .

  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 da .


3
Persönlich habe ich kein Problem damit, dass View Controller Bescheid wissen AppDelegate(ich würde gerne verstehen, warum Sie dies tun) - aber Ihr Kommentar zum Mangel an Animation ist sehr gültig. Das kann durch diese Antwort gelöst werden: stackoverflow.com/questions/8053832/…
HughHughTeotl

2
@ HughHughTeotl Danke für den Kommentar und den Link. Ich habe meine Antwort aktualisiert.
derpoliuk

1
@derpoliuk Was ist, wenn mein Basisansichts-Controller ein UITabBarController ist? Ich kann es nicht in einen UINavigationController schieben.
Giorgio

@Giorgio, es ist eine interessante Frage, die ich lange nicht benutzt UITabBarControllerhabe. Ich würde wahrscheinlich mit dem Fensteransatz beginnen, anstatt die Ansichtssteuerungen zu manipulieren.
derpoliuk

11

Hier ist meine Swifty-Lösung für alle zukünftigen Zuschauer.

1) Erstellen Sie ein Protokoll für die Anmelde- und Abmeldefunktionen:

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

2) Erweitern Sie das Protokoll und stellen Sie hier die Funktionen zum Abmelden bereit:

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) Dann kann ich mein AppDelegate an das LoginFlowHandler-Protokoll anpassen und handleLoginbeim Start Folgendes aufrufen :

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
    }

}

Von hier aus übernimmt meine Protokollerweiterung die Logik oder bestimmt, ob der Benutzer angemeldet / abgemeldet ist, und ändert dann den Windows-RootViewController entsprechend!


Ich bin mir nicht sicher, ob ich dumm bin, aber AppDelegate entspricht nicht LoginFlowHandler. Vermisse ich etwas Ich vermute auch, dass dieser Code die Anmeldung nur beim Start verwaltet. Wie verwalte ich die Abmeldung von einem View Controller?
Luke

@luke Da die gesamte Logik in der Erweiterung implementiert ist, muss sie nicht im AppDelegate implementiert werden. Das ist es, was in Protocol Extensions so großartig ist.
Shannoga

1
Sorry @sirFunkenstine, das war eine benutzerdefinierte Klasse, die ich erstellt habe, um ein Beispiel dafür zu zeigen, wie man den App-Cache überprüft, um zu überprüfen, ob sich ein Benutzer zuvor angemeldet hat oder nicht. Diese AppStateImplementierung hängt daher davon ab, wie Sie Ihre Benutzerdaten auf der Festplatte speichern.
Harry Bloom

@ HarryBloom Wie würde man die handleLogoutFunktionalität nutzen?
Nithinisreddy

1
Hallo @nithinisreddy - um die handleLogout-Funktionalität aufzurufen, müssen Sie die Klasse, von der Sie aufrufen, an das LoginFlowHandlerProtokoll anpassen . Anschließend erhalten Sie einen Bereich, in dem Sie die handleLogout-Methode aufrufen können. In Schritt 3 finden Sie ein Beispiel dafür, wie ich das für die AppDelegate-Klasse gemacht habe.
Harry Bloom

8

Dies vom App-Delegaten aus zu tun, wird NICHT empfohlen. AppDelegate verwaltet den App-Lebenszyklus, der sich auf das Starten, Anhalten, Beenden usw. bezieht. Ich schlage vor, dies von Ihrem anfänglichen View-Controller in der zu tun viewDidAppear. Sie können self.presentViewControllerund self.dismissViewControllervon der Anmeldeansicht Controller. Speichern Sie einen boolSchlüssel, NSUserDefaultsum festzustellen, ob er zum ersten Mal gestartet wird.


2
Sollte die Ansicht in `viewDidAppear 'erscheinen (für den Benutzer sichtbar sein)? Dies erzeugt immer noch ein Flimmern.
Mark13426

2
Keine Antwort. Und "Speichern Sie einen Bool-Schlüssel in NSUserDefaults, um zu sehen, ob er zum ersten Mal gestartet wird." Ist für diese Art von Daten sehr, sehr gefährlich.
Skywinder

6

Erstellen Sie ** LoginViewController ** und ** TabBarController **.

Nach dem Erstellen des LoginViewController und des TabBarControllers müssen wir eine StoryboardID als " loginViewController " bzw. " tabBarController " hinzufügen .

Dann ziehe ich es vor, die Konstantenstruktur zu erstellen :

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

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

In LoginViewController hinzufügen IBAction :

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

In ProfileViewController hinzufügen IBAction :

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

In AppDelegate Codezeilen in hinzufügen didFinishLaunchingWithOptions :

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

    Switcher.updateRootViewController()

    return true
}

Erstellen Sie schließlich die Switcher- Klasse:

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

    }

}

Das ist alles!


Gibt es einen Unterschied, welcher View Controller in Storyboards initial ist? In Ihrem hinzugefügten Foto kann ich sehen, dass Sie die Option "Ist Initial View Controller" auf der Registerkarte "Leistensteuerung" aktiviert haben. In AppDelegate wechseln Sie den Haupt-Root-View-Controller, sodass es wohl keine Rolle spielt, oder?
ShadeToD

@iAleksandr Bitte aktualisieren Sie die Antwort für iOS 13. Coz of SceneDelegate Die aktuelle Antwort funktioniert nicht.
Nitesh

5

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

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

Und hier ist die Swift-Version zum Festlegen 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")

3

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 Benutzer die App löscht und neu installiert, zählt dies wie ein erster Start.)

Im AppDelegate überprüfe ich den ersten Start und erstelle einen Navigations-Controller mit den Anmeldebildschirmen (Anmelden und Registrieren), die ich über das aktuelle Hauptfenster lege:

[self.window makeKeyAndVisible];

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

Da dies über dem normalen Ansichts-Controller liegt, ist es unabhängig vom Rest Ihrer App und Sie können den Ansichts-Controller einfach schließen, wenn Sie ihn nicht mehr benötigen. Auf diese Weise können Sie die Ansicht auch darstellen, wenn der Benutzer eine Taste manuell drückt.

Übrigens: Ich speichere die Login-Daten meiner Benutzer folgendermaßen:

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 entfernt (zu langsam) und verwende jetzt NSArrays und NSDictionaries, um meine Daten zu verwalten. Abmelden bedeutet nur, diese Arrays und Wörterbücher zu leeren. Außerdem stelle ich sicher, dass meine Daten in viewWillAppear festgelegt sind.

Das ist es.


0

Ich bin in der gleichen Situation wie Sie und die Lösung, die ich zum Bereinigen der Daten gefunden habe, besteht darin, alle CoreData-Inhalte zu löschen, auf die sich meine View-Controller verlassen, um ihre Informationen zu zeichnen. Aber ich fand diesen Ansatz immer noch sehr schlecht. Ich denke, dass ein eleganterer Weg, dies zu tun, ohne Storyboards und nur mit Code zum Verwalten der Übergänge zwischen Ansichts-Controllern erreicht werden kann.

Ich habe dieses Projekt bei Github gefunden, das all diese Dinge nur per Code erledigt und ziemlich einfach zu verstehen ist. Sie verwenden ein Facebook-ähnliches Seitenmenü und ändern den Controller für die mittlere Ansicht, je nachdem, ob der Benutzer angemeldet ist oder nicht. Wenn sich der Benutzer abmeldet, werden die appDelegateDaten aus CoreData entfernt und der Hauptansichts-Controller erneut auf den Anmeldebildschirm gesetzt.


0

Ich hatte ein ähnliches Problem in einer App zu lösen und habe die folgende Methode verwendet. Ich habe keine Benachrichtigungen für die Navigation verwendet.

Ich habe drei Storyboards in der App.

  1. Splash Screen Storyboard - zur App-Initialisierung und zur Überprüfung, ob der Benutzer bereits angemeldet ist
  2. Login-Storyboard - zur Abwicklung des Benutzeranmeldeflusses
  3. Registerkarten-Storyboard - zum Anzeigen des App-Inhalts

Mein erstes Storyboard in der App ist das Splash Screen Storyboard. Ich habe den Navigations-Controller als Stamm des Logins für Login und Registerkartenleiste, um die Navigation des View-Controllers zu verwalten.

Ich habe eine Navigator-Klasse für die App-Navigation erstellt und sie sieht folgendermaßen aus:

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)

           }

       }

   }

}

Schauen wir uns die möglichen Szenarien an:

  • Erster App-Start; Der Begrüßungsbildschirm wird geladen, wo ich überprüfe, ob der Benutzer bereits angemeldet ist. Dann wird der Anmeldebildschirm mit der Navigator-Klasse wie folgt geladen.

Da ich den Navigationscontroller als Root habe, instanziiere ich den Navigationscontroller als anfänglichen Ansichtscontroller.

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

let loginNav = loginSB.instantiateInitialViewcontroller() as! UINavigationController

Navigator.moveTo(loginNav, from: self)

Dadurch wird das Slpash-Storyboard aus dem Stammverzeichnis des App-Fensters entfernt und durch das Login-Storyboard ersetzt.

Wenn der Benutzer im Login-Storyboard erfolgreich angemeldet ist, speichere ich die Benutzerdaten in den Benutzerstandards und initialisiere einen UserData-Singleton, um auf die Benutzerdetails zuzugreifen. Anschließend wird das Storyboard der Registerkartenleiste mithilfe der Navigatormethode geladen.

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

Navigator.moveTo(tabBarNav, from: self)

Jetzt meldet sich der Benutzer vom Einstellungsbildschirm in der Registerkartenleiste ab. Ich lösche alle gespeicherten Benutzerdaten und navigiere zum Anmeldebildschirm.

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

let loginNav = loginSB.instantiateInitialViewcontroller() as! UINavigationController

Navigator.moveTo(loginNav, from: self)
  • Der Benutzer ist angemeldet und erzwingt das Beenden der App

Wenn der Benutzer die App startet, wird der Begrüßungsbildschirm geladen. Ich überprüfe, ob der Benutzer angemeldet ist und greife über die Standardeinstellungen auf die Benutzerdaten zu. Initialisieren Sie dann den UserData-Singleton und zeigen Sie die Registerkartenleiste anstelle des Anmeldebildschirms an.


-1

Vielen Dank an Bhavyas Lösung. Es gab zwei Antworten zu Swift, aber diese sind nicht sehr intakt. Ich habe das im swift3 gemacht. 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 der 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()
}

Hallo Eli. Die Frage, die Sie beantwortet haben, hat bereits einige wirklich gute Antworten. Wenn Sie sich entscheiden, eine solche Frage zu beantworten, erklären Sie bitte, warum Ihre Antwort besser ist als die sehr guten, die bereits veröffentlicht wurden.
Noel Widmer

Hallo Noel. Ich bemerkte die anderen Antworten für schnell. Aber ich dachte, die Antworten sind nicht sehr intakt. Also reiche ich meine Antwort zur Swift3-Version ein. Es wäre Hilfe für neue schnelle Programmierer. Danke! @Noel Widmer.
WangYang

Können Sie diese Erklärung oben in Ihrem Beitrag hinzufügen? Auf diese Weise kann jeder sofort den Nutzen Ihrer Antwort erkennen. Viel Spaß auf SO! :)
Noel Widmer

1
Tanks für Ihren Vorschlag. Ich habe die Erklärung hinzugefügt. Nochmals vielen Dank. @ Noel Widmer.
WangYang

Vage Lösung, bei der die Verwendung des Schlüsselworts "Common" nicht hervorgehoben wird.
Samarey

-3

Geben Sie hier die Bildbeschreibung ein

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 wurde geladen

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

}}

In der Abmeldeschaltflächenaktion

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

Warum müssen der Datei ViewController.m einige Funktionen hinzugefügt werden?
Eesha

@Eesha Er hat der TabBar ein TabBar-Schaltflächenelement zum Abmelden hinzugefügt. Ich denke, das Bild fehlt, sonst hättest du es sehen können.
Hallo Welt

Das Speichern des Anmeldeschlüssels NSUserDefaultsist für diese Art von Daten sehr, sehr unsicher!
Skywinder
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.