Найкращі практики для екрана входу в аркуші Storyboard, обробка очищення даних при виході


290

Я будую додаток для iOS за допомогою Storyboard. Контролером кореневого перегляду є контролер панелі вкладок. Я створюю процес входу / виходу, і він працює нормально, але у мене є кілька проблем. Мені потрібно знати найкращий спосіб налаштувати все це.

Я хочу досягти наступного:

  1. Показати екран входу при першому запуску програми. Коли вони ввійдуть у систему, перейдіть на першу вкладку контролера панелі вкладок.
  2. Кожен раз, коли вони запускають додаток після цього, перевірте, чи вони ввійшли в систему, і перейдіть прямо до першої вкладки кореневого контролера панелі вкладок.
  3. Коли вони вручну натискають кнопку виходу, покажіть екран входу та очистіть усі дані з контролерів перегляду.

Що я вже робив, це встановити контролер кореневого виду на контролер панелі вкладок і створити користувацький segue для мого контролера перегляду входу. Всередині мого класу "Контролер бар" я перевіряю, чи вони увійшли в viewDidAppearметод, і чи виконуються виразки:[self performSegueWithIdentifier:@"pushLogin" sender:self];

Я також налаштовую сповіщення про те, коли потрібно виконати дію виходу: [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(logoutAccount) name:@"logoutAccount" object:nil];

Після виходу я очищаю облікові дані від брелка, запускаю [self setSelectedIndex:0]та виконую програму, щоб знову показати контролер перегляду входу.

Це все добре працює, але мені цікаво: чи повинна ця логіка бути в AppDelegate? У мене також є два питання:

  • Перший раз, коли вони запускають додаток , контролер Tab Bar показує задовго до того, як буде здійснено segue. Я спробував перемістити код, viewWillAppearале segue не спрацює так рано.
  • Коли вони виходять із системи, усі дані залишаються всередині контролерів перегляду. Якщо вони увійдуть у новий обліковий запис, старі дані облікового запису все ще відображатимуться, поки вони не оновляться. Мені потрібен спосіб легко це очистити під час виходу.

Я відкритий для переробки цього питання. Я розглядав питання про те, щоб зробити екран входу коректором кореневого виду або створити контролер навігації в AppDelegate, щоб обробити все ... Я просто не впевнений, який найкращий метод на даний момент.


Ви представляєте контролер подання входу як модальний?
вокілам

@TrevorGehman - може додати свій розкадровки рис
Рохан До шахові

Я подав відповідь з подробицями того, що я закінчив. Це схоже на деякі інші надані відповіді, особливо @bhavya kothari.
Тревор Геман

Для представлення екрана входу може бути корисним AuthNavigation . При необхідності він організовує презентацію екрана входу, а також підтримує автоматичний вхід.
Codey

Одна з найосновніших проблем, яка майже завжди вирішується, але в той же час відчуває, що можна було б зробити краще
amar

Відповіді:


311

Ваша розповідь повинна виглядати приблизно так

У вашому appDelegate.m всередині ваших 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;
}

У файлі SignUpViewController.m

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

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

У файлі 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

didFinishLaunchingWithOptions в делеґаті програми, припускаючи, що ваш початковий контролер перегляду є підписаним у TabbarController.

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

    return true

Увійти Реєстратор контролера перегляду:

@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

Ви забули видалити автентифікацію bool з userDefaults після виходу
CodeLover

28
-1 для використання AppDelegateвсередині UIViewControllerі встановлення window.rootViewControllerтам. Я не вважаю це "найкращою практикою".
дерполюк

2
Не хочу давати , -1не відправлено відповідь: stackoverflow.com/a/30664935/1226304
derpoliuk

1
Я намагаюся зробити це швидко на IOS8, але я отримую таку помилку, коли програма запускається і на екрані входу відображається: "Незбалансовані дзвінки для початку / завершення переходів до появи". Я помітив, що коли програма завантажує екран, відображається екран входу, але також завантажується і перша вкладка на контролері панелі вкладок. Підтвердили це за допомогою println () у viewdidload. Пропозиції?
Алекс Лакайо

