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,
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)
}
}
}