스토리 보드 로그인 화면의 모범 사례, 로그 아웃시 데이터 지우기 처리


290

스토리 보드를 사용하여 iOS 앱을 만들고 있습니다. 루트 뷰 컨트롤러는 탭 막대 컨트롤러입니다. 로그인 / 로그 아웃 프로세스를 만들고 있는데 대부분 제대로 작동하지만 몇 가지 문제가 있습니다. 이 모든 것을 설정하는 가장 좋은 방법을 알아야합니다.

다음을 달성하고 싶습니다.

  1. 앱을 처음 시작할 때 로그인 화면을 표시하십시오. 로그인하면 Tab Bar Controller의 첫 번째 탭으로 이동하십시오.
  2. 이후 앱을 시작할 때마다 로그인했는지 확인하고 루트 Tab Bar Controller의 첫 번째 탭으로 바로 건너 뜁니다.
  3. 로그 아웃 버튼을 수동으로 클릭하면 로그인 화면이 표시되고 뷰 컨트롤러에서 모든 데이터가 지워집니다.

내가 지금까지 한 일은 루트보기 컨트롤러를 탭 막대 컨트롤러로 설정하고 내 로그인보기 컨트롤러에 대한 사용자 정의 segue를 작성하는 것입니다. 내 Tab Bar Controller 클래스 내에서 viewDidAppear메소드 내부에 로그인했는지 확인 하고 segue를 수행합니다.[self performSegueWithIdentifier:@"pushLogin" sender:self];

또한 로그 아웃 작업을 수행해야 할 때 알림을 설정했습니다. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(logoutAccount) name:@"logoutAccount" object:nil];

로그 아웃 할 때 키 체인에서 자격 증명을 지우고 [self setSelectedIndex:0]segue를 실행 하고 수행하여 로그인 뷰 컨트롤러를 다시 표시합니다.

이 모든 것이 잘 작동하지만 궁금합니다. 이 논리가 AppDelegate에 있어야합니까? 또한 두 가지 문제가 있습니다.

  • 앱을 처음 시작할 때 segue가 수행되기 전에 Tab Bar Controller가 잠깐 표시됩니다. 코드를 viewWillAppear옮기려고했지만 segue가 일찍 작동하지 않습니다.
  • 로그 아웃해도 모든 데이터가 여전히 모든 뷰 컨트롤러 내에 있습니다. 새 계정으로 로그인하면 이전 계정 데이터는 새로 고칠 때까지 계속 표시됩니다. 로그 아웃시이를 쉽게 지울 수있는 방법이 필요합니다.

나는 이것을 재 작업하기 위해 열려있다. 로그인 화면을 루트보기 컨트롤러로 만들거나 모든 것을 처리하기 위해 AppDelegate에서 탐색 컨트롤러를 만드는 것을 고려했습니다 ...이 시점에서 가장 좋은 방법이 무엇인지 잘 모르겠습니다.


로그인보기 컨트롤러를 모달로 표시합니까?
vokilam 2019

@TrevorGehman은 - 당신의 스토리 보드 그림을 추가 할 수 있습니다
로한 K 샤

나는 내가 한 일에 대한 세부 정보가 담긴 답변을 제출했습니다. 제공된 다른 답변, 특히 @bhavya kothari와 비슷합니다.
Trevor Gehman 2014

로그인 화면을 표시하는 데 AuthNavigation 이 유용 할 수 있습니다. 필요한 경우 로그인 화면 표시를 구성하고 자동 로그인도 지원합니다.
Codey

거의 항상 해결되지만 동시에 더 나아진 것처럼 느껴지는 매우 기본적인 문제 중 하나
amar

답변:


310

스토리 보드는 다음과 같아야합니다

didFinishLaunchingWithOptions 안의 appDelegate.m에서

//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;

}

스위프트 4 버전

초기 뷰 컨트롤러가 서명 된 TabbarController라고 가정하고 앱 델리게이트의 didFinishLaunchingWithOptions

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

로그 아웃 후 userDefaults에서 bool 인증을 삭제하는 것을 잊었습니다
CodeLover

28
-1 사용한 AppDelegate내부 UIViewController및 설정 window.rootViewController있다. 나는 이것을 "모범 사례"로 생각하지 않습니다.
derpoliuk 2018 년

2
-1답변을 게시하지 않고 포기하고 싶지 않았습니다 : stackoverflow.com/a/30664935/1226304
derpoliuk

