Najlepsze praktyki dotyczące ekranu logowania Storyboard, obsługa usuwania danych po wylogowaniu

290

Buduję aplikację na iOS za pomocą Storyboard. Kontroler widoku głównego jest kontrolerem paska kart. Tworzę proces logowania / wylogowywania, i to w większości działa dobrze, ale mam kilka problemów. Muszę znać NAJLEPSZY sposób na skonfigurowanie tego wszystkiego.

Chcę wykonać następujące czynności:

  1. Pokaż ekran logowania przy pierwszym uruchomieniu aplikacji. Po zalogowaniu przejdź do pierwszej karty Kontrolera paska kart.
  2. Za każdym razem, gdy uruchamiają aplikację, sprawdź, czy są zalogowani i przejdź bezpośrednio do pierwszej karty głównego kontrolera paska kart.
  3. Kiedy ręcznie klikną przycisk wylogowania, pokaż ekran logowania i usuń wszystkie dane z kontrolerów widoku.

Do tej pory ustawiłem kontroler widoku głównego na kontroler paska kart i utworzyłem niestandardowy segment dla mojego kontrolera widoku logowania. W mojej klasie Tab Bar Controller sprawdzam, czy są zalogowani w viewDidAppearmetodzie, i wykonuję segue:[self performSegueWithIdentifier:@"pushLogin" sender:self];

Ustawiam również powiadomienie, kiedy należy wykonać akcję wylogowania: [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(logoutAccount) name:@"logoutAccount" object:nil];

Po wylogowaniu usuwam dane uwierzytelniające z pęku kluczy, uruchamiam [self setSelectedIndex:0]i wykonuję segue, aby ponownie wyświetlić kontroler widoku logowania.

To wszystko działa dobrze, ale zastanawiam się: czy ta logika powinna być w AppDelegate? Mam również dwa problemy:

  • Przy pierwszym uruchomieniu aplikacji kontroler paska kart wyświetla się krótko przed wykonaniem segue. Próbowałem przenieść kod do, viewWillAppearale segue nie zadziała tak wcześnie.
  • Po wylogowaniu wszystkie dane nadal znajdują się we wszystkich kontrolerach widoku. Jeśli zalogują się na nowe konto, dane starego konta będą nadal wyświetlane, dopóki nie zostaną odświeżone. Potrzebuję sposobu, aby to łatwo usunąć przy wylogowaniu.

Jestem otwarty na przeróbkę tego. Rozważyłem uczynienie ekranu logowania głównym kontrolerem widoku lub utworzenie kontrolera nawigacyjnego w AppDelegate do obsługi wszystkiego ... Po prostu nie jestem pewien, jaka jest najlepsza metoda w tym momencie.

Trevor Gehman
źródło
Czy prezentujesz kontroler widoku logowania jako modalny?
vokilam
@TrevorGehman - można dodać zdjęcie ze scenorysu
rohan k shah
Udzieliłem odpowiedzi ze szczegółami tego, co skończyłem. Jest podobny do niektórych innych udzielonych odpowiedzi, zwłaszcza @bhavya kothari.
Trevor Gehman
Przy wyświetlaniu ekranu logowania przydatne może być AuthNavigation . W razie potrzeby organizuje prezentację ekranu logowania, a także obsługuje automatyczne logowanie.
Codey,
Jeden z bardzo podstawowych problemów, który prawie zawsze jest rozwiązywany, ale jednocześnie wydaje się, że można go było rozwiązać lepiej
am

Odpowiedzi:

310

Twoja plansza powinna wyglądać tak

W appDelegate.m wewnątrz 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;
}

W pliku SignUpViewController.m

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

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

W pliku 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;

}

Wersja Swift 4

didFinishLaunchingWithOptions w delegacie aplikacji, zakładając, że początkowy kontroler widoku jest podpisany w TabbarController.

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

    return true

W widoku kontrolera rejestracji:

