HowTo change an ObservableObject? (SwiftUI newbie)

Hi!
I struggle around with a SwiftUI problem. I'd like to start an application using SwiftUI but I cannot figure out how to get changes back from a dialog action.
I made a small example:

//  ContentView.swift

import SwiftUI

final class ModelData: ObservableObject {
    @Published var temp   : Double   = 20.0
    @Published var index  : Int      = 0
    @Published var values : [String] = ["ºC", "ºF", "K"]
}

struct myView: View {
    @State private var tempStr : String

    init() {
        self.tempStr = "\(ModelData().temp)"
    }
    
    var body: some View {
        VStack {
            HStack {
                Text("Temperature:")
                TextEditor(text: $tempStr)
                    .frame(width: 80, height: 20, alignment: .leading)
                Text(ModelData().values[ModelData().index])
            }
            HStack {
                Button("ºC", action: {
                    ModelData().index = 0
                    print("ºC button pressed: \(ModelData().index)")
                })
                Button("ºF", action: {
                    ModelData().index = 1
                    print("ºF button pressed: \(ModelData().index)")
                })
                Button("K", action: {
                    ModelData().index = 2
                    print("K button pressed: \(ModelData().index)")
                })
            }
        }
        .font(.system(size: 15, weight: .regular, design: .default))
        .frame(width: 250, height: 100, alignment: .center)
    }
}

struct ContentView: View {
    var body: some View {
            myView()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Running the application does show the "button pressed" message but the ModelData is unchanged. Also I want to get a changed text from TextEditor back to modify the ModelData temp.
I found much information on the net but no simple example for the basics.

Thanks in advance for your help.

You are creating a new instance of ModelData every time you write ModelData(), which then gets instantly de-referenced at the end of the containing code block. What you want is one instance of ModelData and pass that around as an @ObservedObject.

Have a look at this: How to use @ObservedObject to manage state from external objects - a free SwiftUI by Example tutorial

To learn more about how classes work as instances, have a look at "Classes are Reference Types" here: Structures and Classes — The Swift Programming Language (Swift 5.7)

1 Like

Thanks for your help. Now it works as expected. I changed two lines:

final class CModelData: ObservableObject {
…
}
let ModelData = CModelData()

Can you give me also a pointer to my other problem?

Also I want to get a changed text from TextEditor back to modify the ModelData temp .
I have no clue about any text changed event from a TextEditor to handle an input.

Check out how Swift classes, variables, etc., are usually named. For your code, you want to write:

final class ModelData: ObservableObject {
    ...
}
let modelData = ModelData()

For your second question, have a look at how onChange is used here: Introducing SwiftUI TextEditor for Multiline Text Input

Good luck with your project!

1 Like

Great. I did no see any other reference to ".onChange(of: inputText)" before.
Now I can start coding.

Many thanks for your help.

To help other newbies I'd like to publish my little sample. Now I can enter text and change the unit. Both will lead to a recalculation and a view update.

import SwiftUI

final class ModelData: ObservableObject {
    @Published var tempStr : String   = "20.0" {
        willSet { self.objectWillChange.send() }
    }
    @Published var index   : Int      = 0 {
        willSet { self.objectWillChange.send() }
    }
    @Published var values  : [String] = ["ºC", "K"]
}

struct myView: View {
    @ObservedObject var modelData : ModelData
    
    init() {
        modelData = ModelData()
    }
    
    var body: some View {
        VStack {
            HStack {
                Text("Temperature:")
                TextEditor(text: $modelData.tempStr)
                    .frame(width: 80, height: 20, alignment: .leading)
                    .onChange(of: modelData.tempStr, perform: { tempStr in
                        print("onChange called!")
                    })
                Text(modelData.values[modelData.index])
            }
            HStack {
                Button(modelData.values[0], action: {
                    modelData.index = 0
                })
                Button(modelData.values[1], action: {
                    modelData.index = 1
                })
            }
            if modelData.index == 1 {
                Text("\(String(format: "%.2f", Double(modelData.tempStr)!-273.15)) \(modelData.values[0])")
            } else {
                Text("\(String(format: "%.2f", Double(modelData.tempStr)!+273.15)) \(modelData.values[1])")
            }
        }
        .font(.system(size: 15, weight: .regular, design: .default))
        .frame(width: 250, height: 100, alignment: .center)
    }
}

struct ContentView: View {
    var body: some View {
            myView()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

You don't need to call objectWillChange explicitly here, that's what @Published does for you.

2 Likes

Thanks for clarification. You're right. It works without
willSet { self.objectWillChange.send() }