I'm new to SwiftUI architecture especially when using MVVM. Currently I've got the following situation that I try to solve using MVVM. I struggle to understand the boundaries of a view model and how to solve the data handling part for it.
Let's give an example: the properties of the following domain model should be changed throughout a sequence of different NavigationViews, think of it as a wizard.
The struct representing this domain model looks like this:
class BusModel: Identifiable {
let id: UUID
var name: String
var drivers: [DriverModel]
}
class DriverModel: Identifiable {
let id: UUID
var firstName: String
var lastName: String
var toursToDrive: [TourToDriveModel]
}
class TourToDriveModel: Identifiable {
let id: UUID
var dayOfTheWeek: Int
var tour: TourModel
}
class TourModel: Identifiable {
let id: UUID
let label: String
}
As you see, the domain model has some sort of hierarchy: BusModel contains a list of DriverModel contains a list of ToursToDriveModel contains a TourModel
The purpose of the first NavigationView is to make changing the BusModel.name field possible. After clicking the next button, the list of drivers getting displayed, represented using NavigationLinks. There it should be possible to add a new driver or update the existing ones. So clicking on a list item navigates the user to the next view, presenting the data of those DriverModel. There it should be possible to change the drivers firstName and lastName and should show it's toursToDrive presented in a list as well. Changing a tourToDrive follows the same navigation principle as described for the driver.
So my question is: after all every view changes some properties of the underlying BusModel object and I don't know what a view model in this context would look like. Should the view model be related to all those single views or should every view has it's own dedicated view model?
Currently my view model spans the whole set of views, where every view changes to related properties of the view model. But I don't think that's correct because the view model holds every logic of every single view and if the relationship would be 1:1 of view and view model, those logics would be scoped to the distinct view they belong to.
So I guess, putting the domain model into an environment object which can be used within every distinct view model would be a better choice?
Another problem I have right now is, that the BusModel fields are exposed as #Published properties, so it looks something like this:
class AlterBusViewModel: ObservableObject {
#Published var name: String
#Published var drivers: [DriverModel]
}
But if DriverModel.firstname gets updated, the view model doesn't propagate the changes to ancestor views as well, because it's some sort of nested property I guess? Even if the view model would be divided into view related view models, this problem persists from my point of view. How could this be solved as well?
Finally the domain model and data handling gets replaced by core data later on, but for now I would like to solve it by using a class representing the domain model at runtime which should be updated subsequently.
If there are any questions, feel free to ask, I will provide more info as soon as possible!
Many thanks in advice.
Related
Context
I am working on a feature, that allows users to add Components to CoreData. Those Components are obviously NSManagedObjects inserted and saved into a Context.
In addition, I also want to give the user a variety of predefined Components. However, I do not like the idea of populating those predefined ones into CoreData at the first App Launch, since this is really prone to bugs, especially when utilising CloudKit. So my idea was to generate a List of predefined NSManagedObjects without inserting them into a Context, which would make them temporarily, but they could be used in the same way as the real ones. However, as far as I understand, creating NSManagedObjects without a Context isn't really working.
Code
let predefinedComponents: [Component] {
var components: [Component] = []
for name in names {
let component = Component() // This was my idea of creating a temporary NSManagedObject without inserting it into a Context.
component.name = name
components.append(component)
}
return components
}
struct ComponentsView: View {
#FetchRequest(sortDescriptors: [SortDescriptor(\.name)]) private var components: FetchedResults<Component>
var body: some View {
ForEach(allComponents) { component in
ComponentRow(component: component)
}
}
private var allComponents: [Component] {
var allComponents: [Component] = predefinedComponents
for component in components {
allComponents.append(component)
}
return allComponents
}
}
struct ComponentRow: View {
#ObservedObject var component: Component
var body: some View {
Text(component.name)
}
}
Question
How can I achieve my goal described above while being able to work with predefined Components without having to populate them into CoreData at the first AppLaunch?
The easiest and most elegant way is to create a "throwaway scratchpad" context just for the pre-defined Components.
This scratchpad context will be a child of the viewContext, or any background context depending on your use case.
This is how you create the scratchpad:
let scratchpadContext = NSManagedObjectContext(.mainQueue)
scratchpadContext.parent = dataProvider.container.viewContext
The example above creates a context for the main queue, which I assume is your case based on your question. But if you need to access it from a background thread, you initialise it with .privateQueue.
So, as long as you don't save the scratchpadContext, your temporary pre-defined Components will never be saved on your Persistent store. And when it's de-initialised, any NSManagedObject that you've created with it will be thrown away.
You can create "free floating" managed objects that don't belong to a context but you need to provide the entity description to do it-- so you would use Component(entity:insertInto:). The first argument is the NSEntityDescription for Component. The second one is a context, but it's an optional, so you can make it nil. If you wanted to add it to a context later, use NSManagedObjectContext.insert().
It might be better to use an in-memory persistent store instead of a SQLite store. Then you would have a context that only existed while the app was running but did not save to a file. You can set one of those up with NSPersistentContainer if you change the persistent store description.
I can't tell from your question which of these would be better for you.
Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 6 months ago.
Improve this question
I have a rather larger screen that I am building with SwiftUI and Combine. I am using MVVM with an ObservableObject object acting as the VM. The main view itself is composed of smaller child views, each of which have their own "dummy" view models (that are currently not ObservableObject but rather simply structs) that take in the model and format the data for the component. I have a few questions and I would be curious to hear the community's thoughts:
1. Where should the child view models be stored?
Currently, I expose the child view models as #Published within my main view model (let's call it CheckoutViewModel). I then have a Combine publisher inside CheckoutViewModel that does a few things and updates the VMs:
class CheckoutViewModel: ObservableObject, Identifiable {
// MARK: - Publishers
#Published var detailsViewModel: DetailsViewModel?
#Published var optionsListViewModel: OptionsListViewModel?
// etc...
// MARK: - Private Members
private var subscriptions = Set<AnyCancellable>()
private let repository: CheckoutRepository // DI'd
// MARK: - Actions
func load(productId: String) {
repository.fetchData(forProductId: productId)
.sink(
receiveCompletion: { _ in },
receiveValue: { product in
self.detailsViewModel = DetailsViewModel(fromProduct: product)
self.optionsListViewModel = OptionsListViewModel(fromProduct: product)
}
)
.store(in: &subscriptions)
}
}
Within my CheckoutView, I then call load and have the child VMs passed to the child views:
struct CheckoutView: View {
#ObservedObject var viewModel: CheckoutViewModel
var body: some View {
VStack {
if let detailsViewModel = viewModel.detailsViewModel {
ProductDetails(viewModel: detailsViewModel)
}
if let optionsListViewModel = viewModel.optionsListViewModel {
OptionsList(viewModel: optionsListViewModel)
}
// ... and so on
}
}
}
I am not sure if I love this approach. Something feels odd about publishing these child view models like this.
Should each of these child view models be observable objects on their own that then communicate to the parent view model?
As you can see, once you bring in user interactions (i.e. a user can select an option in the OptionsList), my current approach may fall somewhat short or become unmanageable the larger the view gets.
This leads me to my second question.
2. Who should own the state? How should user interactions be facilitated?
As you can imagine, state in this case becomes very important, and with something like a checkout screen, there may be a lot of state to track. This is where I am totally lost currently. With this approach, I know I can store state directly in CheckoutViewModel, but it may just end up becoming too large and cumbersome to work with.
To deal with user interactions, I've tried passing down bindings directly to the child views and go from there, but that circumvents the child view models and "clutters" CheckoutViewModel with a bunch of state variables (that seems like they should be part of a model, not the view model).
Another approach was using closures in the child views, e.g. if the user selects a new option from OptionsList, call a closure with the option ID and then call the main view model from the view to update the state - but that seemed super cumbersome and I kept thinking there must be a better way to do this.
Maybe some sort of store or similar would be a good idea? I am trying to get an idea of best practices for how child views/child VMs should communicate with their parent.
Conclusion
As you can probably tell, I am a bit lost about proper state management and how to deal with child views using MVVM in SwiftUI. Any guidance or general ideas/suggestions would be greatly appreciated. Is using MVVM in SwiftUI maybe a bad idea in general? As anyone else done something similar, or am I on the completely wrong path here?
I was watching a lecture about MVVM in Swift and how it works, and I basically understood that the Model will contain data and logic, ViewModel passes that data to the View, maybe cleans it up a bit and the View can also call intent functions in the ViewModel to notify the Model of some things that need to be modified.
Now I know I don't really have much context but there are a bunch of lectures here and there's no way for me to really explain everything for now but basically we're making a memory card game and now we are now changing it to have a MVVM design pattern(It didn't have one before). The model currently contains a Card struct and a choose function to choose a card and stuff like that, but for some reason the lecturer puts an array of emojis(The content of the cards in this game) in the ViewModel and not the Model.
I thought that the Model should be the one that stores the data and not the ViewModel? Could anyone maybe try to explain why this was done?
ViewModel:
import SwiftUI
class EmojiMemoryGame //this is the ViewModel
{
static let emojis = ["floaf","taco","george","chicken","squeaky","cat","dollar","a","b","c","d","e","f","g","h"] // these are supposed to be the emojis I just used some words instead.
static func createMemoryGame() -> MemoryGame<String>
{
MemoryGame<String>(numbersOfPairsOfCards: 4) { pairIndex in emojis[pairIndex]}
}
private var model : MemoryGame<String> = createMemoryGame()
var cards: Array<MemoryGame<String>.Card>
{
model.cards
}
}
Model:
import Foundation
struct MemoryGame<CardContent> //MemoryGame is the model for the MVVM pattern
{
private(set) var cards : Array<Card>
func choose(_ card: Card)
{
}
init(numbersOfPairsOfCards: Int, createCardContent: (Int) -> CardContent)
{
cards = Array<Card>()
//add numbersOfPairsOfCards*2 to cards array.
for pairIndex in 0..<numbersOfPairsOfCards
{
let content = createCardContent(pairIndex)
cards.append(Card(content: content))
cards.append(Card(content: content))
}
}
struct Card
{
var isFaceUp: Bool = false
var isMatched: Bool = false
var content: CardContent
}
}
It's entirely subjective, MVVM is just a design pattern and does not need to be rigorously adhered to.
Note that in MVVM a given view is typically "backed" by a single ViewModel and that a VM might interact with many different models. If the data is to be shared across views, it probably belongs on some shared model. If the data just used on a single view, it's likely fine on the VM.
I knew and understood which lecture you are referring too.
Model is for storing the data, but VM is the middleman who can uses the Model to compute data or update the View without giving the View a direct access to the Model. ViewModel also updates the Model whenever it receives any specific interaction from the View or User. You do not process or compute any data inside a Model to update the View. You must use the ViewModel.
Also, in this case the "emojis" array is not really the main model. It is just an array of String not "Card". emojis is used to store the data for initializing the "Model". Think of it like an input from the user, you need this to pass to your Model, otherwise you don't need a Model if you don't have any data to store or initialize.
The main model is still "Card", and "Card" is still completely abstract in this context.
ViewModel's job is to compute the data like a middleman and then either passing it to the Model or Update the View. Also, it is fully possible for a ViewModel to have its own implemented variables for helping the process between VM and Model or VM and View.
All in all, Model does not do any process, compute, direct interact with the View. Model only stores the data. ViewModel does all the compute, process, initialize.
ViewModel is a common incorrect interpretation of the underlying concept, stemming from the lack of spaces that we have in programming languages obfuscating intent. What you’re actually working with is a View Model, expressible in Swift via View.Model or Model.View…
struct CardView: View {
struct Model: DynamicProperty {
extension MemoryGame.Card {
struct View: SwiftUI.View
…or, when it’s a model for several views instead of one, that’s called a Store:
final class Store: ObservableObject {
Even though MVVM is a cross-platform term that Apple has largely rejected, every view has a model, regardless of if you abstract it into a separate type. The view model is anything the view needs to draw itself. Anything outside of that is a model for something else and doesn’t belong in the type.
I have a concept question about MVVM. I'm learning this pattern in swift 3.
This is my situation:
I have a screen with a list of users. The UserViewModel has a property that is a list of users, but the controller shouldn't know about the User model. I read that controller shouldn't know absolutely nothing about the models.
If the controller requests data from a user of this list, this data must be a dictionary of data or can be a User model?
Then, the UserViewModel must contain also the same properties than the User model?
Thank you for your help.
If the controller requests data from a user of this list, this data must be a dictionary of data or can be a User model?
Data can be the UserModel.
Then, the UserViewModel must contain also the same properties than the User model?
Hhm. Of course not. Why UserViewModel should have same properties that UserModel have? There is no any reason for that.
For purpose when you need to return some userModel.name property to controller, you can and should get this only from property of UserModel, i.e. get the right UserModel from ViewModel and than get the property:
func controllerFunc() {
let userModel = viewModel.getUserModelFromList()
print(userModel.name)
}
But in case when you need to prepare value for controller, you can create property in ViewModel, that contains some sort of modification for UserModel's property. And controller should get property from ViewModel, not from UserModel.
I read that controller shouldn't know absolutely nothing about the models.
Well, ideally - yes. Also, should be mentioned that ViewModel shouldn't know anything about UI components, i.e. ViewModel shouldn't have import UIKIt statement.
For example, if you need an UIImage, you should return name (String type) of image from ViewModel and create UIImage object in controller.
Being able to share data between multiple view controllers and doing that in a way that makes use of recommended patterns such as MVC seems to be essential to create good apps, but my problem is that these things aren't clear at all for me.
I am conscient that this question is really dense, but for things to be clear I think you really need to understand the whole thing.
First of all we need to be sure of what Model, View and Controller are doing, here is how I would describe them, please tell me if I'm right about that:
Model : a class that's responsible for managing data, and only that (for example, a class that will go on the web to retrieve information, such as weather forecast).
View : a view is an object that's displayed to the user, who can often interact with it, that's the objects that you can drag and drop in Interface Builder (for example a button) and you might also create one from scratch, or custom an already existing one by subclassing it.
Controller : a controller is responsible for managing a view and its subviews, it receives events (such as viewDidLoad, or even when the user taps a button) and can react to it, for example, it might change the text of a label.
Now about the way they are interacting between each other, I'd say that the controller is between the view and the model, it's managing the view and might ask for data to the model. In addition to receiving events from the view, it might also receive events from the model, for example, if the controller asks to the model for a specific data on the web (let's say if it asks weather for a specific city) the data won't be available immediately, instead, the model will notify the controller so that it can update the view with the data it received. Am I right?
One of the first thing I'm wondering is if an object could be considered as a model if it isn't here to retrieve data, but to do other things that are simply not related to the view, for example, could an object that's responsible for communicating and managing a bluetooth accessory considered as a model ? Could an object that sends data to a cloud considered as a model ? And what about a Tic Tac Toe AI ?
Then, singleton instances, I often heard of them when an app had to share data between multiple views, but first of all, I never really understood why it was necessary to use them in this case ?
Then, here is a singleton that I found in an article of the We Heart Swift website.
class Singleton {
struct Static {
static let instance = Singleton()
}
class var sharedInstance: Singleton {
return Static.instance
}
}
Singleton.sharedInstance
The problem if that I have had difficulties to find anywhere more details about why it's written in this way, and most of all, can a singleton have an initializer that takes arguments? How to add properties and methods to a singleton like this one? What are exactly the Static structure and the sharedInstance?
My last question is about why, technically, does a singleton makes it possible to get an access to things we have defined somewhere else? What I mean is that if I create an instance of let's say, a Dog class in my AppDelegate, and if I want to access to this specific instance in a view controller, then it wouldn't be possible, so how does singleton makes that possible under the hood?
EDIT : Oh, and, is the use of singletons recommended by Apple?
Thank you.
It has to do with the static in the struct. Static is essentially a class variable that persists for every instance of that class, so when you make the shared instance static, every time you access it, even from another instance of Singleton.instance it is the same variable because it is static. It persists amongst instances. However, Swift does not support class variables yet, so when it does, that should quickly replace the Struct syntax that is common of singletons. It is very similar to static variables in java.
For example:
class Singleton {
var someVar = 0
struct Static {
static let instance = Singleton()
}
}
to create a singleton with a variable and the following to access it:
let foo = Singleton.Static.instance
foo.someVar = 11
let bar = Singleton.Static.instance
println(bar.someVar) // Prints 11
As you can see, bar.someVar was never set, and that is because the variable for the shared instance was set, so it prints 11.