반응형

원문 : https://www.raywenderlich.com/4875322-sign-in-with-apple-using-swiftui

SwiftUI를 사용해서 Apple 로그인하기(Sign in with Apple using SwiftUI)

iOS 앱에서 사용자에게 더 많은 개인정보를 보호하고 제어하는 것을 재공하기 위해, SwiftUI를 사용해서 Apple에 로그인하기 구현 방법을 배웁니다.

Sign In with Apple는 iOS 13에 있는 앱에서 빨리 등록하고 인증하도록 해주는 새로운 기능입니다.

Apple이 Sign In with Apple이 구현하기 쉽다고 거듭말하지만, 관리해야할 몇 가지 별난(quirk) 것이 있습니다. 이 튜토리얼에서, Sign in with Apple을 구현하는 방법 뿐만아니라 SwiftUI를 사용하는 방법도 배우게 될 것입니다!

이 튜토리얼을 따라하기 위해서 Xcode 11, Apple Developer 유료 맴버쉽과 iOS 13가 필요합니다.

주의 
iOS 13이 구동되는 실제 기기가 필요할것입니다. 시뮬레이터는 항상 제대로 동작하지는 않습니다. 

시작하기(Getting Started)

다운로드

기기에서 실행하고 사용권한을 처리하기 때문에, Project 탐색기로 이동해서 새로운 Signing & Capabilities 탭을 클릭해서 팀 식별자를 설정하고 번들 식별자를 적절하게 업데이트합니다. 앱을 바로 빌드하고 실행하면, 일반적인 로그인 화면을 보게 될 것입니다.

주의
Xcode에 보이는 2개의 경고는 무시할 수 있습니다. 이 튜토리얼에서 그것들을 고칠 것입니다.

기능 추가하기(Add Capabilities)

프로비저닝 프로파일에 Sign in with Apple 기능이 있어야 하므로 지금 추가하세요. Project 탐색기에서 project를 클릭하고 SignInWithApple 타겟을 설정하고나서 Signing & Capbilities 탭을 클릭하세요. 마지막으로 +Capability를 클릭하고 Sign In with Apple 기능을 추가하세요.

앱이 웹사이트와 관련되어 있는 경우에, Associated Domains 기능을 추가해야 합니다. 이 단계는 completely optional(완전히 전택적)이고 이 튜토리얼이나 Sign In with Apple 함수를 만드는데 필요하지 않습니다. associated domain(연관된 도메인)을 사용하도록 선택한 경우에, 도메인 이름으로 webcredentials: 문자열 다음에 Domains값을 설정해야 합니다. 예를들어, webcredentials:www.mydomain.com을 사용할 수 있습니다. 이 뷰토리얼 뒷부분에서 웹사이트에서 변경해야 하는 것들에 대해서 배울 것입니다.

https://koenig-media.raywenderlich.com/uploads/2019/08/capabilities-1.gif

로그인 버튼 추가하기(Add Sign In Button)

Apple은 Sign In with Apple 버튼용 SwiftUI View를 제공하지 않으므로, 여러분 스스로 만들어야 합니다. SignInWithApple.swift이름의 파일을 만들고 다음 코드를 붙여넣습니다.

import SwiftUI
import AuthenticationServices

// 1
final class SignInWithApple: UIViewRepresentable {
  // 2
  func makeUIView(context: Context) -> ASAuthorizationAppleIDButton {
    // 3
    return ASAuthorizationAppleIDButton()
  }
  
  // 4
  func updateUIView(_ uiView: ASAuthorizationAppleIDButton, context: Context) {
  }
}

 

하는 일은 다음과 같습니다:

  1. UIView를 래핑(wrap)하는게 필요한 UIViewRepresentable 하위 클래스입니다.
  2. makeUIView는 항상 특정 UIView타입을 반환해야 합니다.
  3. 사용자정의화를 수행하지 않았기에, Sign In with Apple 객체를 직접 반환합니다.
  4. 그 뷰가 절대 변경되지 않으므로, 구현을 비워둡니다.

주의
SwiftUI를 아직 사용해보지 않은 경우에, 계속 진행하기 전에 SwiftUI: Getting Started 튜토리얼을 보세요.