1
бінго! -2. -1 для AppDelegateвнутрішньої частини UIViewController-1 для зберігання ключа входу в NSUserDefaults. Це дуже-дуже небезпечно для такого роду даних!
skywinder

97

Ось що я в кінцевому підсумку робив, щоб усе виконати. Єдине, що вам потрібно врахувати на додаток до цього, це: (а) процес входу та (б) куди ви зберігаєте дані свого додатка (у цьому випадку я використав одинарний).

Дошка розгортки, що показує контролер подання входу та контролер головних вкладок

Як бачите, контролер кореневого перегляду - це мій головний контролер вкладок . Я зробив це, тому що після входу користувача я хочу, щоб програма запускалася безпосередньо на першу вкладку. (Це дозволяє уникнути будь-якого "мерехтіння", де перегляд входу відображається тимчасово.)

AppDelegate.m

У цьому файлі я перевіряю, чи вже користувач увійшов у систему. Якщо ні, я натискаю контролер перегляду входу. Я також обробляю процес виходу, коли я очищую дані та показую подання для входу.

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

Тут, якщо вхід вдалий, я просто відхиляю представлення даних та надсилаю сповіщення.

-(void) loginWasSuccessful
{

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

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

}

2
Для чого ви використовуєте сповіщення?
заколот

1
@BFeher має рацію. Я використовував сповіщення, щоб викликати нові дані. Ви можете використовувати його, щоб робити все, що завгодно, але в моєму випадку мені потрібно було повідомити, що вхід був успішним, і потрібні нові дані.
Тревор Геман

24
В iOS 8.1 (а можливо, 8.0, не пройшли тестування) це більше не працює безперебійно. Початковий контролер перегляду блимає ненадовго.
BFeher

7
Чи існує версія такого підходу Swift?
Сеано

9
@Julian У iOS 8 я замінюю два рядки [self.window makeKeyAndVisible]; [self.window.rootViewController presentViewController:viewController animated:animated completion:nil];на, self.window.rootViewController = viewController;щоб запобігти мерехтіння. Щоб оживити, що просто загорніть його в[UIView transitionWithView...];
BFeher,

20

EDIT: Додати дію виходу.

введіть тут опис зображення

1. Перш за все підготуйте файл-делегат програми

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. Створіть клас на ім’я Користувач.

User.h

#import <Foundation/Foundation.h>

@interface User : NSObject

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

@end

Користувач.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. Створіть новий контролер RootViewController і підключіть його до першого перегляду, де живуть кнопки входу. Додайте також ідентифікатор розкадрування: "початковий перегляд".

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. Створіть новий контролер LoginViewController і підключіть його до подання входу.

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. В кінці додайте новий контролер ProfileViewController і підключіть його до подання профілю на вкладціViewController.

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 - зразок проекту для отримання додаткової допомоги.


3
зразок проекту дуже допоміг мені зрозуміти концепцію входу в систему виходу з системи .. велике спасибі :)
Дейв

16

Мені не сподобалася відповідь bhavya через те, що через AppDelegateвнутрішні контролери перегляду та налаштування rootViewControllerнемає анімації. І у відповіді Тревора проблема з миготливим контролером перегляду на iOS8.

UPD 18.07.2015

AppDelegate всередині контролерів перегляду:

Зміна стану (властивостей) AppDelegate всередині контролера перегляду порушує інкапсуляцію.

Дуже проста ієрархія об'єктів у кожному проекті iOS:

AppDelegate (володіє windowта rootViewController)

ViewController (володіє view)

Це нормально, що об’єкти зверху змінюють об’єкти внизу, тому що вони створюють їх. Але це не нормально, якщо об'єкти знизу змінюють об'єкти над ними (я описав деякий базовий принцип програмування / OOP: DIP (Принцип інверсії залежності: модуль високого рівня не повинен залежати від модуля низького рівня, але вони повинні залежати від абстракцій) ).

