ukSeung iOS

[Swift] RxSwift+MVVM, Sign In with Apple 애플 로그인 본문

iOS/Swift

[Swift] RxSwift+MVVM, Sign In with Apple 애플 로그인

욱승 2023. 5. 23. 22:15

안녕하세요 욱승임다

이번 포스팅에서는 RxSwift와 MVVM패턴을 활용한 애플 로그인을 포스팅 해보겠읍니다 ㅋ

 

ㅋㅋ 네?

Capability

요로코롬 Sign in with Apple을 추가!

 

추가 해주지 않음 에러나요

컴파일 에러가 나는건 아니고 런타임 에러가 나더라구염

 

구현 결과

버튼을 누르면 애플로그인을 작동하게 해놨음

 

내 이메일을 보여줄건지 숨길건지 체크

 

결과!

저저 userIdentifier값은 애플 아이디마다 고유한 값이라 바뀌지 않음!

그래서 로그인 할때마다 동일 값을 리턴받기 때문에 사용자를 식별할수 있음!

나머지 성, 이름, 이메일이 보이는걸 확인할 수 있당

 

두번째 캡쳐본에서 Hide My Email을 했다면 성, 이름, 이메일이 nil로 리턴됨 !

만약 Hide My Email을 막고 싶다면

 

 

예제코드(RxSwift + MVVM)

 

Model

//
//  User.swift
//  SwiftPractice
//
//  Created by ukseung.dev on 2023/05/22.
//

import Foundation

struct AppleUser {
    let userIdentifier: String?
    let familyName: String?
    let givenName: String?
    let email: String?
}

 

View

//
//  AppleLoginViewController.swift
//  SwiftPractice
//
//  Created by ukseung.dev on 2023/05/22.
//

import Foundation
import UIKit
import AuthenticationServices
import RxSwift
import RxCocoa

final class AppleLoginViewController: UIViewController, UIViewControllerAttribute {
    
    private let disposeBag = DisposeBag()
    private var viewModel = AppleLoginViewModel()
    private var appleUser = AppleUser(userIdentifier: nil, familyName: nil, givenName: nil, email: nil)
    
    // Apple 로그인 버튼 생성
    lazy var appleLoginButton = UIButton(type: .system).then {
        $0.setTitle("Sign In with Apple", for: .normal)
        $0.setTitleColor(.white, for: .normal)
        $0.layer.borderWidth = 1
        $0.layer.cornerRadius = 5
        $0.backgroundColor = .black
    }

    // Apple 로그인 성공 후 UILabel.text
    lazy var userInfoLabel = UILabel().then {
        $0.numberOfLines = 0 // 여러 줄
        $0.sizeToFit()
    }
    
    var navTitle: String?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setNavigationBar()
        setUI()
        setAttributes()
        bindRx()
    }
    
    func setNavigationBar() {
        self.navigationItem.title = navTitle ?? ""
    }
    
    func setUI() {
        self.view.backgroundColor = .white
        
        self.view.addSubview(appleLoginButton)
        self.view.addSubview(userInfoLabel)
    }
    
    func setAttributes() {
        appleLoginButton.snp.makeConstraints {
            $0.width.equalTo(200)
            $0.height.equalTo(50)
            $0.top.equalTo(view.safeAreaLayoutGuide).offset(30)
            $0.centerX.equalToSuperview()
        }
        
        userInfoLabel.snp.makeConstraints {
            $0.left.equalTo(20)
            $0.right.equalTo(-20)
            $0.bottom.equalTo(self.view.safeAreaLayoutGuide)
            $0.top.equalTo(appleLoginButton.snp.bottom).offset(40)
        }
    }
    
    func bindRx() {
        // 애플 로그인 버튼 Action
        appleLoginButton.rx.tap
            .bind(to: viewModel.signInButtonTapped)
            .disposed(by: disposeBag)
        
        // 성공시 userInfoLabel에 정보들 setText
        viewModel.signInCompleted
            .subscribe(onNext: { value in
                self.appleUser = value
                self.userInfoLabel.text = """
                    userIdentifier: \(self.appleUser.userIdentifier)
                    familyName: \(self.appleUser.familyName)
                    givenName: \(self.appleUser.givenName)
                    email: \(self.appleUser.email)
                """
            })
            .disposed(by: disposeBag)
    }
}

 

ViewModel

//
//  AppleLoginViewModel.swift
//  SwiftPractice
//
//  Created by ukseung.dev on 2023/05/22.
//

import Foundation
import RxSwift
import RxCocoa
import AuthenticationServices

final class AppleLoginViewModel: NSObject {
    let signInButtonTapped = PublishRelay<Void>()
    let signInCompleted = PublishRelay<AppleUser>()
    
    private let disposeBag = DisposeBag()
    
    override init() {
        super.init()
        
        signInButtonTapped
            .subscribe(onNext: { [weak self] in
                self?.performAppleSignIn()
            })
            .disposed(by: disposeBag)
    }
    
    //애플 로그인
    func performAppleSignIn() {
        let provider = ASAuthorizationAppleIDProvider()
        let request = provider.createRequest()
        request.requestedScopes = [.fullName, .email]
        
        let controller = ASAuthorizationController(authorizationRequests: [request])
        controller.delegate = self
        controller.performRequests()
    }
}

extension AppleLoginViewModel: ASAuthorizationControllerDelegate {
    
    // 애플 로그인 성공
    func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
        if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
            let userIdentifier = appleIDCredential.user
            let familyName = appleIDCredential.fullName?.familyName
            let givenName = appleIDCredential.fullName?.givenName
            let email = appleIDCredential.email
            let state = appleIDCredential.state
            
            let user = AppleUser(
                userIdentifier: userIdentifier,
                familyName: familyName,
                givenName: givenName,
                email: email
            )
            
            signInCompleted.accept(user)
        }
    }
    
    // 애플 로그인 실패
    func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
        print("Apple Sign In Error: \(error.localizedDescription)")
    }
}

 

 

결론

참고로 다른 소셜 로그인도 기획 혹은 구현중이라면 애플 로그인을 반드시 구현 해야함 !

왜냐면 애플 심사지침에 있걸랑..

 

 

Reference

 

App Store 심사 지침 - Apple Developer

App Store 심사 지침에는 사용자 인터페이스 디자인, 기능, 콘텐츠 및 특정 기술 사용 등을 비롯하여 개발과 관련된 다양한 주제에 대한 지침과 예가 나와 있습니다. 이러한 지침은 앱 승인 절차를

developer.apple.com

 

728x90
반응형