이제 SwiftUI로 버튼을 추가할 수 있으며, ContentView.swift를 열고 UserAndPassword 뷰 바로 아래에 추가하세요.

SignInWithApple()
  .frame(width: 280, height: 60)

 

Apple의 스타일 가이드는 최소 280x60의 크기를 요구하므로, 그에 따라야 합니다. 앱을 빌드하고 실행하고 여러분의 버튼을 보세요!

버튼 탭 처리하기(Handle Button Taps)

지금 바로, 버튼을 탭하면 아무것도 하지 않습니다. 버튼의 frame을 설정한 바로 아래에 제스쳐 인식을 추가하세요.

.onTapGesture(perform: showAppleLogin)

 

그리고나서 body 프로퍼티 뒤에 showAppleLogin()을 구현하세요.

private func showAppleLogin() {
  // 1
  let request = ASAuthorizationAppleIDProvider().createRequest()

  // 2
  request.requestedScopes = [.fullName, .email]

  // 3
  let controller = ASAuthorizationController(authorizationRequests: [request])    
}

설정한 내용은 다음과 같습니다.

  1. 모든 로그인 요청에 ASAuthorizationAppleIDRequest가 필요합니다.
  2. 알고자하는 최종사용자 데이터의 타입을 지정합니다.
  3. 로그인 다이얼로그를 보여주기 위한 컨트롤러를 생성합니다.

필요한 사용자 데이터만 요청해야 합니다. Apple은 사용자 ID를 만듭니다. 따라서 이메일을 가져오고자 하는 목적이 고유한 식별자를 같는 것이라면, 실제로는 필요하지 않습니다 - 이를 요청하지 마세요. :]

ASAuthorizationControllerDelegate

사용자가 인증을 시도할때, Apple은 두개의 델리게이터 메소드중 하나를 호출할 것이므로, 이제 구현합니다. SignInWithAppleDelegates.swift을 열어주세요. 사용자가 버튼을 탭한 후에 실행하는 코드를 이곳에 구현할 것입니다. 뷰에 바로 코드를 구현할 수 있지만, 재사용가능하도록 다른 곳에 두는 것이 더 깔끔합니다. 

지금은 authorizationController(controller:didCompleteWithError:)을 비워들 것이지만, 프로덕션(production) 앱에서는, 이러한 오류들을 처리해야 합니다.

인증이 성공할때, authorizationController(controller:didCompleteWithAuthorization:)이 호출될 것입니다. 다운로드했던 샘플코드에 처리해야 하는 2가지 경우(cases)가 있는 것을 알 수 있습니다. credential 프로퍼티를 검사해서, Apple ID 또는 저장된 iCloud 암호를 통해서 사용자가 인증되었는지를 결정합니다.

델리게이트(delegate) 메소드에 전달된 ASAuthorization 객체에는 이메일이나 이름과 같은 요청한 모든 프로퍼티가 포함되어 있습니다. 그 값이 존재하는지로 새로 등록하거나 기존 로그인 하는지를 결정 할 수 있는 방법입니다.

주의 
Apple은 첫번째 인증에서 요청했던 정보만을 제공합니다.

앞의 주의사항을 반드시 기억해야 합니다! Apple은 요청했던 세부정보를 저장하고 다시 요청하지 않는다고 가정합니다. 처리해야 할 Sign In with Apple의 단점 중 하나입니다.

사용자가 맨 처음 로그인 하는 경우를 고려해서, 등록을 수행해야 합니다. Apple은 사용자의 이메일과 이름을 가지고 있습니다. 그리고나서 서버에 등록하는 코드를 호출하려 합니다. 서버가 온라인 상태가 아니거나 기기의 네트워크 연결이 끊어지은 것은 예외로 합니다.

다음번에 사용자가 로그인하는 경우, Apple은 이미 가지고 있을 것이라 예상하기 때문에 세부정보를 제공하지 않습니다. 이 때문에 기존 사용자(existing user)가 실행되어 오류가 발생합니다.

등록 처리하기(Handling Registration)

authorizationController(controller:didCompleteWithAuthorization:)에서, 첫번째 case문 내부에 다음에 오는 것을 추가합니다.