1
IOS8에서 신속 하게이 작업을 수행하려고하지만 응용 프로그램이 시작되고 로그인 화면에 "불균형 호출이 시작 / 종료 모양 전환을 시작합니다"라는 메시지가 표시됩니다. 앱이로드되면 로그인 화면이 표시되지만 탭 막대 컨트롤러의 첫 번째 탭도로드되는 것을 알았습니다. viewdidload의 println ()을 통해이를 확인했습니다. 제안?
Alex Lacayo 2016 년

1
빙고! -2. 로그인 키를 저장 하기위한 AppDelegate내부 UIViewController-1의 경우 -1입니다 NSUserDefaults. 이런 종류의 데이터에는 매우 안전하지 않습니다!
skywinder

97

여기에 내가 모든 것을 성취하기 위해 한 일이 있습니다. 이 외에도 (a) 로그인 프로세스 및 (b) 앱 데이터를 저장하는 위치 (이 경우 단일 톤을 사용)뿐입니다.

로그인보기 컨트롤러 및 기본 탭 컨트롤러를 보여주는 스토리 보드

보시다시피 루트 뷰 컨트롤러는 내 메인 탭 컨트롤러 입니다. 사용자가 로그인 한 후 앱이 첫 번째 탭으로 직접 시작되기 때문에이 작업을 수행했습니다. 로그인 화면이 일시적으로 표시되는 "깜박임"을 방지합니다.

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가 옳습니다. 알림을 사용하여 새로운 데이터 풀을 트리거했습니다. 그것을 사용하여 원하는대로 할 수 있지만 제 경우에는 로그인이 성공했으며 새로운 데이터가 필요하다는 알림을 받아야했습니다.
Trevor Gehman

24
iOS 8.1 (및 아마도 8.0, 테스트되지 않은)에서는 더 이상 원활하게 작동하지 않습니다. 초기 View Controller가 잠깐 동안 깜박입니다.
BFeher

7
이 접근법의 스위프트 버전이 있습니까?
Seano

9
@ 줄리안 iOS 8에서는 깜박임을 방지하기 위해 두 줄 [self.window makeKeyAndVisible]; [self.window.rootViewController presentViewController:viewController animated:animated completion:nil];을 바꿉니다 self.window.rootViewController = viewController;. 그것을 감싸기 위해 애니메이션을 적용하려면[UIView transitionWithView...];
BFeher

20

편집 : 로그 아웃 작업을 추가하십시오.

여기에 이미지 설명을 입력하십시오

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

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. 새 제어기 RootViewController를 작성하고 로그인 단추가있는 첫 번째보기와 연결하십시오. 스토리 보드 ID : "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. 새 컨트롤러 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를 추가하고 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 은 추가 도움이 필요한 샘플 프로젝트입니다.


3
샘플 프로젝트는 로그인과 로그 아웃의 개념을 이해하는데 크게 도움이되었습니다. 많은 감사 :)
Dave

16

AppDelegate뷰 컨트롤러 내부 를 사용하기 때문에 bhavya의 답변을 좋아하지 않았 으며 설정 rootViewController에는 애니메이션이 없습니다. 그리고 Trevor의 답변은 iOS8에서 깜박이는 뷰 컨트롤러에 문제가 있습니다.

UPD 07/18/2015

View Controller 내부의 AppDelegate :

뷰 컨트롤러 내에서 AppDelegate 상태 (속성)를 변경하면 캡슐화가 중단됩니다.

모든 iOS 프로젝트에서 매우 간단한 객체 계층 구조 :

AppDelegate (소유자 windowrootViewController)

ViewController (소유자 view)

상단의 객체가 객체를 생성하기 때문에 하단의 객체가 변경되는 것이 좋습니다. 그러나 맨 아래에있는 객체가 그 위에 객체를 변경하면 괜찮습니다 (기본 프로그래밍 / OOP 원칙 : DIP (Dependency Inversion Principle : 상위 레벨 모듈은 하위 레벨 모듈에 의존해서는 안되지만 추상화에 의존해야 함)에 대해 설명했습니다) ).

객체가이 계층 구조의 객체를 변경하면 조만간 코드에 혼란이 발생합니다. 작은 프로젝트에서는 괜찮을지도 모르지만 비트 프로젝트 에서이 혼란을 파는 것은 재미가 없습니다.

UPD 07/18/2015

