I am struggling recently with my app and I think it is because I am missing something in the conception. I am fairly new to the OOP and probably it is my fault but I can't say where it is.
The thing is that I have different views in my app which display properties from an @Observable class. For some reason in the different view I see different values displayed while all of them have to be the same since it comes from the same property of the observable class. I am using shared instance, which I know is not correct but I have tried with @Environment and directly to inject the class instance but it is all the same. Clearly it is my fault, and I would highly appreciate where I am wrong.
Here is the case - I will describe it simply for the sake of the principle. I have a class which handles UDP socket connection. In that class I receive a string which is going to be processed further more. String is being received every second.
class UDPHandler {
//use shared instance - I know it is not correct methods
var fromNMEA = NMEAReader.share
func udpSocket(_ sock: GCDAsyncUdpSocket, didReceive data: Data, fromAddress address: Data, withFilterContext filterContext: Any?) {
if let receivedString = String(data: data, encoding: .utf8) {
//here I receive the string, this function is being triggered and the string is pass to a function in another class which will handle further actions with the string
fromNMEA.processRawString(rawData: receivedString)
}
}
Then I have the @Observable class which stores all the properties to be displayed and the function that processes the string that comes from the UDPHandler
@Observable class NMEAReader {
//create shared instance
static let shared = NMEAReader()
var boatSpeedLag = Double() //value to be displayed
//there are a lot more values but it doesn't work even with one
func processRawString(rawData: String){
//here I make different manipulations over the string, take the values, convert them to Doubles, calculate different things and update all the properties declared
}
//even more support functions here ... and so on.
}
Then it comes the view part. I have couple of views which I call in the ContentView
struct ContentView {
//shared instance so I can get to the observable class
var nmea = NMEAReader.shared
var body: some View {
//portrait view, where I pass the shared instance
PortraitView(nmea: nmea).opacity((horizontalSizeClass == .compact && verticalSizeClass == .regular) ? 1 : 0)
}
}
struct PortraitView {
VStack{
//this is a separate settings menu from where I start the socket communication
NavigationLink(destination: SettingsMenu()) {
Image(systemName: "gear")
.dynamicTypeSize(.xxxLarge)
.foregroundStyle(Color(UIColor.systemGray))
}
//all views are of the same type, with a single Text line where I just display the value. They are so many, so I can compare and see what is going on, in reality views are different. I pass to them the shared instance of the NMEAReader class so they can update when it changes
HStack{
SmallDisplayCell(nmea: nmea)
SmallDisplayCell(nmea: nmea)
}
HStack{
SmallDisplayCell(nmea: nmea)
SmallDisplayCell(nmea: nmea)
SmallDisplayCell(nmea: nmea)
}
HStack{
SmallDisplayCell(nmea: nmea)
SmallDisplayCell(nmea: nmea)
SmallDisplayCell(nmea: nmea)
}
}
}
Then I have the SettingsMenu() where I just start / stop the communication with the outside world.
struct SettingsMenu: View {
//initialize an instance of the UDPHandler, so I can start/stop the communication
private var connector = UDPHandler()
var body: some View {
VStack(){
Button("Start") {
connector.start()
}
Button("Stop") {
connector.stop()
}
}
}
}
SmallDisplayView is made very simple for the test, however it is not so complicated in the real app too.
struct SmallDisplayCell: View {
var nmea: NMEAReader
var value = Double()
var body: some View {
Text(String(nmea.boatSpeedLag))
.frame(width: width, height: width, alignment: .center)
}//END OF BODY
}//END OF STRUCTURE
And that is it. Yet I can't get all the views of the display cell to be updated simultaneously. Please see the picture, so you can get idea of what I am talking about.
Any ideas and advices on the OOP and how can I implement that structure would be much appreciated, thanks.
So, after I read the article and implemented the suggestions from workingdog support Ukraine I ended up with the following configuration:
After all of the above it works like a charm, exactly as expected. Not very surprised, though, suggestions and advices from workingdog support Ukraine are always correct and educational, thanks again.
This is the result that I wanted to get: