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

SwiftUI集成Apple与Google登录按钮布局报错求助

解决“()->() cannot conform to shape style”错误并实现Apple/Google登录按钮并排显示

错误原因分析

该错误通常是编译器将闭包错误解析为ShapeStyle类型参数导致的,结合你的代码,核心问题是自定义按钮与系统登录按钮叠加时的语法歧义,以及部分样式设置的写法引发编译器解析混乱。

修正后的完整代码

import SwiftUI
import AuthenticationServices
import GoogleSignIn
import GoogleSignInSwift
import Firebase

struct Login: View {
    @StateObject var loginModel = LoginViewModel()
    
    var body: some View {
        ScrollView(.vertical, showsIndicators: false){
            VStack(alignment: .leading, spacing: 5){
                Image(systemName: "person")
                    .resizable()
                    .aspectRatio(contentMode: .fill)
                    .frame(width: 75, height: 75)
                    .foregroundColor(.black)
                    .ignoresSafeArea()
                
                (Text("Welcome")
                    .foregroundColor(.black) +
                 Text("\n Login to Continue")
                    .foregroundColor(.secondary)
                )
                .font(.title)
                .fontWeight(.semibold)
                .lineSpacing(10)
                .padding(.top, 20)
                .padding(.trailing, 15)
                
                // 自定义手机号输入框
                CustomTextField(hint: "+16505551234", text: $loginModel.mobileNo)
                    .disabled(loginModel.showOTPField)
                    .opacity(loginModel.showOTPField ? 0.4 : 1)
                    .overlay(alignment: .trailing, content:{
                        Button("Change"){
                            withAnimation(.easeInOut){
                                loginModel.showOTPField = false
                                loginModel.otpCode = ""
                                loginModel.CLIENT_CODE = ""
                            }
                        }
                        .font(.caption)
                        .foregroundColor(.indigo)
                        .opacity(loginModel.showOTPField ? 1 : 0)
                        .padding(.trailing, 15)
                    })
                    .padding(.top, 40)
                
                // OTP输入框
                CustomTextField(hint: "OTP Code", text: $loginModel.otpCode)
                    .disabled(!loginModel.showOTPField)
                    .opacity(!loginModel.showOTPField ? 0.4 : 1)
                    .padding(.top, 20)
                
                // 获取/验证OTP按钮
                Button(action: loginModel.showOTPField ? loginModel.verifyOTPCode : loginModel.getOTPCode) {
                    HStack(spacing: 15){
                        Text(loginModel.showOTPField ? "Verify Code" : "Get OTP")
                            .fontWeight(.semibold)
                            .contentTransition(.identity)
                        Image(systemName: "line.diagonal.arrow")
                            .font(.title3)
                            .rotationEffect(.init(degrees: 45))
                    }
                    .foregroundColor(.black)
                    .padding(.horizontal, 25)
                    .padding(.vertical)
                    .background(
                        RoundedRectangle(cornerRadius: 10, style: .continuous)
                            .fill(Color.black.opacity(0.05))
                    )
                    .padding(.top, 10)
                }
                
                // 分割文本
                Text("(OR)")
                    .foregroundColor(.gray)
                    .frame(maxWidth: .infinity)
                    .padding(.top,10)
                    .padding(.bottom, 20)
                    .padding(.horizontal)
                
                // 第三方登录按钮组
                HStack (spacing: 10){
                    // Apple登录按钮
                    SignInWithAppleButton{ request in
                        loginModel.nonce = randomNonceString()
                        request.requestedScopes = [.email, .fullName]
                        request.nonce = sha256(loginModel.nonce)
                    } onCompletion: { result in
                        switch result{
                        case .success(let user):
                            guard let credential = user.credential as? ASAuthorizationAppleIDCredential else{
                                print("Error with Firebase")
                                return
                            }
                            loginModel.appleAuthenticate(credential: credential)
                        case .failure(let error):
                            print(error.localizedDescription)
                        }
                    }
                    .signInWithAppleButtonStyle(.black)
                    .frame(height: 55)
                    .cornerRadius(10)
                    
                    // Google登录按钮
                    if let clientID = FirebaseApp.app()?.options.clientID {
                        GoogleSignInButton(action: {
                            // 获取当前根视图控制器
                            guard let rootVC = UIApplication.shared.connectedScenes
                                .filter({$0.activationState == .foregroundActive})
                                .first(where: {$0 is UIWindowScene})?
                                .delegate as? UIWindowSceneDelegate??.window??.rootViewController else {
                                print("Could not get root view controller")
                                return
                            }
                            
                            GIDSignIn.sharedInstance.signIn(with: GIDConfiguration(clientID: clientID), presenting: rootVC) { user, error in
                                if let error = error {
                                    print(error.localizedDescription)
                                    return
                                }
                                if let user = user {
                                    loginModel.logGoogleUser(user: user)
                                }
                            }
                        })
                        .frame(height: 55)
                        .cornerRadius(10)
                    }
                }
                .frame(maxWidth: .infinity)
            }
            .padding(.horizontal, 60)
            .padding(.vertical,15)
        }
        .alert(loginModel.errorMessage, isPresented: $loginModel.showError){}
    }
}

// 自定义输入框组件
struct CustomTextField: View {
    let hint: String
    @Binding var text: String
    
    var body: some View {
        TextField(hint, text: $text)
            .padding()
            .background(RoundedRectangle(cornerRadius: 10).fill(Color.black.opacity(0.05)))
    }
}

// 登录逻辑ViewModel
class LoginViewModel: ObservableObject {
    @Published var mobileNo = ""
    @Published var otpCode = ""
    @Published var CLIENT_CODE = ""
    @Published var showOTPField = false
    @Published var errorMessage = ""
    @Published var showError = false
    var nonce = ""
    
    func getOTPCode() {
        // 实现获取OTP的业务逻辑
        showOTPField = true
    }
    
    func verifyOTPCode() {
        // 实现验证OTP的业务逻辑
    }
    
    func appleAuthenticate(credential: ASAuthorizationAppleIDCredential) {
        // 实现Apple登录Firebase的逻辑
    }
    
    func logGoogleUser(user: GIDGoogleUser) {
        // 实现Google登录Firebase的逻辑
    }
}

// Apple登录辅助函数
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
}

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
}

// 预览视图
struct Login_Previews: PreviewProvider {
    static var previews: some View {
        Login()
    }
}

关键修改说明

  1. 移除自定义按钮叠加逻辑:直接使用系统原生的SignInWithAppleButton和GoogleSignInButton,避免自定义视图与系统按钮叠加导致的样式冲突和语法歧义。
  2. 修正Google登录视图控制器获取方式:使用标准的UIApplication场景API获取根视图控制器,避免自定义方法引发的潜在错误。
  3. 简化样式写法:使用.cornerRadius替代自定义RoundedRectangle叠加,消除编译器对ShapeStyle的解析混乱。
  4. 修正预览视图:确保预览的是Login视图而非ContentView,避免不必要的警告。
  5. 规范方法命名:将VerifyOTPCode改为小写开头的verifyOTPCode,符合Swift命名规范。

额外检查项

如果仍有错误,请确认:

  • GoogleSignIn、GoogleSignInSwift的版本与当前Xcode/Swift版本兼容。
  • Firebase已正确初始化,FirebaseApp配置无误。
  • LoginViewModel中的方法签名与回调闭包参数类型完全匹配。

内容的提问来源于stack exchange,提问作者Sathish Kumar V

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 09:17:02