// 1
if let _ = appleIdCredential.email, let _ = appleIdCredential.fullName {
  // 2
  registerNewAccount(credential: appleIdCredential)
} else {
  // 3
  signInWithExistingAccount(credential: appleIdCredential)
}

 

코드에서 하는 것은 다음과 같습니다.

  1. 세부사항을 받은 경우에, 새로운 등록임을 알게 됩니다.
  2. 세부사항을 받으면, 등록 메소드를 호출합니다.
  3. 세부사항을 받지 않은 경우에는 기존 계정 메소드를 호출합니다.

확장의 맨 윗부분에 다음에 오는 인증 메소드를 붙여넣습니다.

private func registerNewAccount(credential: ASAuthorizationAppleIDCredential) {
  // 1
  let userData = UserData(email: credential.email!,
                          name: credential.fullName!,
                          identifier: credential.user)

  // 2
  let keychain = UserDataKeychain()
  do {
    try keychain.store(userData)
  } catch {
    self.signInSucceeded(false)
  }

  // 3
  do {
    let success = try WebApi.Register(
      user: userData,
      identityToken: credential.identityToken,
      authorizationCode: credential.authorizationCode
    )
    self.signInSucceeded(success)
  } catch {
    self.signInSucceeded(false)
  }
}

 

여기에는 몇가지 일이 일어납니다.

  1. 원하던 세부사항과 Apple이 제공하는 user를 구조체로 저장합니다.
  2. 나중에 사용하도록 세부사항을 iCloud 키체인에 저장합니다.
  3. 서비스를 호출하고 등록이 성공했는지 여부를 호출한 곳에 알려줍니다.

credential.user의 사용법을 확인합니다. 해당 프로퍼티에는 Apple이 최종 사용자에게 할당한 고유한 식별자를 포함합니다. 서버에서 사용자를 저장할때 이 값(이메일이나 사용자 이름이 아닙니다)을 활용합니다. 제공된 값은 사용자가 소유한 모든 기기에서 정확하게 일치할 것입니다. 그 외에, Apple은 팀 ID와 연결된 모든 앱에 대해 동일한 값을 사용자에게 제공할 것입니다. 사용자가 실행하는 모든 앱은 동일한 ID를 받으며, 서버에 모든 정보가 있으므로 사용자에게 이를 제공하도록 요청할 필요가 없습니다.

서버의 데이터베이스에는 이미 사용자의 다른 식별자가 저장되어 있을수도 있습니다. Apple이 제공한 식별자를 보유한 user 타입 테이블에 새로운 열(column)을 추가하면 됩니다. 서버측 코드는 해당 열이 일치하는지 먼저 확인합니다. 찾지 못한 경우에, 이메일 주소 또는 로그인 이름과 같은 기존 로그인 또는 등록하기 처리로 되돌립니다.

서버가 보안을 처리하는 방법에 따라, credential.identityToken과 credential.authrizationCode를 보내야 하거나 보내지 않을 수 있습니다. OAuth는 해당 2가지를 사용합니다. OAuth 설정은 튜토리얼의 범위를 벗어납니다.

주의 
Apple은 OAuth 세부사항과 함께 공유 키를 생성하기 위해 필요한 데이터를 제공합니다. 본질적으로, JSON Web Key(JWK)를 제공합니다.

키체인에 제대로 저장하기 위해서, CredentialStorage에 있는 UserDataKeychain.swift를 편집하고 앱에 대한 번들 식별자를 보유하기 위해 account를 업데이트 하고나서 다른 텍스트 값을 추가합니다. 번들 식별자에 .Details를 추가하고자 합니다. 중요한 것은 account 프로퍼티와 번들 식별자가 정확하게 일치하지 않는다는 것이므로, 저장된 값은 의도한 목적으로만 사용됩니다.

기존 계정 처리하기(Handling Existing Accounts)

앞서 설명했듯이, 기존 사용자가 앱에 로그인 할때, Apple은 이메일과 이름을 제공하지 않습니다. 이 경우를 처리하기 위해서 SignInWithAppleDelegates.swift에 등록 메소드 바로 아래에 다음 메소드를 추가합니다.

