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

1

6

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,

Conroy answered 21/7, 2021 at 11:43 Comment(2)
you have in your "FavoritesView" a "LoginViewModel", but you are showing the code of some "UpdateProfileViewModel" which has nothing to do with your FavoritesView code. Show us the "LoginViewModel" code.Horatio
You aren’t sharing the correct viewmodel in your sample. But something to note is that your variables should be marked @PublishedFraudulent
H
6

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)
        }
        
    }
}
Horatio answered 21/7, 2021 at 12:11 Comment(1)
Yes, that was the problem. I didn't think of that because there was data actually being passed to the variables.. Thanks a lot!Conroy

© 2022 - 2024 — McMap. All rights reserved.