Building, Testing, and Scaling With SwiftUI
I like the idea of Xcode Previews, but in practice I can’t see the logic of putting elements in your codebase that will auto-open the canvas and take tens of seconds to render a preview (or an error) every time you open a source file. Even when I’m working with SwiftUI, I have to turn previews off. I don’t know how they ever fix this, since it’s booting up virtual machines in the background every time and that will just never be fast or reliable.
You just have to learn what it is good for and what it’s not. It’s good for building small UI components and getting feedback on changes very quickly. It’s not good at running your main view and being reliable when you’re switching between many view files.
One aspect of SwiftUI that is crucial to my workflows is the SwiftUI Preview system. Previews are an incredibly powerful and sophisticated tool, intelligently recompiling only the code which has been modified, in order to achieve a blazing fast edit-refresh cycle.
[…]
Beyond its time-saving qualities, I find that when you develop your views to be previewable, you’re also just writing good code. Writing views that preview easily means you’re writing views that clearly define their inputs and outputs, which make them highly reusable and highly testable.
[…]
The goal of defining a view model protocol is to clearly define all inputs and outputs to a view. By doing so, we are able to create stub implementations of our view models that don’t rely on a production environment to function.
[…]
Since we’re hardcoding our values in, we can largely ignore the behavior of data fetching tasks for most use cases (hence the no-op) and now our preview is based entirely on our locally defined static data. This means we can do lots of things we couldn’t very easily do before!
You’ll notice that regardless of what kind of view model we pass into
PokemonList
, we are always using a production view model forPokemonDetailView
(notice the lack ofStub
in the view model name, which I have been using to indicate the test/stub version of the view model protocols).To rectify this, it’s important to make a key observation: the view models for child views are a dependency of the parent view. In this example,
PokemonDetailViewModel
is a dependency ofPokemonListView
, sincePokemonListView
is the one instantiating it and passing it to the child view. Since it’s a dependency, we should include it in our view model protocol, which requires getting a little fancy with the type system.In order to preserve concrete types through a protocol, we introduce an associated type that allows each version of the view model protocol to define its own detail type. Basically, we’re trying to add the ability for a production view model to say “I want to use a production detail view model” and for stub view models to say “I want to use a stub detail view model”.
Update (2023-08-10): Craig Hockenberry:
If you’re experiencing problems with SwiftUI not updating previews and getting stuck “preparing”, that’s because there are some processes that don’t get terminated and hang around causing problems. Even if you quit Xcode and Simulator.
Here’s a simple script that cleans things up[…]
Update (2023-08-15): Craig Hockenberry:
Xcode Folks: I don’t know what the future of UI previews looks like, but here’s what it doesn’t look like: launching hundreds of CoreSimulator processes that fail unpredictably.
I restart Xcode and the Simulator dozens of times every day because I’m working on things that target multiple platforms. In practice, developers who work across Apple’s entire ecosystem are penalized. Adding watchOS to an iOS project results in reduced productivity.
And now we’re adding visionOS to this mess…
Update (2023-08-18): Craig Hockenberry:
Imagine how awful it would be to do web development by typing a line of code and then firing up a VM with Windows 10 and its Edge browser.
Guess what? We’re doing that awful thing with SwiftUI thousands of times every day.