Якщо будь-який об’єкт змінить будь-який об’єкт у цій ієрархії, рано чи пізно в коді виникне безлад. На малих проектах це може бути нормально, але не весело копатись по цій каші на бітових проектах =]

UPD 18.07.2015

Я копіюю анімації модального контролера за допомогою UINavigationController(tl; dr: перевірка проекту ).

Я використовую, UINavigationControllerщоб представити всі контролери в моєму додатку. Спочатку я відображав контролер перегляду входу в стеку навігації із звичайною анімацією push / pop. Тож я вирішив змінити його на модальний з мінімальними змінами.

Як це працює:

  1. Початковий контролер перегляду (або self.window.rootViewController) - це UINavigationController з ProgressViewController як rootViewController. Я показую ProgressViewController тому, що DataModel може зайняти деякий час, щоб ініціалізуватися, оскільки він вставляє основний стек даних, як у цій статті (мені дуже подобається такий підхід).

  2. AppDelegate відповідає за отримання оновлень статусу входу.

  3. DataModel обробляє вхід / вихід із користувача, а AppDelegate спостерігає за його userLoggedInвласністю через KVO. Можливо, це не найкращий метод для цього, але він працює для мене. (Чому KVO поганий, ви можете перевірити в цій чи цій статті (частина "Чому не використовувати сповіщення?").

  4. ModalDismissAnimator і ModalPresentAnimator використовуються для налаштування анімації push за замовчуванням.

Як працює логіка аніматорів:

  1. AppDelegate встановлює себе як делегат self.window.rootViewController(який є UINavigationController).

  2. При необхідності AppDelegate повертає одного з аніматорів -[AppDelegate navigationController:animationControllerForOperation:fromViewController:toViewController:].

  3. Аніматори реалізують -transitionDuration:та -animateTransition:методи. -[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]];
         }];
    }

Тестовий проект тут .


3
Особисто я не маю жодних проблем, коли про контролери перегляду знають AppDelegate(мені було б цікаво зрозуміти, чому ви це робите) - але ваш коментар про відсутність анімації дуже вірний. Це може бути вирішена таким відповіддю: stackoverflow.com/questions/8053832 / ...
HughHughTeotl

2
@HughHughTeotl Дякую за коментар та за посилання. Я оновив свою відповідь.
дерполюк

1
@derpoliuk що робити, якщо моїм базовим контролером є UITabBarController? Я не можу натиснути його на UINavigationController.
Джорджіо

@Giorgio, це цікаве питання, яким я не користувався UITabBarControllerдуже довго. Я, мабуть, почав би з віконного підходу, а не маніпулювати контролерами перегляду.
дерполюк

11

Ось моє швидке рішення для будь-яких майбутніх глядачів.

1) Створіть протокол для обробки функцій входу та виходу:

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

2) Розширити згаданий протокол і надати тут функціональні можливості для виходу з системи:

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) Тоді я можу відповідати своєму AppDelegate протоколу LoginFlowHandler та зателефонувати handleLoginпри запуску:

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
    }

}

Звідси моє розширення протоколу буде обробляти логіку чи визначати, чи користувач увійшов / вийшов, а потім відповідно змінить Windows rootViewController!


Не впевнений, чи я дурний, але AppDelegate не відповідає LoginFlowHandler. Я щось пропускаю? Крім того, я здогадуюсь, що цей код керує лише входом у систему при запуску. Як керувати виходом із контролера перегляду?
luke

@luke, оскільки вся логіка реалізована в розширенні, немає необхідності в її реалізації в AppDelegate. Ось що так чудово в розширенні протоколів.
shannoga

1
Вибачте @sirFunkenstine, це був спеціальний клас, який я створив, щоб показати приклад того, як можна перевірити кеш додатків, щоб перевірити, чи користувач раніше увійшов чи ні. Отже, ця AppStateреалізація залежатиме від того, як ви зберігаєте свої дані користувача на диску.
Гаррі Блум

@HarryBloom, як можна використовувати цю handleLogoutфункціональність?
nithinisreddy

