18

I am trying to put the data that I am passing into the view into the ViewModel.

I see lots of tutorial about retrieving data from outside sources like Json or Firebase but nothing about using my data passed from the previous page.

I have to do a lot of calculations on the data to get my final output.

here I need to get 'entry' into the ShowViewModel.

struct ShowView: View {
  
    @ObservedObject var entry:EntryData
 
    var showVM = ShowViewModel()
 
    var body: some View {
   
       Text("\(entrybeg)")
    
    }
}

I am just playing with a blank ViewModel here for testing.

class ShowViewModel: ObservableObject {

    @Published var test = "something"
    @Published var m:Month = Month()

}
1
  • it is all ambiguous. hypothetical. "EntryData" would be the information coming from previous page in the form of a struct/class. I need to insert that data into the ViewModel so I can work on it. Commented Aug 28, 2020 at 7:42

3 Answers 3

16

You may try the following:

struct ShowView: View {
    @ObservedObject var entry: EntryData

    @ObservedObject var showVM: ShowViewModel // declare only (@ObservedObject optionally)
    
    init(entry: EntryData) {
        self.entry = entry
        self.showVM = ShowViewModel(entry: entry)
    }

    var body: some View {
        Text("\(entrybeg)")
    }
}

class ShowViewModel: ObservableObject {
    @Published var test = "something"
    @Published var m: Month = Month()
    
    private var entry: EntryData
    
    init(entry: EntryData) {
        self.entry = entry
    }
}

Now when you create your view you need to pass EntryData to it:

ShowView(entry: EntryData())
Sign up to request clarification or add additional context in comments.

12 Comments

your are totally amazing - you have no idea how many hours of YouTube videos I have watch today!!!
should the EntryData be a struct or a class? or no difference?
@diogenes It obviously depends on what are you trying to do. Struct are value types, classes are reference types. If you don't know what to choose, start with a struct (especially if it's just for storing the data).
I think the ShowView(entry: EntryData()) is blowing up the data I am passing in using: @StateObject var entry:EntryData = EntryData()
You must not init objects in View init. With SwiftUI when there is a change a View struct is init, the actual view is rendered and the View struct is destroyed. Thus any objects you init in a View struct are lost instantly. You must use property wrappers that store them somewhere else that keeps the objects alive, e.g. @StateObject var showVM = ShowViewModel() this actually inits the object once and gives it to the View struct every time it is recreated by a parent View's body. If the View is not recreated, e.g. it was inside an if that became false, then the StateObject is deinit.
|
5

SwiftUI doesn't use the View Model pattern, you have to learn something new. SwiftUI uses lightweight data structs (badly named as Views) that are created super fast, tell the system how to create the actual views shown on screen and then are thrown away immediately and if you follow Apple's tutorials they state that the "Views are very cheap, we encourage you to make them your primary encapsulation mechanism" (20:50 Data Essentials) . You can also group related properties into their own custom struct so that a change to any property is detected as a change to the struct itself, this is called value semantics which is a feature of structs you cannot get with objects like view model objects. Define a struct var with @State in parent View and reference it in child view using @Binding passing it in using the $ syntax. These property wrappers allow the struct to behave like an object reference. SwiftUI does dependency tracking and if any @State or @Binding that is referenced by the body method, body will be called whenever these properties change.

struct ShowViewConfig {
    var test = "something"
    var m:Month = Month()

    // you can even put a method in to update it
    mutating func fetch(name:String, ascending: Bool){
    }
}

Then create it in your ContentView like:

struct ContentView {
    @State var config = ShowViewConfig()
     var body: some View {
          ...
          ShowView(config:$config)

Then use it in your ShowView like:

struct ShowView: View {
    @Binding var config : ShowViewConfig
    var body: some View {
       Text("\(config.test)")
    }
}

You can see this pattern at 8:44 in Data Essentials in SwiftUI WWDC 2020

enter image description here

If you have a model object, i.e. that is being monitored by @ObservableObject then your job is to convert from the rich data types to simple data types in a View struct somewhere in the hierarchy, using formatters if necessary. You can learn more about this in WWDC 2020 Structure your app for SwiftUI previews @ 11:22. So as you can see if you tried to do this in a View Model object instead of the View structs then that is the wrong approach.

enter image description here

It's also fine to use @ObservableObject for a loader or fetcher (20:14 Data Essentials).

enter image description here

3 Comments

As I commented before, it's helpful that you found this question and added an updated answer. But please do not downvote all the answers to promote yours - other answers were completely valid at the time of answer (and are still valid - SwiftUI 2 is still in beta). Your comments to the other answers could have been included at the top of your answer as well if you wanted to make a point.
SwiftUI works by creating structs all the time, that is perfectly normal. Structs are value types and super fast.
I never said always. I said @ObservedObject should never init an object, which is what Apple say, who designed SwiftUI, so I think they should know :-)
3

I'm assuming here, that you have a class which has done a calculation. Furthermore, that calculation its result is required for the ShowViewModel. I'd do something as follows:

class EntryData: ObservableObject {
    @Published var calculation = 42
}

struct ShowView: View {
    @ObservedObject var showVM: ShowViewModel
    
    var body: some View {
        Text("\(showVM.m.daysRequired)")
    }
    
    init(entryData: EntryData) {
        let month = ShowViewModel.Month(daysRequired: entryData.calculation)
        self.showVM = ShowViewModel(month: month)
    }
}

class ShowViewModel: ObservableObject {
    struct Month {
        let daysRequired: Int
    }

    @Published var test = "something"
    @Published var m: Month
    
    init(month: Month) {
        self.m = month
    }
}

struct ContentView: View {
    var body: some View {
        ShowView(entryData: EntryData())
    }
}

6 Comments

in the ShowView you put the "var body" above the init. Is this ordering required? thanks for your help.
No, the sequence doesn't matter at all.
Since var body is a variable, most people put variables higher than functions. I took a look at what Apple does, but their Landmarks tutorial actually doesn't feature a single init anywhere in the views!
cool, thanks. having issues that I cant figure out.
Stack Overflow is pretty great for specific questions but Reddit has a subreddit that's useful for more free flow questions reddit.com/r/iOSProgramming
|

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.