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

Firebase数据上传时最后字符丢失问题的解决请求

字符串末尾字符上传Firebase时频繁被截断的解决方案

问题现象

向Firebase上传数据时,超半数概率出现最后一个字符被截断:

  • 创建用户账号时,用户名丢失最后一个字母
  • 内置论坛发帖时,标题或正文最后一个字符被移除
  • 无论字符串长度为8位还是30位,均会出现该问题

相关代码

向Firebase存储数据的新用户注册函数

static func signupUser (username: String, email: String, password: String, imageData: Data, onSuccess: @escaping(_ user: User) -> Void, onError: @escaping(_ errorMessage: String) -> Void) {
        //Firebase.createAccount(username: username, email: email, password: password)
        Auth.auth().createUser(withEmail: email, password: password) { (authData, error) in
            if error != nil {
                print(error!.localizedDescription)
                onError(error!.localizedDescription)
                return
            }
            
            guard let userId = authData?.user.uid else { return }            
           
            let storageAvatarUserId = Ref.STORAGE_AVATAR_USERID(userId: userId)
            let metadata = StorageMetadata()
            metadata.contentType = "image/jpg"
            
            StorageService.saveAvatar(userId: userId, username: username, email: email, imageData: imageData, metadata: metadata, storageAvatarRef: storageAvatarUserId, onSuccess: onSuccess, onError: onError)            
        }
    }
}

注册视图模型

class SignupViewModel: ObservableObject {
     var username: String = ""
     var email: String = ""
     var password: String = ""
     var image: Image = Image(IMAGE_USER_PLACEHOLDER)
     var imageData: Data = Data()
     var errorString = ""
     @Published var showImagePicker: Bool = false
     @Published var showAlert: Bool = false
        
    func signUp(username: String, email: String, password: String, imageData: Data, completed: @escaping(_ user: User) -> Void, onError: @escaping(_ errorMesssage: String) -> Void) {
        if !username.isEmpty && !email.isEmpty && !password.isEmpty && !imageData.isEmpty {
           AuthService.signupUser(username: username, email: email, password: password, imageData: imageData, onSuccess: completed, onError: onError)
        } else {
            showAlert = true
            errorString = "Please fill in all fields"
        }
    }
}

注册视图