1
Привіт @nithinisreddy - для виклику функцій handleLogout вам потрібно буде відповідати класу, з якого ви телефонуєте, до LoginFlowHandlerпротоколу. Тоді ви отримаєте сферу, щоб мати можливість викликати метод handleLogout. Дивіться крок 3 для прикладу того, як я це зробив для класу AppDelegate.
Гаррі Блум

8

Робити це з боку делегата програми НЕ рекомендується. AppDelegate керує життєвим циклом програми, який стосується запуску, призупинення, припинення тощо. Я пропоную зробити це з вашого початкового контролера подання в viewDidAppear. Можна self.presentViewControllerі self.dismissViewControllerз контролера перегляду входу. Зберігайте boolключ, NSUserDefaultsщоб побачити, чи він запускається вперше.


2
Чи повинен відображатися перегляд (бути видимим для користувача) у "viewDidAppear"? Це все одно створить мерехтіння.
Mark13426

2
Не відповідь. І "Зберігайте ключ bool у NSUserDefaults, щоб побачити, чи він запускається вперше". Це дуже небезпечно для такого роду даних.
skywinder

6

Створіть ** LoginViewController ** та ** TabBarController **.

Після створення LoginViewController і TabBarController нам потрібно додати StoryboardID як " loginViewController " і " tabBarController " відповідно.

Тоді я віддаю перевагу , щоб створити Constant - структуру:

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

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

У LoginViewController додайте IBAction :

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

У ProfileViewController додайте IBAction :

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

У AppDelegate додайте рядок коду в didFinishLaunchingWithOptions :

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

    Switcher.updateRootViewController()

    return true
}

Нарешті створіть клас 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

    }

}

Це все!


Чи є якась різниця, який контролер перегляду є початковим у таблицях? На вашому доданому фотографії я бачу, що у вас є опція "Контролер початкового перегляду", що встановлена ​​на контрольному панелі вкладки. У перемикачі AppDelegate u основний контролер кореневого виду, так що я думаю, це не має значення, чи не так?
ShadeToD

@iAleksandr Оновіть відповідь на iOS 13. Поточна відповідь SceneDelegate не працює.
Нітеш

5

У Xcode 7 ви можете мати декілька таблиць історії. Буде краще, якщо ви зможете зберегти потік входу в окрему дошку.

Це можна зробити за допомогою SELECT VIEWCONTROLLER> Редактор> Рефактор на аркуші розгортки

Ось версія Swift для налаштування подання як RootViewContoller-

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

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

3

Я використовую це для перевірки першого запуску:

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

(якщо користувач видаляє додаток і повторно встановить його, він вважається першим запуском)

У AppDelegate я перевіряю на перший запуск та створюю навігаційний контролер з екранами входу (вхід та реєстрація), який я ставлю поверх поточного головного вікна:

[self.window makeKeyAndVisible];

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

Оскільки це знаходиться поверх звичайного контролера перегляду, він не залежить від решти вашої програми, і ви можете просто відхилити контролер перегляду, якщо він вам більше не потрібен. Ви також можете представити подання таким чином, якщо користувач натискає кнопку вручну.

BTW: я зберігаю дані входу від своїх користувачів так:

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

Для виходу: я відключився від CoreData (занадто повільно) і використовую NSArrays та NSDic Dictionary для управління своїми даними зараз. Вихід просто означає спорожнити масиви та словники. Плюс я обов’язково встановлюю свої дані в viewWillAppear.

Це воно.


0

Я перебуваю в тій же ситуації, що і ви, і рішення, яке я знайшов для очищення даних, видаляє всі речі CoreData, на які мої контролери перегляду покладаються, щоб отримати цю інформацію. Але я все-таки вважаю такий підхід дуже поганим, я вважаю, що більш елегантний спосіб зробити це може бути досягнуто без рекламних дощок і з використанням лише коду для управління переходами між контролерами перегляду.

Я знайшов цей проект у Github, який робить все це лише за кодом, і це зрозуміти досить просто. Вони використовують бокове меню, схоже на Facebook, і це те, що вони роблять, це змінювати контролер перегляду в центрі залежно від того, користувач увійшов чи ні. Коли користувач виходить з системи, він appDelegateвидаляє дані з CoreData і знову встановлює головний контролер перегляду на екран входу.