private func signInWithExistingAccount(credential: ASAuthorizationAppleIDCredential) {
  // You *should* have a fully registered account here.  If you get back an error
  // from your server that the account doesn't exist, you can look in the keychain 
  // for the credentials and rerun setup

  // if (WebAPI.login(credential.user, 
  //                  credential.identityToken,
  //                  credential.authorizationCode)) {
  //   ...
  // }

  self.signInSucceeded(true)
}

 

이 메소드에 있는 코드는 앱마다 매우 다릅니다. 서버로부터 사용자가 등록되지 않았다는 오류를 받은 경우에, retrieve()를 사용해서 키체인을 조회해야 합니다. 반환된 UserData 구조체의 세부사항으로 최종 사용자 등록을 다시 시도합니다. 

사용자이름과 비밀번호(Username and Password)

다른 용도로, Sign In with Apple을 사용할때, 최종 사용자는 사이트에 대한 iCloud 키체인에 이미 저장된 자격증명(credentials)을 선택할 것입니다. authorizationConroller(controller:didCompleteWithAuthorization:)에 대한 두번째 case문에 다음에 오는 줄을 추가하세요.

signInWithUserAndPassword(credential: passwordCredential)

 

그리고나서 signInWithExistingAccount(credential:)바로 아래에 적절한 메소드를 구현하세요.

private func signInWithUserAndPassword(credential: ASPasswordCredential) {
  // if (WebAPI.login(credential.user, credential.password)) {
  //   ...
  // }
  self.signInSucceeded(true)
}

 

다시 한번 얘기하지만, 앱에 따라 구현이 다릅니다. 하지만, 서버 로그인을 호출하고 사용자이름과 비밀번호를 전달하려고 할 것입니다. 서버가 사용자에데 대해서 알지 못하는 경우에, 이메일과 이름에 대한 키체인에 사용가능한 세부정보가 없으므로 전체 등록 과정을 실행해야 할 것입니다.

버튼 누르기 처리 완료하기(Finish Handling Button Press)

ContentView.swift로 돌아가서, 방금 만들었던 델리게이터(delegate)를 저장하기 위한 프로퍼티가 필요할 것입니다. 클래스의 상단에, 다음 코드를 추가합니다.

@State var appleSignInDelegates: SignInWithAppleDelegates! = nil

 

@State는 그 구조체가 소유하고 업데이트하는 가변 컨텐츠인 것을 SwiftUI에 알려주는 방법입니다. 모든 @State 프로퍼티들은 반드시 실제 값을 저장해야 하며, 현재 nil을 할당한 이유입니다.

이제, 동일한 파일에서, controller 생성하는 부분을 다음과 같이 교체해서 showAppleLogin()을 마무리합니다.

// 1
appleSignInDelegates = SignInWithAppleDelegates() { success in
  if success {
    // update UI 
  } else {
    // show the user an error
  }
}

// 2
let controller = ASAuthorizationController(authorizationRequests: [request])
controller.delegate = appleSignInDelegates

// 3
controller.performRequests()

 

다음은 무슨 일을 하는 지 입니다.

  1. 델리게이트(delegate)를 만들고 클래스의 프로퍼티에 할당합니다.
  2. 이전처럼 ASAuthorizationController을 만들지만, 이번에는, 사용자정의 델리게이트 클래스를 사용합니다.
  3. performRequests()를 호출해서, iOS에 Sign In with Apple 모달 뷰를 표시하도록 요청합니다.

델리게이트(delegate)의 콜백(callback)은 최종 사용자가 앱을 성공적으로 인증했는지 여부에 따라 필요한 프리젠테이션 변경사항을 처리합니다.

자동 로그인(Automate Sign In)

여러분은 Sign In with Apple을 구현했지만, 사용자는 버튼을 명시적으로 탭해야합니다. 로그인 페이지로 이동하는 경우에, 이미 Sign In with Apple가 구성되었는지 확인해야 합니다. ContentView.swift로 돌아가서, .onApperar 블럭에 다음 줄을 추가합니다.

self.performExistingAccountSetupFlows()

 

주의
SwiftUI의 .onAppear { }은 UIKit의viewDidAppear(_:)과 본질적으로 동일한 것입니다.

