0

Really just starting out with SwiftUI and trying to get my head around MVVM.

The code below displays a height and 4 toggle buttons, thus far I have only connected 2.

Major up and Major Down.

When clicked I see in the console that the value is altered as expected.

What I don't see is the the main display updating to reflect the change.

I have tried refactoring my code to include the view model into each Struct but still not seeing the change.

I think I have covered the basics but am stumped, I'm using a single file for now but plan to move the Model and ViewModel into separate files when I have a working mockup.

Thanks for looking.

import SwiftUI

/// This is our "ViewModel"
class setHeightViewModel: ObservableObject {
    struct ImperialAndMetric {
        var feet = 16
        var inches = 3
        var meters = 4
        var CM = 95
        var isMetric = true
    }
    
    // The model should be Private?
    // ToDo: Fix the private issue.
    @Published var model = ImperialAndMetric()
        
    // Our getters for the model
    var feet: Int { return model.feet }
    
    var inches: Int { return model.inches }
    
    var meters: Int { return model.meters }
    
    var cm: Int { return model.CM }
    
    var isMetric: Bool { return model.isMetric }
    
    /// Depending upon the selected mode, move the major unit up by one.
    func majorUp() {
        if isMetric == true {
            model.meters += 1
            print("Meters is now: \(meters)")
        } else {
            model.feet += 1
            print("Feet is now: \(feet)")
        }
    }
    
    /// Depending upon the selected mode, move the major unit down by one.
    func majorDown() {
        if isMetric == true {
            model.meters -= 1
            print("Meters is now: \(meters)")
        } else {
            model.feet -= 1
            print("Feet is now: \(feet)")
        }
    }
    
    /// Toggle the state of the display mode.
    func toggleMode() {
        model.isMetric = !isMetric
    }
}

// This is our View

struct ViewSetHeight: View {
    
    // UI will watch for changes for setHeihtVM now.
    @ObservedObject var setHeightVM = setHeightViewModel()
    
    var body: some View {
        NavigationView {
            Form {
                ModeArea(viewModel: setHeightVM)
                SelectionUp(viewModel: setHeightVM)
                
                // Show the correct height format
                if self.setHeightVM.isMetric == true {
                    ValueRowMetric(viewModel: self.setHeightVM)
                } else {
                    ValueRowImperial(viewModel: self.setHeightVM)
                }
                
                SelectionDown(viewModel: setHeightVM)
                
            }.navigationTitle("Set the height")
        }
        
    }
}

    struct ModeArea: View {
        var viewModel: setHeightViewModel
        
        var body: some View {
            Section {
                if viewModel.isMetric == true {
                    SwitchImperial(viewModel: viewModel)
                } else {
                    SwitchMetric(viewModel: viewModel)
                }
            }
        }
    }
    
    struct SwitchImperial: View {
        var viewModel: setHeightViewModel
        
        var body: some View {
            HStack {
                Button(action: {
                    print("Imperial Tapped")
                }, label: {
                    Text("Imperial").onTapGesture {
                        viewModel.toggleMode()
                    }
            })
                Spacer()
                Text("\(viewModel.feet)\'-\(viewModel.inches)\"").foregroundColor(.gray)
            }
        }
    }
        
    struct SwitchMetric: View {
        
        var viewModel: setHeightViewModel
        
        var body: some View {
            HStack {
                Button(action: {
                    print("Metric Tapped")
                }, label: {
                Text("Metric").onTapGesture {
                    viewModel.toggleMode()
                   }
            })
                Spacer()
                Text("\(viewModel.meters).\(viewModel.cm) m").foregroundColor(.gray)
            }
        }
    }

    struct SelectionUp: View {
        
        var viewModel: setHeightViewModel
        
        var body: some View {
            Section {
                HStack {
                        Button(action: {
                            print("Major Up Tapped")
                            viewModel.majorUp()
                        }, label: {
                            Image(systemName: "chevron.up").padding()
                        })
                    
                        Spacer()
                    
                    Button(action: {
                        print("Minor Up Tapped")
                    }, label: {
                        Image(systemName: "chevron.up").padding()
                    })
                }
            }
        }
    }

    struct ValueRowImperial: View {
        
        var viewModel: setHeightViewModel
        
        var body: some View {
            HStack {
                Spacer()
                Text(String(viewModel.feet)).accessibility(label: Text("Feet"))
                Text("\'").foregroundColor(Color.gray).padding(.horizontal, -10.0).padding(.top, -15.0)
                Text("-").foregroundColor(Color.gray).padding(.horizontal, -10.0)
                Text(String(viewModel.inches)).accessibility(label: Text("Inches"))
                Text("\"").foregroundColor(Color.gray).padding(.horizontal, -10.0).padding(.top, -15.0)
                Spacer()
            }.font(.largeTitle).padding(.zero)
        }
    }

    struct ValueRowMetric: View {
        
        var viewModel: setHeightViewModel
        
        
        var body: some View {
            HStack {
                Spacer()
                Text(String(viewModel.meters)).accessibility(label: Text("Meter"))
                Text(".").padding(.horizontal, -5.0).padding(.top, -15.0)
                Text(String(viewModel.cm)).accessibility(label: Text("CM"))
                Text("m").padding(.horizontal, -5.0).padding(.top, -15.0).font(.body)
                Spacer()
            }.font(.largeTitle)
        }
    }

    struct SelectionDown: View {
        
        var viewModel: setHeightViewModel
        
        var body: some View {
            Section {
                HStack {
                        Button(action: {
                            print("Major Down Tapped")
                            viewModel.majorDown()
                        }, label: {
                            Image(systemName: "chevron.down").padding()
                        })
                    
                        Spacer()
                    
                    Button(action: {
                        print("Minor Down Tapped")
                    }, label: {
                        Image(systemName: "chevron.down").padding()
                    })
                }
            }
        }
    }
4
  • 1
    So much code… model is the published property so that is what you should use in your view and not the computed properties Commented Sep 3, 2021 at 19:35
  • Thanks for your reply, if I try and access model directly in valueRowMetric it’s not in scope. That’s why I pass in the view model. Commented Sep 3, 2021 at 20:16
  • 2
    @JoakimDanielson: But there is no issue for computed properties, they would just work like normal Commented Sep 3, 2021 at 20:17
  • 1
    I didn’t mean you should access it directly. I meant that you should use it when accessing the properties, so for instance viewModel.model.feet but then again as mentioned this might not be the issue. Commented Sep 4, 2021 at 7:29

1 Answer 1

1

At the moment you receive the setHeightViewModel in various views as a var, you should receive it as an ObservedObject.

I suggest you try this, in ViewSetHeight

@StateObject var setHeightVM = setHeightViewModel()  

and in all your other views where you pass this model, use:

@ObservedObject var viewModel: setHeightViewModel

such as in ModeArea, SwitchImperial, SwitchMetric, SelectionUp, etc...

Alternatively you could use @EnvironmentObject ... to pass the setHeightViewModel to all views that needs it.

Sign up to request clarification or add additional context in comments.

1 Comment

Cool that works, I guess I managed to get myself confused, I wanted to try and keep the structs as small as possible by breaking the code down into smaller chunks, but couldn't seem to get the observed object to be available that far down the view tree.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.