0

У мене було подібне питання, щоб вирішити в додатку, і я використав наступний метод. Я не використовував сповіщення для обробки навігації.

У мене в додатку три розклади.

  1. Інструменталізація екрану на екрані заставки - для ініціалізації програми та перевірки, чи користувач уже ввійшов у систему
  2. Роздільна дошка входу - для обробки потоку входу користувачів
  3. Роздільна панель на вкладках - для відображення вмісту програми

Моя початкова розкадровка в додатку - це розгортка на екрані Splash screen. У мене є контролер навігації як корінь вхідної панелі та панелі вкладок для обробки навігацій.

Я створив клас Навігатор для обробки навігації в додатку, і це виглядає приблизно так:

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)

           }

       }

   }

}

Давайте розглянемо можливі сценарії:

  • Перший запуск програми; Екран заставки буде завантажений там, де я перевіряю, чи користувач уже ввійшов. Потім екран для входу буде завантажений за допомогою класу Навігатор наступним чином;

Оскільки у мене корінь навігаційного керування є кореневим, я створюю контролер навігації як початковий контролер перегляду.

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

let loginNav = loginSB.instantiateInitialViewcontroller() as! UINavigationController

Navigator.moveTo(loginNav, from: self)

Це видаляє розгортання slpash з кореня вікна додатка і замінює його на раскадровку для входу.

Після того, як користувач успішно увійшов у систему розкадрівки для входу, я зберігаю дані користувача в налаштуваннях за замовчуванням та ініціалізую одиночну кнопку UserData для доступу до даних про користувача. Потім розкадровка панелі вкладок завантажується за допомогою методу навігатора.

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

Navigator.moveTo(tabBarNav, from: self)

Тепер користувач виходить із екрана налаштувань на панелі вкладок. Я очищую всі збережені дані користувача та переходжу до екрана входу.

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

let loginNav = loginSB.instantiateInitialViewcontroller() as! UINavigationController

Navigator.moveTo(loginNav, from: self)
  • Користувач увійшов у систему та змушує вбивати додаток

Коли користувач запустить додаток, завантажується екран сплеску. Я перевіряю, чи користувач увійшов у систему та отримую доступ до даних користувача з налаштувань користувача за замовчуванням. Потім ініціалізуйте UserData сингл і показує панель вкладок замість екрана входу.


-1

Завдяки рішенню bhavya. Існувало дві відповіді про швидке, але це не дуже недоторкано. Я це роблю в swift3.Blowlow - основний код.

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

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

У функції logOutAction

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

Привіт Елі. На питання, на яке ви вже відповіли, є пара справді хороших відповідей. Коли ви вирішите відповісти на таке запитання, будь ласка, поясніть, чому ваша відповідь краща за ті, які вже були опубліковані.
Ноель Відмер

Привіт Ноель. Я помітив інші відповіді для швидкого. Але я вважав, що відповіді не дуже цілі. Тож я надсилаю свою відповідь про версію swift3. Це допоможе новому швидкому програмісту. Дякую! @Noel Widmer.
ВанЯн

Чи можете ви додати це пояснення вгорі своєї публікації? Таким чином, кожен може негайно побачити користь вашої відповіді. Приємно провести час! :)
Ноель Відмер

1
Танки для вашої пропозиції. Я додав пояснення. Дякую ще раз. @ Ноель Відмер.
ВанЯн

Неясне рішення, яке не підкреслює використання ключового слова "Загальні".
Самарей

-3

введіть тут опис зображення

В 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 Перегляд завантажився

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

}

Дія кнопки виходу

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

Чому виникає необхідність додати деяку функціональність у файл ViewController.m ??
Eesha

@Eesha Він додав до кнопки TabBar пункт кнопки "вихід". Я думаю, що зображення відсутнє ще, ви могли його бачити.
helloWorld

Ключ для входу в магазин NSUserDefaultsдуже-дуже небезпечний для такого роду даних!
піднятий на
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.