I've also noticed this, and until recently, I thought they were just trying to keep the examples simple, but now I think it's part of their design.
Before SwiftUI, I was using MVC, but as a purely UI architecture, so the M was a view model, and all of this was in the UI layer. The business logic would be separate in the "Business layer".
If you tap a button in the View, it would call some function in the Controller, which would call some function in the view Model. If that requires some business logic to be run, then the view Model would call something in the Business layer through some sort of interface.
If the view Controllers were done correctly, they would simply be a connection between the view and the model without any real logic (and this is why they’ve really become unnecessary with reactive frameworks, like SwiftUI). Now with Swift UI, there is no longer a need for the ‘C; we basically just need just a view and a view model. But I still keep the business logic separate in the business layer. If you tap a button in the View, it would call some function in the view Model directly (without a controller). If that requires some business logic, then again, the view model would call some function in the business layer via some sort of interface.
Now it might seem that these view models are kind of like the view controllers from before in that are mostly just an intermediary, just a bunch of connectors, really. Are view models redundant and could we get rid of them? Well, I still see a need for some place to have view logic that is not business logic. If tapping that button just displays another set of UI controls, for example, then this is not business logic at all. It shouldn’t be in the business models in the business layer. But exactly which controls to show and how to display them might involve some logic about colours and display text, perhaps translated into another language, positioned according to the user’s layout preferences, etc. This could involve a bit of code to apply some view logic. In that case, I think a view model makes sense. We can keep all of these functions and algorithms in a model that is easy to test and maintain.
While I still see the need to separate view logic from business logic, I am starting to think that most of this view logic can just go right in the views with simple @State vars in most cases. If complex views are broken down into separate subviews that are small enough, then each of these views can be very simple in what it displays and how it interacts, so it can have view logic that is simple enough to not require a whole view model class. If the view logic is in little functions in the view, then it can be unit tested directly. Or it could just be covered indirectly through UI tests.
Now as SwiftData emerges, I am wondering the same thing: How do I use this elegant framework that greatly simplifies database/persistence operations, but still keep it separate from my business logic? We do not want to make our business logic dependent on SwiftData, just as we wouldn’t want it to depend on SwiftUI or any other UI framework. The business layer should depend on Foundation only. (Imagine if we want to share this business logic with applications on other platforms that might not have SwiftUI or SwiftData.)
Suppose one of our main business objects is “Book”. We cannot have the Book class/struct decorated with @Model to make it persist with SwiftData because that would introduce a dependency in our business layer on the data layer. So Book has to be pure. But in order to take advantage of SwiftData, we need a model to persist. So perhaps we make a Data Transfer Object class “BookDTO”, which is decorated with @Model. This could also be an @ObservedObject in the BookView, so it could just as well be called “BookViewModel”. So I guess we are back to having view models. You could call it something else so it doesn’t sound like a view model, like “BookObject”, “PresentedBook”, or “PersistedBook”, but if the view is observing it and reacting to it, it kind of is a view model.
Whatever it is, it has to be a different object from the pure “Book” in the business layer. I was hoping we could apply the @Model decoration in an extension somehow. That way we could use the same class/struct for the business logic and the persisted object, but without making the business layer dependent on SwiftData (by putting the extension somewhere else). But I don’t think that is possible.
It certainly still makes sense to break views down into small subviews. In many cases, these small views might just need a couple @State vars and no view model. Sometimes they might just need a few let constants. In other cases, they might need to observe an object (or two), which we could think of as a view model or data-transfer object. When it is time to talk to the business layer, we would have to convert any of these view models/DTOs into the pure business objects since those are the only types it knows. Maybe there is a simpler way.