뷰가 나타날때, iOS는 앱과 관련된 자격증명에 대하 Apple ID와 iCloud 키체인 모두 확인하길 원합니다. 만약 그것들이 존재하는 경우에, Sign In with Apple 다이얼로그를 자동으로 보여줄 것이며, 사용자가 버튼을 수동으로 누를 필요가 없습니다. 버튼을 누르고 자동으로 2개의 공유 코드를 호출하므로, showAppleLogin 메소드를 다음 2개의 메소드로 리팩토링 하세요.

private func showAppleLogin() {
  let request = ASAuthorizationAppleIDProvider().createRequest()
  request.requestedScopes = [.fullName, .email]
  
  performSignIn(using: [request])
}

private func performSignIn(using requests: [ASAuthorizationRequest]) {
  appleSignInDelegates = SignInWithAppleDelegates() { success in
    if success {
      // update UI
    } else {
      // show the user an error
    }
  }

  let controller = ASAuthorizationController(authorizationRequests: requests)
  controller.delegate = appleSignInDelegates

  controller.performRequests()
}

 

이는 델리게이터 생성과 보여주는 것을 자체 메소드로 이동하는 것외에 다른 코드 변경은 없습니다.

이제 performExistingAccountSetupFlows()를 구현하세요.

private func performExistingAccountSetupFlows() {
  // 1
  #if !targetEnvironment(simulator)

  // 2
  let requests = [
    ASAuthorizationAppleIDProvider().createRequest(),
    ASAuthorizationPasswordProvider().createRequest()
  ]

  // 2
  performSignIn(using: requests)
  #endif
}

 

여기에는 몇 단계만 있습니다.

  1. 만약 시뮬레이터를 사용하는 경우에는, 아무일도 일어나지 않습니다. 시뮬레이터는 해당 호출에 대해 오류를 출력할 것입니다.
  2. Apple에 Apple ID와 iCloud 키체인 확인에 대한 요청합니다.
  3. 기존 설정 코드를 호출합니다.

2단계에서, 검색하고자 하는 최종사용자 세부사항을 지정하지 않는 것을 주목합니다. 튜토리얼의 앞부분에서, 세부사항이 한번반 제공된다는 것을 배웠습니다. 이러한 흐름은 기존 계정을 확인하는데 사용되며, requestedScopes 프로퍼티를 지정할 이유가 없습니다. 사실상, 여기에 설정하는 경우에, 그것은 간단히 무시될 것입니다.

웹 자격 증명(Web Credentials)

앱에 전용 웹사이트가 있는 경우에, 조금 더 나아가서 웹 자격 증명을 다룰수 있습니다. UserAndPassword.swift를 살펴보면, SharedWebCredential(domain:)을 호출하는 것을 보게 될 것이며, 생성자에 현재 비어있는 문자열을 보냅니다. 웹 사이트의 도메인을 교체하세요.

이제, 웹사이트에 로그인하고 사이트의 root에 .well-known이라는 디렉토리를 만듭니다. 그곳에 apple-app-site-association이라는 새 파일을 만들고 다음에 오는 JSON을 붙여넣기 하세요.

{
    "webcredentials": {
        "apps": [ "ABCDEFGHIJ.com.raywenderlich.SignInWithApple" ]
    }
}

주의
파일이름에 확장자가 없는지 확인하세요.

ABCDEFGHIJ를 여러분의 팀의 10-문자 Team ID로 교체하고 싶을 것입니다. https://developer.apple.com/account에서 Membership 탭 아래에서 Team ID를 찾을 수 있습니다. 또한 번들 식별자를 앱에서 사용하는 것과 일치하도록 해야 합니다.

이런 단계를 통해서, Safari의 저장된 로그인 세부사항과 앱의 로그인 세부사항을 연결했습니다. 이제 Sign in with Apple를 사용할 수 있을 것입니다.

사용자가 수동으로 사용자이름과 비밀번호를 입력할때 자격 증명이 저장되어 나중에 사용할 수 있게 됩니다.

실시간 검사(Runtime Checks)

앱이 살아있는 동안에 언제든지, 사용자는 기기 설정으로 이동할수 있고 앱에 대한 Sign In with Apple를 비활성화 할 수 있습니다. 여러분은 수행할 동작에 따라 여전히 로그인 되어 있는지를 확인하고 싶을 것입니다. Apple은 다음 코드를 실행하는 것을 권장합니다.