@ObservedObject var signupViewModel = SignupViewModel()
      
    func signUp() {
        signupViewModel.signUp(username: signupViewModel.username, email: signupViewModel.email, password: signupViewModel.password, imageData: signupViewModel.imageData, completed: { (user) in
            print(user.email)
            self.clean()
            //Switch to main app
        }) { (errorMessage) in
            print("Error: \(errorMessage)")
            self.signupViewModel.showAlert = true
            self.signupViewModel.errorString = errorMessage
            self.clean()
        }
    }
    
    func clean() {
        self.signupViewModel.username = ""
        self.signupViewModel.email = ""
        self.signupViewModel.password = ""
    }
    
    var body: some View {
        
        VStack {
        
                VStack{
                    
            signupViewModel.image
                        .resizable()
                        .aspectRatio(contentMode: .fill)
                        .frame(width: 80, height: 80)
                        .clipShape(Circle())
                        .onTapGesture {
                            self.signupViewModel.showImagePicker = true
                        }
                    
                    Text("Tap user icon to select a profile picture.")
                        .font(.footnote)
                        .foregroundColor(.gray)
                        .padding(.bottom, 15)
                    
                    
            TextField("Username", text: $signupViewModel.username)
                        .padding()
                        .background(LinearGradient(colors: [.white.opacity(0.70), .white.opacity(0.70)], startPoint: .topLeading, endPoint: .bottomTrailing))
                        .cornerRadius(8)
                    // shadow effect...
                        .shadow(color: Color.black.opacity(0.1), radius: 5, x: 0, y: 5)
                        .shadow(color: Color.black.opacity(0.08), radius: 5, x: 0, y: -5)
                    
                
        }
        .padding(.horizontal, 25)
        .padding(.top, 15)
     
        
        
        VStack(alignment: .leading, spacing: 12) {
            
            
            TextField("Email", text: $signupViewModel.email)
                .padding()
                .background(LinearGradient(colors: [.white.opacity(0.70), .white.opacity(0.70)], startPoint: .topLeading, endPoint: .bottomTrailing))
                .cornerRadius(8)
            // shadow effect...
                .shadow(color: Color.black.opacity(0.1), radius: 5, x: 0, y: 5)
                .shadow(color: Color.black.opacity(0.08), radius: 5, x: 0, y: -5)
            }
        .padding(.horizontal, 25)
        .padding(.top, 10)
        
        
        
            VStack(alignment: .leading, spacing: 15) {
                
                SecureField("Password", text: $signupViewModel.password)
                    .padding()
                    .background(LinearGradient(colors: [.white.opacity(0.70), .white.opacity(0.70)], startPoint: .topLeading, endPoint: .bottomTrailing))
                    .cornerRadius(8)
                // shadow effect...
                    .shadow(color: Color.black.opacity(0.1), radius: 5, x: 0, y: 5)
                    .shadow(color: Color.black.opacity(0.08), radius: 5, x: 0, y: -5)
                
            }
            .padding(.horizontal, 25)
            .padding(.top, 10)
            
        
        
        Button(action:  signUp) {
                
                Text("Sign Up").foregroundColor(.black)
                    .font(.system(size: 20))
                    .fontWeight(.bold)
                    .padding(.vertical)
                    .frame(width: UIScreen.main.bounds.width - 50)
                    .background(LinearGradient(colors: [Color("1"), Color("2")], startPoint: .topLeading, endPoint: .bottomTrailing))
                    .cornerRadius(8)
        }.alert(isPresented: $signupViewModel.showAlert) {
            Alert(title: Text("Error"), message: Text(self.signupViewModel.errorString), dismissButton: .default(Text("OK")))
        }
            .padding(.horizontal, 25)
            .padding(.top, 20)
}

可能的原因及解决方案

1. SwiftUI TextField实时更新与clean()方法冲突

用户刚输入最后一个字符就点击按钮时,SwiftUI的@Published属性更新是异步的,此时clean()立即清空字段,可能导致上传的是未完成更新的字符串(缺失最后一个字符)。

解决方法:
先保存输入内容的快照,避免clean()干扰,仅在操作成功后清空字段:

func signUp() {
    // 保存当前输入的快照
    let currentUsername = signupViewModel.username
    let currentEmail = signupViewModel.email
    let currentPassword = signupViewModel.password
    let currentImageData = signupViewModel.imageData
    
    signupViewModel.signUp(username: currentUsername, email: currentEmail, password: currentPassword, imageData: currentImageData, completed: { (user) in
        print(user.email)
        self.clean() // 仅成功后清空
        //Switch to main app
    }) { (errorMessage) in
        print("Error: \(errorMessage)")
        self.signupViewModel.showAlert = true
        self.signupViewModel.errorString = errorMessage
        // 失败时保留输入,方便用户修改
    }
}

2. StorageService.saveAvatar的字符串处理问题

检查saveAvatar方法是否对username进行了不必要的截断(比如误写prefix(upTo: username.count - 1)),或Firestore字段设置了错误的长度限制。

解决方法:

  • 查看saveAvatar实现,确保无截取操作
  • 确认Firestore对应字段无不合理的长度限制

3. 键盘收起时的文本同步延迟

点击按钮时键盘未完全收起,TextField内容可能未同步到绑定属性。

解决方法:
强制收起键盘并延迟获取输入内容,确保同步完成:

func signUp() {
    // 收起键盘
    UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
    
    // 延迟0.1秒确保文本同步
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
        let currentUsername = self.signupViewModel.username
        let currentEmail = self.signupViewModel.email
        let currentPassword = self.signupViewModel.password
        let currentImageData = self.signupViewModel.imageData
        
        self.signupViewModel.signUp(username: currentUsername, email: currentEmail, password: currentPassword, imageData: currentImageData, completed: { (user) in
            print(user.email)
            self.clean()
            //Switch to main app
        }) { (errorMessage) in
            print("Error: \(errorMessage)")
            self.signupViewModel.showAlert = true
            self.signupViewModel.errorString = errorMessage
        }
    }
}

4. 字符串编码/日志排查

在saveAvatar开头添加日志,确认上传前字符串是否完整:

// 在StorageService.saveAvatar中添加
print("Uploading username: \(username), length: \(username.count)")

如果日志显示字符串完整,问题出在Firebase存储端;如果已缺失,排查视图或ViewModel层的字符串处理逻辑。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 19:48:23