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.
indiedotkim
(Joachim "Kim" Baran)
2
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.
indiedotkim
(Joachim "Kim" Baran)
4
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()
}
}
bzamayo
(Benjamin Mayo)
7
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() }