let provider = ASAuthorizationAppleIDProvider()
provider.getCredentialState(forUserID: "currentUserIdentifier") { state, error in
  switch state {
  case .authorized:
    // Credentials are valid.
    break
  case .revoked:
    // Credential revoked, log them out
    break
  case .notFound:
    // Credentials not found, show login UI
    break
  }
}

 

Apple은 getCredentialState(forUserId:) 호출이 매우 빠르다고 말했습니다. 따라서 앱을 시작하는 동안과 사용자가 여전히 인증되었는지 확인해야 할때마다 실행해야 합니다. 꼭 필요한 경우가 아니라면 앱을 시작할때 실행하지 않는 것을 권장합니다. 앱에 실제로 로그인 되었거나 등록된 사용자가 필요합니다. 실제로 로그인해야 하는 동작을 수행할때까지 로그인 하지 않아도 됩니다. 사실상, Human Interface Guidelines 에서도 이를 권장합니다!

가장 먼저 등록을 요청한 경우에 많은 사용자들이 방금 다운로드된 앱을 제거합니다.

대신에, 사용자가 로그아웃 할때를 알리기 위해 Apple이 제공하는 알림을 들어보세요. ASAuthorizationAppleIDProvider.credentialRevokedNotification알림을 수신하고 적절한 조치를 취하세요. 

The UIWindow

이 시점에, Sign In with Apple을 모두 구현했습니다. 축하합니다!

Sign In with Apple WWDC 프리젼테이션을 보거나 다른 튜토리얼을 읽은 경우에, 여기에서 놓친 부분이 있다는 것을 알아챌지도 모릅니다. iOS에서 사용할 UIWindow를 알려주는ASAuthorizationControllerPresentationContextProving델리게이트 메소드를 구현하지 않았습니다. 기본 UIWindow를 사용하는 경우에 기술적으로 필요하지 않지만, 처리하는 방법을 아는 것이 좋습니다.

SwiftUI를 사용하지 않은 경우에, SceneDelegate에서 window프로퍼티를 가져와서 값을 반환하는 것은 매우 간단합니다. SwiftUI에서는, 훨씬 더 어려워집니다.

환경(The Environment)

SwiftUI의 새로운 개념은 Environment입니다. 그것은 많은 SwiftUI 뷰에서 사용할 수 있는 데이터를 저장할 수 있는 영역입니다. 어느 정도는 의존성 주입(dependency injection)처럼 생각할 수 있습니다.

환경 설정(Environment Setup)

EnvironmentWindowKey.swift를 살펴보고, SwiftUI environment에 값을 저장하기 위해 필요한 코드를 볼수 있습니다. 이는 @Environment 프로퍼티 래퍼에 전달할 키와 저장될 값을 정의하는 상용구(boilerplate) 코드 입니다. 클래스 타입이 저장되고 있기에, retain cycle을 막기위해 명시적으로 weak 참조로 표시합니다. 

주의
environment 코드는 WWDC labs에서 Apple 엔지니어에 의해 제공되었습니다.

ContentView 변경(ContentView Changes)

ContentView.swift로 돌아가서 ContentView의 상단에 다른 프로퍼티를 추가하세요.

@Environment(\.window) var window: UIWindow?

 

iOS는 environment에 저장된 값으로 window 변수를 자동으로 채워넣습니다.

performSignIn(using:)에서, window 프로퍼티를 전달하는 생성자를 수정하세요.

