【Swift】Apple Sign In ボタンの実装
初めに
Swift で Apple Sign In ボタンを実装する際、何点かつまづいた点があったので、書き留めておきます。
記事の対象者
- Swift学習者
- Apple Sign In ボタンを実装したい方
- App Store へアプリをリリースしたい方
実装
コード
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 ボタンの大きさが変わってしまう問題
以下の画像のように特定のデバイスでテストするとボタンのサイズが変化してしまう問題がありました。
解決策
以下のコードでボタンの高さに関して最大値と最小値を設定することで解消できました。
.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 ボタンの上に用意していたフォームのフォーカスを操作するためのコードでした。
フォーム以外の部分がタップされた場合にフォームのフォーカスを外す処理を実装していましたが、それが原因でボタンが押されても反応しなかったようです。
.onTapGesture {
UIApplication.shared.endEditing()
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
isTextfieldEditting = false
}
このコードを削除することで解決することができました。
ユーザー認証あたりの問題かと思い込んでいましたが、意外な原因だったのでつまづきました。
以上です。
あとがき
最後まで読んでいただきありがとうございました。
Apple Sign In は App Store にリリースする際には必ず必要になるものなので、スムーズに実装できるようになればかなり時間の短縮にもなるかと思います。
誤っている箇所があればご指摘いただければ幸いです。
Discussion