🍎

【Swift】Apple Sign In ボタンの実装

2023/07/31に公開

初めに

Swift で Apple Sign In ボタンを実装する際、何点かつまづいた点があったので、書き留めておきます。

記事の対象者

  • Swift学習者
  • Apple Sign In ボタンを実装したい方
  • App Store へアプリをリリースしたい方

実装

コード

apple_sign_in_button.dart
struct EntryAuthView: View {
    @State var isShowAppleAlert: Bool = false
    @State var appleAlertMessage: String = ""
    @State var isAppleSignInSuccess: Bool = false
        // MARK: - Firebase用
    private func randomNonceString(length: Int = 32) -> String {
        precondition(length > 0)
        let charset: [Character] =
        Array("0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._")
        var result = ""
        var remainingLength = length
        
        while remainingLength > 0 {
            let randoms: [UInt8] = (0 ..< 16).map { _ in
                var random: UInt8 = 0
                let errorCode = SecRandomCopyBytes(kSecRandomDefault, 1, &random)
                if errorCode != errSecSuccess {
                    fatalError(
                        "Unable to generate nonce. SecRandomCopyBytes failed with OSStatus \(errorCode)"
                    )
                }
                return random
            }
            
            randoms.forEach { random in
                if remainingLength == 0 {
                    return
                }
                
                if random < charset.count {
                    result.append(charset[Int(random)])
                    remainingLength -= 1
                }
            }
        }
        
        return result
    }
    
    // MARK: - Firebase用
    @available(iOS 13, *)
    private func sha256(_ input: String) -> String {
        let inputData = Data(input.utf8)
        let hashedData = SHA256.hash(data: inputData)
        let hashString = hashedData.compactMap {
            String(format: "%02x", $0)
        }.joined()
        
        return hashString
    }
    // MARK: - Firebase用
    @State  var currentNonce:String?
    
    SignInWithAppleButton(.signIn) { request in
        request.requestedScopes = [.email,.fullName]
        let nonce = randomNonceString()
        currentNonce = nonce
        request.nonce = sha256(nonce)
    } onCompletion: { result in
        switch result {
             case .success(let authResults):
                  let appleIDCredential = authResults.credential as? ASAuthorizationAppleIDCredential
                            
                   guard let nonce = currentNonce else {
                       fatalError("Invalid state: A login callback was received, but no login request was sent.")
                    }
                    guard let appleIDToken = appleIDCredential?.identityToken else {
                        fatalError("Invalid state: A login callback was received, but no login request was sent.")
                    }
                    guard let idTokenString = String(data: appleIDToken, encoding: .utf8) else {
                         print("Unable to serialize token string from data: \(appleIDToken.debugDescription)")
                         return
                    }
                            
                    let credential = OAuthProvider.credential(withProviderID: "apple.com",idToken: idTokenString,rawNonce: nonce)
                    print("credential: \(credential)")
                    Auth.auth().signIn(with: credential) { result, error in
                        if result?.user != nil{
                            print("result data: \(result)")
			    // ログイン処理
                            isAppleSignInSuccess = true
                            print("ログイン完了")
                        } else {
                            print("apple sign in error: \(error)")
                            isAlertShown = true
                            }
                        }
             case .failure(let error):
                 appleAlertMessage = error.localizedDescription
                 isShowAppleAlert = true
                 print("Authentication failed: \(error.localizedDescription)")
                 break
             }
        }
        .signInWithAppleButtonStyle(isDarkMode ? .white : .black)
        .frame(maxWidth: .infinity, minHeight: 44, maxHeight: 56)
}

Apple Sign In ボタンの大きさが変わってしまう問題

以下の画像のように特定のデバイスでテストするとボタンのサイズが変化してしまう問題がありました。
apple_sign_in_respoisive_img

解決策

以下のコードでボタンの高さに関して最大値と最小値を設定することで解消できました。

apple_sign_in_button.dart
.frame(maxWidth: .infinity, minHeight: 44, maxHeight: 56)

Apple Sign In ボタンのサイズについてはAppleのサイトで定められているので、それを参考にしました。

https://developer.apple.com/design/human-interface-guidelines/sign-in-with-apple

タップしても反応しない問題①

今回は FirebaseAuth を使ってユーザー管理をしていたのですが、Apple Sign In ボタンを押してもユーザーが作成されない問題がありました。
問題の原因はプロジェクトのBundle id と Firebase に登録していた Bundle id が異なることでした。Bundle id は App Store Connect で申請する時に間違って別の名前で登録してしまった時など特別なことがないと変更しないため、見落としがちです。

解決策

プロジェクトの Bundle id を確認して、Firebase のアプリの Bundle id を変更する
プロジェクトの Bundle id は XCode > Targets > Signing & Capabilities > Bundle Identifier で確認できます。
Firebase のアプリの Bundle id は 「プロジェクトの概要」の横の歯車 > プロジェクトの設定 > 全般 > マイアプリ > バンドル ID で確認できます。

Firebase は登録したアプリの Bundle id を変更することができないため、一度「このアプリを削除」を押して作り直した方が早いこともあります。

タップしても反応しない問題②

Bundle id の問題を解決した後、ボタンを長押しすれば反応するようになしましたが、通常のタップだと反応しない問題に陥りました。

解決策

問題の原因は以下のコードで、Apple Sign In ボタンの上に用意していたフォームのフォーカスを操作するためのコードでした。
フォーム以外の部分がタップされた場合にフォームのフォーカスを外す処理を実装していましたが、それが原因でボタンが押されても反応しなかったようです。

apple_sign_in_button.dart
.onTapGesture {
      UIApplication.shared.endEditing()
      UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
      isTextfieldEditting = false
}

このコードを削除することで解決することができました。
ユーザー認証あたりの問題かと思い込んでいましたが、意外な原因だったのでつまづきました。

以上です。

あとがき

最後まで読んでいただきありがとうございました。
Apple Sign In は App Store にリリースする際には必ず必要になるものなので、スムーズに実装できるようになればかなり時間の短縮にもなるかと思います。
誤っている箇所があればご指摘いただければ幸いです。

Discussion