You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中Apple登录Firebase Auth无法获取用户姓名的解决方法

解决Apple登录获取姓名为空并存入数据库的问题

核心原因

Apple登录的fullName字段仅在用户首次授权登录时返回,后续用户再次登录(比如通过Face ID快速登录)时,这个字段会是空的。所以必须抓住首次登录的时机把姓名存入数据库,后续登录直接从数据库读取展示。

代码修正方案

//
//  AuthenticationViewModel.swift
//  TrackMe
//

import Foundation
import SwiftUI
import AuthenticationServices
import CryptoKit
import FirebaseAuth

@MainActor
class AuthenticationViewModel : ObservableObject{
    @Published var currentNonce : String? = ""
    @Published var errorMessage: String = "" // 补上之前缺失的errorMessage属性
    private let viewModel: UsersViewModel = UsersViewModel() // 改用let,无需ObservedObject
}

extension AuthenticationViewModel {
 
    func handleSignInWithAppleRequest(_ request: ASAuthorizationAppleIDRequest){
        request.requestedScopes = [.fullName, .email]
        let nonce = randomNonceString()
        currentNonce = nonce
        request.nonce = sha256(nonce)
    }
    
    func handleSignInWithAppleCompletion(_ result: Result<ASAuthorization, Error>){
        switch result {
        case .failure(let failure):
            errorMessage = failure.localizedDescription
        case .success(let success):
            guard let appleIDCredential = success.credential as? ASAuthorizationAppleIDCredential else {
                errorMessage = "无法获取Apple授权凭证"
                return
            }
            
            guard let nonce = currentNonce else{
                fatalError("无效状态:收到登录回调,但未发送登录请求")
            }
            
            guard let appleIdToken = appleIDCredential.identityToken else {
                errorMessage = "无法获取ID Token"
                return
            }
            
            guard let idTokenString = String(data: appleIdToken, encoding: .utf8) else{
                errorMessage = "无法转换Token为字符串"
                return
            }
            
            let credential = OAuthProvider.credential(withProviderID: "apple.com", idToken: idTokenString, rawNonce: nonce)
            
            Task {
                do {
                    let authResult = try await Auth.auth().signIn(with: credential)
                    let userId = authResult.user.uid
                    
                    // 处理姓名逻辑
                    var displayName: String = ""
                    if let fullName = appleIDCredential.fullName {
                        // 首次登录,拼接姓名
                        let givenName = fullName.givenName ?? ""
                        let familyName = fullName.familyName ?? ""
                        displayName = "\(givenName) \(familyName)".trimmingCharacters(in: .whitespaces)
                        
                        // 存入自定义数据库(仅首次登录调用,避免覆盖)
                        viewModel.createNewUser(userName: displayName, userId: userId)
                        
                        // 可选:同步姓名到Firebase用户信息,方便后续快速读取
                        let profileUpdate = authResult.user.createProfileChangeRequest()
                        profileUpdate.displayName = displayName
                        try await profileUpdate.commitChanges()
                    } else {
                        // 非首次登录,优先从Firebase用户信息读取,否则从自定义数据库拉取
                        if let existingName = authResult.user.displayName {
                            displayName = existingName
                        } else {
                            displayName = await viewModel.fetchUserName(for: userId) ?? "匿名用户"
                        }
                    }
                    
                    print("最终展示姓名:\(displayName)")
                    // 这里可将displayName赋值给UI展示用的变量
                } catch {
                    errorMessage = error.localizedDescription
                    print("登录错误:\(error.localizedDescription)")
                }
            }
        }
    }
    
    // 补充缺失的辅助函数
    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("无法生成随机数:SecRandomCopyBytes失败,错误码\(errorCode)")
                }
                return random
            }
            
            randoms.forEach { random in
                if remainingLength == 0 { return }
                if random < charset.count {
                    result.append(charset[Int(random)])
                    remainingLength -= 1
                }
            }
        }
        return result
    }
    
    private func sha256(_ input: String) -> String {
        let inputData = Data(input.utf8)
        let hashedData = SHA256.hash(data: inputData)
        return hashedData.compactMap { String(format: "%02x", $0) }.joined()
    }
}

关键注意事项

  • 首次登录才会返回姓名:必须在首次登录时将姓名写入自定义数据库,后续登录无法再从Apple获取该字段。
  • 兜底处理:如果用户首次登录时拒绝授权姓名,要设置默认值(比如“匿名用户”)避免UI异常。
  • Firebase同步:将姓名同步到Firebase Auth用户信息后,后续登录可以直接从authResult.user.displayName读取,无需频繁请求自定义数据库。

内容的提问来源于stack exchange,提问作者DevAcc

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 21:07:24