@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
źródło
Zapomniałeś usunąć uwierzytelnienia bool z userDefaults po wylogowaniu
CodeLover
28
-1 do używania AppDelegatewewnątrz UIViewControlleri ustawiania window.rootViewControllertam. Nie uważam tego za „najlepszą praktykę”.
derpoliuk
2
Nie chciałem dawać -1bez opublikowania odpowiedzi: stackoverflow.com/a/30664935/1226304
derpoliuk
1
Próbuję to zrobić szybko na IOS8, ale pojawia się następujący błąd, gdy aplikacja uruchamia się, a ekran logowania pokazuje: „Niezrównoważone połączenia do rozpoczęcia / zakończenia zmiany wyglądu”. Zauważyłem, że gdy aplikacja ładuje się, wyświetla się ekran logowania, ale ładowana jest również pierwsza karta na pasku kart kontrolera. Potwierdzono to za pomocą println () w viewdidload. Propozycje?
Alex Lacayo
1
bingo! -2. -1 do AppDelegatewewnątrz UIViewController-1 do przechowywania klucza logowania w NSUserDefaults. Jest bardzo niepewny dla tego rodzaju danych!
skywinder,
97

Oto, co zrobiłem, aby wszystko osiągnąć. Jedyną rzeczą, którą musisz wziąć pod uwagę oprócz tego jest (a) proces logowania i (b) gdzie przechowujesz dane aplikacji (w tym przypadku użyłem singletonu).

Scenorys pokazujący kontroler widoku logowania i kontroler głównej karty

Jak widać, kontroler widoku głównego jest moim kontrolerem karty głównej . Zrobiłem to, ponieważ po zalogowaniu się użytkownika chcę, aby aplikacja uruchomiła się bezpośrednio na pierwszej karcie. (Pozwala to uniknąć „migotania”, gdy widok logowania pokazuje się tymczasowo).

AppDelegate.m

W tym pliku sprawdzam, czy użytkownik jest już zalogowany. Jeśli nie, pcham kontroler widoku logowania. Zajmuję się także procesem wylogowania, w którym usuwam dane i wyświetlam widok logowania.

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

Tutaj, jeśli logowanie się powiedzie, po prostu odrzucam widok i wysyłam powiadomienie.

-(void) loginWasSuccessful
{

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

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

}
Trevor Gehman
źródło
2
Do czego używasz powiadomienia?
bunt
1
@BFeher ma rację. Skorzystałem z powiadomienia, aby uruchomić nowe pobieranie danych. Możesz go używać do robienia wszystkiego, co chcesz, ale w moim przypadku musiałem zostać powiadomiony, że logowanie się powiodło i potrzebne były nowe dane.
Trevor Gehman
24
W iOS 8.1 (i być może 8.0, nie testowałem) nie działa to już płynnie. Początkowy kontroler widoku miga przez chwilę.
BFeher
7
Czy istnieje szybka wersja tego podejścia?
Seano
9
@Julian w IOS 8 wymienić dwie linie [self.window makeKeyAndVisible]; [self.window.rootViewController presentViewController:viewController animated:animated completion:nil];z self.window.rootViewController = viewController;celu zapobiegania migotania. Aby ożywić, po prostu zawiń go w[UIView transitionWithView...];
BFeher
20

EDYCJA: Dodaj akcję wylogowania.

wprowadź opis zdjęcia tutaj

1. Przede wszystkim przygotuj plik delegowany aplikacji

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. Utwórz klasę o nazwie Użytkownik.

Użytkownik. H

#import <Foundation/Foundation.h>

@interface User : NSObject

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

@end

Użytkownik.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. Utwórz nowy kontroler RootViewController i połączony z pierwszym widokiem, w którym znajduje się przycisk logowania. Dodaj także identyfikator serii ujęć: „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. Utwórz nowy kontroler LoginViewController i połączony z widokiem logowania.

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. Na koniec dodaj nowy kontroler ProfileViewController i połączony z widokiem profilu w tabViewController.

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 to przykładowy projekt zapewniający dodatkową pomoc.