UINavigationController(tl; dr : check project )를 사용하여 모달 컨트롤러 애니메이션을 복제합니다 .

UINavigationController내 앱에 모든 컨트롤러를 표시하는 데 사용 하고 있습니다. 처음에는 일반 푸시 / 팝 애니메이션으로 탐색 스택에 로그인 뷰 컨트롤러를 표시했습니다. 나는 최소한의 변화로 모달로 변경하기로 결정했습니다.

작동 방식 :

  1. 초기보기 컨트롤러 (또는 self.window.rootViewController)는 ProgressViewController를 a로 사용하는 UINavigationController입니다 rootViewController. DataModel 이이 기사 와 같이 핵심 데이터 스택을 초기화하기 때문에 초기화하는 데 시간이 걸릴 수 있기 때문에 ProgressViewController를 보여줍니다 (이 방법은 정말 좋습니다).

  2. AppDelegate는 로그인 상태 업데이트를 담당합니다.

  3. DataModel은 사용자 로그인 / 로그 아웃을 처리하고 AppDelegate는 userLoggedInKVO를 통해 해당 속성을 관찰합니다 . 아마도 이것을하는 가장 좋은 방법은 아니지만 그것은 나를 위해 작동합니다. (왜 KVO 나쁜, 당신은에서 확인할 수 있습니다 또는 이 문서 (왜 사용하지 않을 알림? 부분).

  4. ModalDismissAnimator 및 ModalPresentAnimator는 기본 푸시 애니메이션을 사용자 정의하는 데 사용됩니다.

애니메이터 로직 작동 방식 :

  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
개인적으로 View Controllers에 대해 아는 데 아무런 문제가 없습니다 AppDelegate(왜 그런지 이해하고 싶습니다)-애니메이션 부족에 대한 귀하의 의견은 매우 유효합니다. 이 답변으로 해결할 수 있습니다 : stackoverflow.com/questions/8053832/…
HughHughTeotl

2
@HughHughTeotl 댓글과 링크를 주셔서 감사합니다. 답변을 업데이트했습니다.
derpoliuk

1
@derpoliuk 기본 뷰 컨트롤러가 UITabBarController 인 경우 어떻게합니까? UINavigationController에서 푸시 할 수 없습니다.
Giorgio

@ Giorgio, 그것은 흥미로운 질문입니다, 나는 UITabBarController오랫동안 사용하지 않았습니다 . 아마도 뷰 컨트롤러를 조작 하는 대신 창 접근법으로 시작했을 것입니다 .
derpoliuk

11

미래의 모든 구경꾼을위한 Swifty 솔루션은 다음과 같습니다.

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당신이 디스크에 사용자 데이터를 저장하는 방법을 구현 그러므로에 따라 달라집니다.
Harry Bloom

@HarryBloom 어떻게 handleLogout기능을 사용 합니까?
nithinisreddy

1
안녕하세요 @nithinisreddy-handleLogout 기능을 호출하려면 호출하는 클래스를 LoginFlowHandler프로토콜 로 준수해야 합니다. 그런 다음 handleLogout 메소드를 호출 할 수있는 범위를 얻게됩니다. AppDelegate 클래스에서 어떻게 수행했는지에 대한 예제는 3 단계를 참조하십시오.
해리 블룸

8

앱 델리게이트에서이 작업을 수행하지 않는 것이 좋습니다. AppDelegate는 시작, 일시 중단, 종료 등과 관련된 앱 수명주기를 관리합니다. 의 초기보기 컨트롤러에서이 작업을 수행하는 것이 좋습니다 viewDidAppear. 당신은 할 수 self.presentViewControllerself.dismissViewController로그인 뷰 컨트롤러에서. bool키가 NSUserDefaults처음으로 실행되는지 확인하려면 키를 저장 하십시오.


2
뷰가`viewDidAppear '에 나타나야합니까 (사용자에게 표시)? 그래도 깜박임이 발생합니다.
Mark13426

2
답이 아닙니다. "NSUserDefaults에 bool 키를 저장하여 처음 시작되는지 확인하십시오."는 이런 종류의 데이터에 매우 위험합니다.
skywinder

6

** LoginViewController ** 및 ** TabBarController **를 작성하십시오.

LoginViewControllerTabBarController를 생성 한 후 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
}

마지막으로 스위처 클래스를 작성하십시오 .

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의 Coz 현재 답변이 작동하지 않습니다.
Nitesh

5

Xcode 7에서는 스토리 보드가 여러 개있을 수 있습니다. 로그인 흐름을 별도의 스토리 보드에 유지할 수 있으면 더 좋습니다.

SELECT VIEWCONTROLLER> 편집기> 스토리 보드로 리팩터링을 사용하여 수행 할 수 있습니다.

여기에보기를 RootViewContoller-로 설정하는 Swift 버전이 있습니다.

    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 및 NSDictionaries를 사용하여 데이터를 관리합니다. 로그 아웃은 해당 배열과 사전을 비우는 것을 의미합니다. 또한 viewWillAppear에서 데이터를 설정해야합니다.

그게 다야.


0

나는 당신과 같은 상황에 있으며 데이터 정리를 위해 찾은 솔루션은 뷰 컨트롤러가 정보를 그리는 데 의존하는 모든 CoreData 항목을 삭제하는 것입니다. 그러나 나는 여전히이 접근법이 매우 나쁘다는 것을 알았습니다. 스토리 보드없이 코드를 사용하여 뷰 컨트롤러 간의 전환을 관리하는 더 우아한 방법이 가능하다고 생각합니다.

코드로만 모든 것을 수행하는 Github 에서이 프로젝트 를 찾았 으며 이해하기 쉽습니다. 그들은 페이스 북과 같은 사이드 메뉴를 사용하며 사용자가 로그인했는지 여부에 따라 센터 뷰 컨트롤러를 변경합니다. 사용자가 로그 아웃하면 appDelegateCoreData에서 데이터 가 제거되고 기본보기 컨트롤러가 다시 로그인 화면으로 설정됩니다.


0

앱에서 해결해야 할 비슷한 문제가 있었고 다음 방법을 사용했습니다. 탐색 처리에 알림을 사용하지 않았습니다.

앱에 스토리 보드가 3 개 있습니다.

  1. 스플래시 화면 스토리 보드-앱 초기화 및 사용자가 이미 로그인했는지 확인
  2. 로그인 스토리 보드-사용자 로그인 흐름 처리
  3. 탭 바 스토리 보드-앱 컨텐츠 표시

앱의 초기 스토리 보드는 스플래시 화면 스토리 보드입니다. 뷰 컨트롤러 탐색을 처리하기 위해 로그인 및 탭 막대 스토리 보드의 루트로 탐색 컨트롤러가 있습니다.

앱 탐색을 처리하기 위해 Navigator 클래스를 만들었으며 다음과 같습니다.

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)

           }

       }

   }

}

가능한 시나리오를 봅시다 :

  • 첫 번째 앱 출시; 사용자가 이미 로그인했는지 확인하는 스플래시 화면이로드됩니다. 그러면 다음과 같이 Navigator 클래스를 사용하여 로그인 화면이로드됩니다.

탐색 컨트롤러를 루트로 사용하므로 탐색 컨트롤러를 초기보기 컨트롤러로 인스턴스화합니다.

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에서 그렇게했습니다. 아래는 주요 코드입니다.

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

안녕 엘리. 당신이 대답 한 질문에는 이미 몇 가지 좋은 답변이 있습니다. 그러한 질문에 대답하기로 결정한 경우, 이미 게시 된 아주 좋은 질문보다 답변이 더 나은 이유를 설명하십시오.
Noel Widmer

안녕 노엘 나는 다른 답변을 신속하게 보았습니다. 그러나 나는 대답이 그다지 손상되지 않았다고 생각했다. 그래서 swift3 버전에 대한 답변을 제출합니다. 새로운 신속한 프로그래머에게 도움이 될 것입니다. 감사합니다! @Noel Widmer.
WangYang

게시물 상단에 해당 설명을 추가 할 수 있습니까? 그렇게하면 모든 사람이 귀하의 답변의 이점을 즉시 볼 수 있습니다. SO에 좋은 시간 되세요! :)
Noel Widmer

1
귀하의 제안에 대한 탱크. 설명을 추가했습니다. 다시 한번 감사드립니다. @ Noel Widmer.
WangYang

'공통'키워드 사용을 강조하지 않는 모호한 솔루션.
Samarey

-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 단추 항목을 TabBar에 추가했습니다. 나는 당신이 그것을 볼 수있는 이미지가 누락 된 것 같아요.
helloWorld

로그인 키를 저장 NSUserDefaults하면 이런 종류의 데이터에 대해 매우 안전하지 않습니다!
skywinder
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.