appleSignInDelegates = SignInWithAppleDelegates(window: window) { success in

 

presentationContextProvider 델리게이트에 대한 클래스를 사용하기 위해 ASAuthorizationController에 알리고자 할 것이므로, controller.delegate 할당하는 바로 뒤에 다음 코드를 추가하세요.

controller.presentationContextProvider = appleSignInDelegates

 

델리게이트 업데이트(Update the Delegate)

클래스의 새로운 프로퍼티와 생성자 변경을 처리하기 위해SignInWithAppleDelegates.swift를 엽니다. 클래스 정의를 교체하지만, 다음과 같이 모든 등록과 델리게이트 메소드가 포함된 확장은 아닙니다

class SignInWithAppleDelegates: NSObject {
  private let signInSucceeded: (Bool) -> Void
  // 1
  private weak var window: UIWindow!

  // 2
  init(window: UIWindow?, onSignedIn: @escaping (Bool) -> Void) {
    // 3
    self.window = window
    self.signInSucceeded = onSignedIn
  }
}

 

몇가지가 변경되었습니다.

  1. window에 새로운 weak 참조를 저장합니다.
  2. 초기화에 첫번째 인자로 UIWindow 매개변수를 추가합니다.
  3. 프로퍼티에 전달된 값을 저장합니다.

마지막으로, 새로운 델리게이트 타입을 구현하세요.

extension SignInWithAppleDelegates: 
    ASAuthorizationControllerPresentationContextProviding {
  func presentationAnchor(for controller: ASAuthorizationController) 
      -> ASPresentationAnchor {
    return self.window
  }
}

그 델리게이트는 window를 반환할 것으로 예상되는 단일 메소드만 가지고 있으며, Sign In with Apple 모달 다이얼로그를 보여줍니다.

장면 업데이트(Update the Scene)

UIWindw를 environment에 보내는 것이 하나 남아 있습니다. SceneDelegate.swift를 열고 다음 줄을 교체하세요.

window.rootViewController = UIHostingController(rootView: ContentView())

 

다음 줄로 교체합니다.

// 1
let rootView = ContentView().environment(\.window, window)

// 2
window.rootViewController = UIHostingController(rootView: rootView)

 

2가지 작은 단계를 모두 수행합니다.

  1. ContentView를 생성하고 window 변수의 값에 추가하세요.
  2. 기존 초기화 대신에 UIHostingController에 rootView 변수를 전달하세요.

environment 메소드는 기본적으로 ContentView를 사용한다는 의미의 some View를 반환하며, 뷰의 environment에 전달한 값을 밀어넣고나서 다시 ContentView를 반환합니다. 모든 SwiftUI View는 ContentView에서 제공되는 environment에 있는 값을 유지할 것입니다.

다른 곳에서 새로운 root 뷰를 생성하는 경우에, 해당 root는 명시적으로 다시 전달하지 않는 한 environment 값을 포함하지 않을 것입니다.

로그인은 스크롤되지 않습니다(Logins Do not Scroll)

염두해야하는 Sign In with Apple의 단 한가지 단점은 해당 window가 iOS 화면에서 스크롤되지 않는다는 것입니다. 대부분의 사용자에게 중요하지 않지만, 주의해야 합니다. 앱에서 사용하는 사이트의 소유자로써, 예를 들어, 수많은 로그인이 있습니다. 앱 자체 로그인이 있을뿐만 아니라, SQL 데이터베이스, PHP 관리 사이트, 등등에 대한 로그인이 있습니다.

로그인이 너무 많은 경우에, 최종 사용자가 실제로 필요한 것을 보지 못할 수도 있습니다. 앱에 사이트를 연결하는 경우에 사이트에 앱에 중요한 사이트 로그인만 있는지 확인해보세요. 모든 앱을 하나의 도메인 번들로 만들지 마세요. 

여기에서 어디로 가야하나요?(Where to Go From Here?)

튜토리얼의 상단과 하단에서 완성된 프로젝트를 다운로드 할 수 있습니다,

SignInWithAppleDelegates.swift는 현재 Boolean 성공을 반환하지만, Swift 5의 Result 타입 같은것을 사용해서 서비에서의 데이터 뿐만아니라 오류시 사용자정의 오류 타입을 반환할 수 있도록 합니다.

반응형

'SwiftUI' 카테고리의 다른 글

SwiftUI + UIKit  (0) 2023.05.10
Using TimelineView and Canvas in SwiftUI  (0) 2022.01.27
Understanding Data Flow in SwiftUI  (0) 2021.01.05
How to Create a Neumorphic Design With SwiftUI  (1) 2020.06.01
How to Create a Splash Screen With SwiftUI  (0) 2019.09.06
SwiftUI  (0) 2019.07.23
SwiftUI: Getting Started  (0) 2019.06.27
Posted by 까칠코더
,