안녕하세요 욱승임다
이번 포스팅에서는 RxSwift와 MVVM패턴을 활용한 애플 로그인을 포스팅 해보겠읍니다 ㅋ

Capability

추가 해주지 않음 에러나요
컴파일 에러가 나는건 아니고 런타임 에러가 나더라구염
구현 결과



저저 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
'iOS > Swift' 카테고리의 다른 글
[Swift] 화면이동시 SafeArea Background Color이 변경 되는 이슈 (2) | 2023.06.20 |
---|---|
[Swift] Hotspot Configuration, 핫스팟 연결 (1) | 2023.05.24 |
[Swift] ScrollView 스크롤 네비게이션바 hide On/Off (1) | 2023.05.19 |
[iOS] Background, Inactive(Foreground) 진입시 화면가리기 (0) | 2023.03.23 |
[Swift] print와 dump차이, 콘솔에 로그찍기 Console Log (0) | 2023.03.16 |
안녕하세요 욱승임다
이번 포스팅에서는 RxSwift와 MVVM패턴을 활용한 애플 로그인을 포스팅 해보겠읍니다 ㅋ

Capability

추가 해주지 않음 에러나요
컴파일 에러가 나는건 아니고 런타임 에러가 나더라구염
구현 결과



저저 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
'iOS > Swift' 카테고리의 다른 글
[Swift] 화면이동시 SafeArea Background Color이 변경 되는 이슈 (2) | 2023.06.20 |
---|---|
[Swift] Hotspot Configuration, 핫스팟 연결 (1) | 2023.05.24 |
[Swift] ScrollView 스크롤 네비게이션바 hide On/Off (1) | 2023.05.19 |
[iOS] Background, Inactive(Foreground) 진입시 화면가리기 (0) | 2023.03.23 |
[Swift] print와 dump차이, 콘솔에 로그찍기 Console Log (0) | 2023.03.16 |