Learning Apple’s new SwiftUI has been a bit of a learning curve for me.
Although the advantages of SwiftUI promises to bring are obvious, the truth is I have programmed with the MCV mindset so long I was not grasping the fundamentals of MVVM which is a must to make the most of SwiftUI.
If you are having the same problems, I thought I would share my observations and maybe it will help. The solution is to think of it as a mapping the part of MCV to their corresponding parts of the MVVM. This is tricky, because most tutorials I have come across tend to gloss over this.
Here is the mapping:
Model -> Model.
Your Models in MCV can remain your models in MVVM.
View -> View’S’
The trick here is your views stay basically the same, but there are more of them.
Whether you used storyboards or created views programmatically you are basically trading those in for SwiftUI based view structs. In some aspects because Apple has always pushed Storyboards in the past thinking that a view can exist as pure code may even seem alien to a few. Largely we never saw Storyboard views as pure Swift (or old Objective-C) code. Even in their ‘native’ form storyboards file are just large XML files (more on that in a bit) which Swift (and Objective-C code) would interact via the IBOutlet and IBAction bindings. Apple reasoning has always been that interface builder took care of a lot of boiler plate code (true) that would be tedious if you have to build it entirely in native code (Swift/Objective-C). That was kind for them to do but led to a problem: Large XML Storyboard files equal large View Controller files.
We all know the joke that MCV stands for ‘Massive view controllers’, but I always thought was a little pejorative. Just because something is large does not means it is unwieldy. Most gripes about large controller files tend to come about because they are poorly organized, and developers are lazy to take the extra step to manage them, but I digress. In many ways, Controllers in MCV with Storyboards only became Massive because of the views they were dealing with.
First, most ‘controllers’ are ‘View Controllers’, and View Controllers tend to be associated with the largest encompassing elements of a UI, the page. You can get away with A LOT, using system defaults and customization just inside Interface Builder. In fact, Interface Builder, can be a little discouraging for those who do like to build views programmatically under the Storyboard paradigm. It is easier to add a function for customization to the View Controller for a contained view in your UI, then it is to create a band new Nib and associated code files and integrate that. It is because of that why controllers get that bad ‘massive’ rap. But fortunately, SwiftUI brings an alternative.
Controller -> (many) View Models.
This is the biggest change with SwiftUI. Take your controllers and blow them up, into many, many, smaller controllers. These will become your View Models.
Take your View Controller, which probably has your Text Field delegates, your Table delegates, your Table data source, your Picker Delegates, you Picker Data Source, navigation delegates etc, etc, etc and break them up into their component parts. Sure, you probably want to keep things like Table delegate and Table data sources together, but break them up. These parts will now become your View Models, and they do the exact same job as the controllers. They act an intermediator between the views and the models.
So, in truth nothing has changed. However, instead of having one central (View) Controller, we now have many smaller View Models (controllers) for each view in our UI. The C of MVC has become VM of MVVM.
Something lost, something gained — Variables
In general, this is all great, smaller parts are easier to test, but in losing our Controller in favorite of smaller View Models, we do lose one handy thing, (variable) state. But fortunately, Apple supplies us a replacement to make up for that: variable bindings.
The nice thing about the old (view) controllers is if you have to keep track of some data, just make a variable and store it. Heck, IBOutlets were basically just variables. But with view models, passing any changes to a variable back and forth to the views could get super tedious really fast. So, use bindings to get around that.
The most basic binding is probably @State, and they are found in Views. When you stick this attribute in front of a variable, you are basically telling the computer, “Hey dude this variable is going to be changing so pay attention when it does and do not get too attached to one state”.
The second most needed binding is probably @Binding, found in Views also. When you stick this attribute in front of a variable, you are basically telling the computer, “Hey dude whatever that @State variable is doing, make this variable the same.”
Using @State and @Binding is how different views can stay in sync with one another.
@ObserveredObject and @Published are responsible for binding data between views and view models. With @ObserveredObject a view can watch a class that conforms to the ObservableObject protocol and update itself when something happens. Remember only watch a view model, never a model directly.
@Published is a variable in a View Model puts out for the world and can change. Think of it as accessor like public and private, however in this case it is like public+. When subscribed to an @Published variable the listener (@ObserveredObject) will get notified of any changes made to the @Published variable. This allows the view model to manage the data without doing anything that may affect (crash) the view. The best example of this, is the view model receiving data from a URL call. One the view models get the data it formats the data to whatever the view needs and let the view know it is ready.
There are several ways to ‘publish’ data via Apple’s new Combine framework and ObservableObject. So, you don’t always have to use @Published, but if it is something in your view model that needs to change or be updated in your UI, it has to be watched via @ObserveredObject.
@EnviromentObject is the attribute I can see is going to be abused the most, and why I put it last. In many ways @EnviromentObject does everything @State and @Binding does all in one. It seems simpler to use to why not just use it? The trick is @EnviromentObject(s) should only be used for stuff that is global to your UI views, like on the application level. Maybe something like your UI’s theme color. This is why it is perfect for use in the PreviewProvider in previewing SwiftUI in XCode, because it can act as a catch all grab bag for all of our data. But we should try to avoid this, because it will break the principle of low coupling / high cohesion in good software development.
In any case sometimes a picture is worth a thousand words, so if you are used to seeing the diagram of the old MVC style looking like this:
Your can now imagine the new MVVM as this:
I will admit I am being no means an expert on SwiftUI, yet. However, once I made these observations between the differences of MVC and MVVM my comprehension became a little better. So, I thought it would be good to share if it might help others. For the experts who know more than me, if I got something wrong feel free to drop a line and I will update everything, in the meantime I hope this can be some help to those which are still confused about MVVM.