Dimitris Bouzikas
źródło
3
przykładowy projekt bardzo pomógł mi zrozumieć koncepcję wylogowania i wylogowania .. wielkie dzięki :)
Dave
16

Nie podobała mi się odpowiedź bhavyi z powodu używania AppDelegatewewnątrz kontrolerów widoku, a ustawienie rootViewControllernie ma animacji. Odpowiedź Trevora dotyczy problemu z flashowaniem kontrolera widoku na iOS8.

UPD 18.07.2015

AppDelegate wewnątrz kontrolerów widoku:

Zmiana stanu (właściwości) AppDelegate w kontrolerze widoku przerywa enkapsulację.

Bardzo prosta hierarchia obiektów w każdym projekcie iOS:

AppDelegate (właściciel windowi rootViewController)

ViewController (właścicielem view)

Jest ok, że obiekty z góry zmieniają obiekty na dole, ponieważ je tworzą. Ale nie jest ok, jeśli obiekty na dole zmieniają obiekty na nich (opisałem kilka podstawowych zasad programowania / OOP: DIP (Zasada odwrócenia zależności: moduł wysokiego poziomu nie może zależeć od modułu niskiego poziomu, ale powinien zależeć od abstrakcji) ).

Jeśli jakikolwiek obiekt zmieni dowolny obiekt w tej hierarchii, wcześniej czy później w kodzie pojawi się bałagan. W małych projektach może być w porządku, ale nie jest fajnie przekopać się przez ten bałagan w projektach bitowych =]

UPD 18.07.2015

Replikuję animacje kontrolera modalnego za pomocą UINavigationController(tl; dr: sprawdź projekt ).

Używam UINavigationControllerdo prezentacji wszystkich kontrolerów w mojej aplikacji. Początkowo wyświetlałem kontroler widoku logowania na stosie nawigacji z prostą animacją push / pop. Następnie postanowiłem zmienić go na modalny z minimalnymi zmianami.

Jak to działa:

  1. Kontroler widoku początkowego (lub self.window.rootViewController) jest UINavigationController z ProgressViewController jako rootViewController. Pokazuję ProgressViewController, ponieważ inicjowanie DataModel może trochę potrwać, ponieważ rozpoczyna on stos danych podstawowych jak w tym artykule (bardzo podoba mi się to podejście).

  2. AppDelegate jest odpowiedzialny za uzyskiwanie aktualizacji statusu logowania.

  3. DataModel obsługuje logowanie / wylogowanie użytkownika, a AppDelegate obserwuje jego userLoggedInwłaściwość za pośrednictwem KVO. Prawdopodobnie nie jest to najlepsza metoda, ale działa dla mnie. (Dlaczego KVO jest złe, możesz sprawdzić w tym lub w tym artykule (część Dlaczego nie korzystać z powiadomień?).

  4. ModalDismissAnimator i ModalPresentAnimator służą do dostosowania domyślnej animacji push.

Jak działa logika animatorów:

  1. AppDelegate ustawia się jako delegat self.window.rootViewController(który jest UINavigationController).

  2. AppDelegate zwraca w -[AppDelegate navigationController:animationControllerForOperation:fromViewController:toViewController:]razie potrzeby jednego z animatorów .

  3. Animatorzy wdrażają -transitionDuration:i -animateTransition:metody. -[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]];
         }];
    }

Projekt testowy jest tutaj .

derpoliuk
źródło
3
Osobiście nie mam problemu z tym, że kontrolery widoku wiedzą AppDelegate(chciałbym zrozumieć, dlaczego tak robisz) - ale twój komentarz na temat braku animacji jest bardzo poprawny. Można to rozwiązać za pomocą tej odpowiedzi: stackoverflow.com/questions/8053832/...
HughHughTeotl
2
@HughHughTeotl Dziękujemy za komentarz i link. Zaktualizowałem swoją odpowiedź.
derpoliuk
1
@derpoliuk co jeśli mój kontroler widoku podstawowego jest UITabBarController? Nie mogę wcisnąć go w UINavigationController.
Giorgio
@Giorgio, to ciekawe pytanie, z którego nie korzystałem UITabBarControllerzbyt długo. Prawdopodobnie zacznę od podejścia do okna zamiast manipulowania kontrolerami widoku.
derpoliuk
11

