swiftuiswiftui-form

Swiftui Binding<String> action tried to update multiple times per frame


I am having trouble debugging an issue with input from textfields.

Information:
-MacOs Monterey Version 12.0 Beta 21A5284e
-Xcode 13.0 beta 3 13A5192j
-Physical phone IOS 15.0 19A297e

Issue explanation: When i try to type in a textfield that is binded to a viewmodel i get the error: Swiftui Binding action tried to update multiple times per frame. This will result in data los when you try to send it to firebase for example.

Data you see:

Jhon
Do
119203940

The data you actually get back:
Jho
D
119203

Code that i use:

VIEW

import SwiftUI
import Firebase
import ImagePickerView

struct EditProfileView: View {
    var storageManager = StorageManager()
    @StateObject var UpdateProfileVM = UpdateProfileViewModel()
    
    @State private var image = UIImage()
    
    @State private var contentOffset = CGFloat(0)
    @State private var showImagePicker: Bool = false
    
    var body: some View {
        ZStack(alignment: .top) {
            TrackableScrollView(offsetChanged: { offsetPoint in
                contentOffset = offsetPoint.y
            }) {
                content
            }
            
            VisualEffectBlur(blurStyle: .systemMaterial)
                .opacity(contentOffset < -16 ? 1 : 0)
                .ignoresSafeArea()
                .frame(height: 0)
        }
        .background(Color("Background3").ignoresSafeArea(.all))
        .frame(maxHeight: .infinity, alignment: .top)
        .navigationTitle("Instellingen").font(.largeTitle)
    }
    
    var content: some View {
        
        VStack(alignment: .leading, spacing: 16) {
            Text("Pas je gegevens aan")
                .font(.subheadline)
            HStack {
                CustomGreenIcon(icon: "person.circle")
                    .onChange(of: image, perform: { value in
                        storageManager.uploadProfile(image: image)
                    })
                Text("Kies een foto")
                    .foregroundColor(Color.white)
                    .font(.subheadline)
            }
            .frame(maxWidth: .infinity, alignment: .leading)
            .padding(10)
            .background(Color("Accent"))
            .cornerRadius(20)
            .onTapGesture {
                self.showImagePicker.toggle()
            }
            
            HStack {
                CustomGreenIcon(icon: "character")
                TextField("Voornaam", text: $UpdateProfileVM.voornaam)
                    .font(.subheadline)
            }
            .padding(10)
            .background(Color("BackgroundFields"))
            .cornerRadius(20)
            HStack {
                CustomGreenIcon(icon: "textformat")
                TextField("Achternaam", text: $UpdateProfileVM.achternaam)
                    .font(.subheadline)
            }
            .padding(10)
            .background(Color("BackgroundFields"))
            .cornerRadius(20)
            
            HStack {
                CustomGreenIcon(icon: "iphone")
                TextField("Telefoonnummer", text: $UpdateProfileVM.telefoonnummer)
                    .font(.subheadline)
            }
            .padding(10)
            .background(Color("BackgroundFields"))
            .cornerRadius(20)
            
            GreenButton(text: "Bijwerken")
                .onTapGesture {
                    UpdateProfileVM.UpdateProfile {
                        print("Met succes gedaan")
                    }
                }
        }
        .frame(maxHeight: .infinity, alignment: .top)
        .padding()
        .sheet(isPresented: $showImagePicker) {
            ImagePickerView(sourceType: .photoLibrary) { image in
                self.image = image
            }
        }
    }
}

struct EditProfileView_Previews: PreviewProvider {
    static var previews: some View {
        EditProfileView()
            .environmentObject(UserStore())
//          .environment(\.colorScheme, .dark)
    }
}

ViewModel

import SwiftUI
import Firebase

class UpdateProfileViewModel: ObservableObject {
    
    var voornaam: String = ""
    var achternaam: String = ""
    var telefoonnummer: String = ""
    var alertMessage: String = ""
    
    private var db = Firestore.firestore()
    
    func UpdateProfile(completion: @escaping () -> Void) {

        let userId = Auth.auth().currentUser?.uid ?? ""
        let docRef = db.collection("gebruikers").document(userId)
        
        docRef.updateData([
            "Voornaam": voornaam,
            "Achternaam": achternaam,
            "Telefoonnummer": telefoonnummer
        ]) { error in
            if let error = error {
                print("Error updaten profiel:  \(error)")
            } else {
                print("Profile updated")
            }
        }
        
        
    }
}

The function part is kinda irrelevant since once you start typing the message will start showing in the console. For simplicity, i just added a new view with only one text field with the same result.

Whenever i put the function with @state private variables then everything is just fine.

My expectation
I expect the data that is filled in in the view textfield to be send to the ViewModel variable so that I can use it in a function to send data to firestore for example.

am I doing something wrong with maybe Binding the values from another view to the ViewModel? I cannot find anything on this topic or an error message.

Hope someone can tell me where to look or what to check next.

Greetings,


Solution

  • this is what you probably wanted to do:

    class UpdateProfileViewModel: ObservableObject {
        
        @Published var voornaam: String = ""
        @Published var achternaam: String = ""
        @Published var telefoonnummer: String = ""
        @Published var alertMessage: String = ""
        
        private var db = Firestore.firestore()
        
        func UpdateProfile(completion: @escaping () -> Void) {
    
            let userId = Auth.auth().currentUser?.uid ?? ""
            let docRef = db.collection("gebruikers").document(userId)
            
            docRef.updateData([
                "Voornaam": voornaam,
                "Achternaam": achternaam,
                "Telefoonnummer": telefoonnummer
            ]) { error in
                if let error = error {
                    print("Error updaten profiel:  \(error)")
                } else {
                    print("Profile updated")
                }
            }
    
        }
    }
    
    struct FavoritesView: View {
        @StateObject private var viewModel = UpdateProfileViewModel()
        
        var body: some View {
            VStack {
                Text("Favorieten view")
                TextField("Voornaam", text: $viewModel.voornaam)
            }
            
        }
    }