Home > Software design >  SwiftUI: Why onReceive run duplicate when binding a field inside ObservableObject?
SwiftUI: Why onReceive run duplicate when binding a field inside ObservableObject?

Time:03-01

This is my code and "print("run to onReceive (text)")" run twice when text change (like a image). Why? and thank you!

import SwiftUI

class ContentViewViewModel : ObservableObject {
    @Published var text = ""
}

struct ContentView: View {
    @StateObject private var viewModel = ContentViewViewModel()
    
    var body: some View {
        ZStack {
            TextField("pla", text: $viewModel.text)
                .padding()
        }
        .onReceive(viewModel.$text) { text in
            print("run to onReceive \(text)")
        }
    }
}

enter image description here

CodePudding user response:

Because you have a @Published variable inside the @StateObject of your view, the changes in that variable will automatically update the view.

If you add the .onReceive() method, you will:

  • update the view because you have the @Published var
  • update it again when the .onReceive() method listens to the change

Just delete the .onReceive() completely and it will work:

class ContentViewViewModel : ObservableObject {
    @Published var text = ""
}

struct ContentView: View {
    @StateObject private var viewModel = ContentViewViewModel()
    
    var body: some View {
        ZStack {
            TextField("pla", text: $viewModel.text)
                .padding()
        }

        // It still works without this modifier
        //.onReceive(viewModel.$text) { text in
        //    print("run to onReceive \(text)")
        //}
    }
}

CodePudding user response:

I think it's because the view is automatically updated as your @Published property in your ViewModel changes and the .onReceive modifier updates the view yet again due to the 2 way binding created by viewModel.$text resulting in the view being updated twice each time.

If you want to print the text as it changes you can use the .onChange modifier instead.

class ContentViewViewModel: ObservableObject {
    @Published var text = ""
}

struct ContentView: View {
    @StateObject private var viewModel = ContentViewViewModel()

    var body: some View {
        ZStack {
            TextField("pla", text: $viewModel.text)
                .padding()
        }.onChange(of: viewModel.text) { newValue in
            print("run to onChange \(newValue)")
        }
    }
}

onChanged in SwiftUI

  • Related