Oto moje rozwiązanie Swifty dla przyszłych obserwatorów.

1) Utwórz protokół do obsługi funkcji logowania i wylogowania:

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

2) Rozszerz wspomniany protokół i podaj tutaj funkcję wylogowania:

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) Następnie mogę dostosować moją aplikację AppDelegate do protokołu LoginFlowHandler i wywołać handleLoginprzy starcie:

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
    }

}

Stąd moje rozszerzenie protokołu zajmie się logiką lub określi, czy użytkownik jest zalogowany / wylogowany, a następnie odpowiednio zmieni rootViewController systemu Windows!

Harry Bloom
źródło
Nie jestem pewien, czy jestem głupi, ale AppDelegate nie jest zgodny LoginFlowHandler. Czy coś brakuje? Zgaduję też, że ten kod zarządza logowaniem tylko podczas uruchamiania. Jak zarządzać wylogowaniem z kontrolera widoku?
luke
@luke, ponieważ cała logika jest zaimplementowana w rozszerzeniu, nie ma potrzeby implementowania jej w AppDelegate. To jest takie świetne w rozszerzeniach protokołów.
shannoga,
1
Przepraszamy @sirFunkenstine, to była klasa niestandardowa, którą utworzyłem, aby pokazać przykład sprawdzania pamięci podręcznej aplikacji w celu sprawdzenia, czy użytkownik wcześniej się zalogował. Ta AppStateimplementacja zależy zatem od sposobu zapisywania danych użytkownika na dysku.
Harry Bloom
@HarryBloom jak można korzystać z tej handleLogoutfunkcji?
nithinisreddy
1
Cześć @ nithinisreddy - aby wywołać funkcję handleLogout, musisz dostosować klasę, z której dzwonisz, do LoginFlowHandlerprotokołu. Następnie uzyskasz zasięg, aby móc wywołać metodę handleLogout. Zobacz mój krok 3, aby zobaczyć, jak to zrobiłem dla klasy AppDelegate.
Harry Bloom,
8

Robienie tego z poziomu delegata aplikacji NIE jest zalecane. AppDelegate zarządza cyklem życia aplikacji związanym z uruchamianiem, zawieszaniem, kończeniem i tak dalej. Sugeruję zrobienie tego z początkowego kontrolera widoku w viewDidAppear. Możesz self.presentViewControlleriz self.dismissViewControllerkontrolera widoku logowania. Zapisz boolklucz, NSUserDefaultsaby sprawdzić, czy uruchamia się po raz pierwszy.

Mihado
źródło
2
Czy widok powinien pojawić się (być widoczny dla użytkownika) w `viewDidAppear '? Nadal spowoduje to migotanie.
Mark13426,
2
Nie odpowiedź. I „Przechowuj klucz bool w NSUserDefaults, aby zobaczyć, czy uruchamia się po raz pierwszy”. Jest bardzo niebezpieczne dla tego rodzaju danych.
skywinder
6

Utwórz ** LoginViewController ** i ** TabBarController **.

Po utworzeniu LoginViewController i TabBarController musimy dodać StoryboardID odpowiednio jako „ loginViewController ” i „ tabBarController ”.

Następnie wolę stworzyć strukturę Constant :

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

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

W LoginViewController dodaj IBAction :

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

W ProfileViewController dodaj IBAction :

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

W AppDelegate dodaj wiersz kodu w didFinishLaunchingWithOptions :

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

    Switcher.updateRootViewController()

    return true
}

Na koniec utwórz klasę 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

    }

}

To wszystko!

iAleksandr
źródło
Czy jest jakaś różnica, który kontroler widoku jest początkowy w scenorysach? Na dodanym zdjęciu widzę, że masz opcję „kontroler widoku początkowego” zaznaczoną na kontrolerze Tab Bar. W AppDelegate przełączasz główny kontroler widoku głównego, więc myślę, że to nie ma znaczenia, prawda?
ShadeToD
@iAleksandr Zaktualizuj odpowiedź na iOS 13. Nie działa bieżąca odpowiedź SceneDelegate.
Nitesh,
5

W Xcode 7 możesz mieć wiele storyBoardów. Lepiej będzie, jeśli możesz zachować przepływ logowania w osobnej scenorysie.

Można to zrobić za pomocą WYBIERZ WIDOK KONTROLERA> Edytor> Refaktoryzuj do scenorysu

A oto wersja Swift do ustawiania widoku jako RootViewContoller-

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

    let rootViewController: UIViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("LoginViewController")
Mahbub Morshed
źródło
3

Używam tego do sprawdzenia pierwszego uruchomienia:

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

(jeśli użytkownik usunie aplikację i ponownie ją zainstaluje, liczy się to jako pierwsze uruchomienie)

W AppDelegate sprawdzam pierwsze uruchomienie i tworzę kontroler nawigacyjny z ekranami logowania (login i rejestracja), które umieszczam na górze bieżącego okna głównego:

[self.window makeKeyAndVisible];

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

Ponieważ jest to zwykły kontroler widoku, jest on niezależny od reszty aplikacji i możesz po prostu zamknąć kontroler widoku, jeśli nie jest już potrzebny. Możesz również przedstawić widok w ten sposób, jeśli użytkownik ręcznie naciśnie przycisk.

BTW: Zapisuję dane logowania od moich użytkowników w następujący sposób:

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

Aby się wylogować: przełączyłem się z CoreData (zbyt wolno) i teraz używam NSArrays i NSDictionaries do zarządzania moimi danymi. Wylogowanie oznacza po prostu opróżnienie tych tablic i słowników. Ponadto upewnij się, że ustawiłem moje dane w widoku Widoczność.

Otóż ​​to.

Thorsten
źródło
0

Jestem w tej samej sytuacji co ty i rozwiązanie, które znalazłem do czyszczenia danych, usuwa wszystkie rzeczy CoreData, na których polegają moje kontrolery widoku, aby narysować informacje. Ale nadal uważałem to podejście za bardzo złe, myślę, że bardziej elegancki sposób można to osiągnąć bez scenorysów i używając tylko kodu do zarządzania przejściami między kontrolerami widoku.

Znalazłem ten projekt w Github, który robi wszystkie te rzeczy tylko za pomocą kodu i jest dość łatwy do zrozumienia. Korzystają z bocznego menu przypominającego Facebooka i zmieniają kontroler widoku centralnego w zależności od tego, czy użytkownik jest zalogowany, czy nie. Gdy użytkownik się appDelegatewyloguje, usuwa dane z CoreData i ponownie ustawia główny kontroler widoku na ekran logowania.

amb
źródło
0

Miałem podobny problem do rozwiązania w aplikacji i zastosowałem następującą metodę. Nie korzystałem z powiadomień do obsługi nawigacji.

W aplikacji mam trzy scenorysy.

  1. Scenariusz ekranu powitalnego - do inicjalizacji aplikacji i sprawdzania, czy użytkownik jest już zalogowany
  2. Scenariusz logowania - do obsługi przepływu logowania użytkownika
  3. Storyboard z paskiem zakładek - do wyświetlania zawartości aplikacji

Moje początkowe scenorysy w aplikacji to scenorysy z ekranem powitalnym. Mam kontroler nawigacyjny jako korzeń loginu i paska zadań, aby obsłużyć nawigację po widoku kontrolera.

Utworzyłem klasę Navigator do obsługi nawigacji w aplikacji i wygląda to tak:

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)

           }

       }

   }

}

Spójrzmy na możliwe scenariusze:

  • Pierwsze uruchomienie aplikacji; Zostanie załadowany ekran powitalny, w którym sprawdzę, czy użytkownik jest już zalogowany. Następnie ekran logowania zostanie załadowany za pomocą klasy Navigator w następujący sposób;

Ponieważ mam kontroler nawigacji jako root, tworzę instancję kontrolera nawigacji jako kontrolera widoku początkowego.

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

let loginNav = loginSB.instantiateInitialViewcontroller() as! UINavigationController

Navigator.moveTo(loginNav, from: self)

Spowoduje to usunięcie scenorysu slpash z katalogu głównego okna aplikacji i zastąpienie go scenariuszem logowania.

Z storyboardu logowania, po pomyślnym zalogowaniu użytkownika, zapisuję dane użytkownika w Ustawieniach domyślnych użytkownika i inicjalizuję singleton UserData, aby uzyskać dostęp do szczegółów użytkownika. Następnie storyboard z paskiem Tab jest ładowany za pomocą metody nawigatora.

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

Navigator.moveTo(tabBarNav, from: self)

Teraz użytkownik wylogowuje się z ekranu ustawień na pasku kart. Usuwam wszystkie zapisane dane użytkownika i przechodzę do ekranu logowania.

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

let loginNav = loginSB.instantiateInitialViewcontroller() as! UINavigationController

Navigator.moveTo(loginNav, from: self)
  • Użytkownik jest zalogowany i wymusza zabicie aplikacji

Gdy użytkownik uruchomi aplikację, ekran powitalny zostanie załadowany. Sprawdzam, czy użytkownik jest zalogowany i uzyskuję dostęp do danych użytkownika z ustawień domyślnych użytkownika. Następnie zainicjuj singleton UserData i wyświetli pasek kart zamiast ekranu logowania.

Jithin
źródło
-1

Dzięki rozwiązaniu bhavyi. Istnieją dwie odpowiedzi na temat szybkiego, ale nie są one nienaruszone. Zrobiłem to w swift3.Below jest głównym kodem.

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

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

W funkcji 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
źródło
Cześć Eli. Pytanie, na które odpowiedziałeś, ma już kilka naprawdę dobrych odpowiedzi. Kiedy decydujesz się odpowiedzieć na takie pytanie, wyjaśnij, dlaczego twoja odpowiedź jest lepsza niż te bardzo dobre, które zostały już opublikowane.
Noel Widmer
Cześć Noel. Zauważyłem inne odpowiedzi dla szybkiego. Ale uznałem, że odpowiedzi nie są nienaruszone. Więc przesyłam odpowiedź na temat wersji swift3. Byłoby to pomocne dla nowego szybkiego programisty. Dziękuję! @Noel Widmer.
WangYang
Czy możesz dodać to wyjaśnienie na początku swojego postu? W ten sposób każdy może natychmiast zobaczyć korzyści płynące z Twojej odpowiedzi. Baw się dobrze na SO! :)
Noel Widmer
1
Czołgi za sugestię. Dodałem wyjaśnienie. Dzięki jeszcze raz. @ Noel Widmer.
WangYang
Niejasne rozwiązanie, które nie podkreśla użycia słowa kluczowego „Common”.
Samarey
-3

wprowadź opis zdjęcia tutaj

W aplikacji Delegat.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 W widoku załadowano

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

}

Działanie przycisku wylogowania

-(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];
});}
użytkownik5575941
źródło
Dlaczego trzeba dodać funkcjonalność do pliku ViewController.m?
Eesha,
@Eesha Dodał element TabBar „wyloguj się” do TabBar. Myślę, że brakuje obrazu, inaczej można go było zobaczyć.
helloWorld
Przechowywanie klucza logowania NSUserDefaultsjest bardzo niepewne dla tego rodzaju danych!
skywinder