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.
9 Comments RSS · Twitter · Mastodon
> achieve a blazing fast edit-refresh cycle
I don't know what he smoked, but I want the same.
I love "You just have to learn" comments on social media from rando people who have been developing iOS software since all the way back in 2021!!! and are here to educate us that all the terrible Apple solutions are actually amazing, we are just using them wrong.
>a blazing fast edit-refresh cycle.
In comparison with testing UI changes in AppKit or UIKit, maybe?
But this sounds like someone who hasn't checked out other toolchains much. It takes seconds to refresh a SwiftUI preview, even in a fairly simple case, on this M1 Pro. You can do way better than that with something like XAML or HTML.
I would guess native/AOT compilation plays a big role here. I wonder if they've explored a JIT mode for this use case? (I imagine it would also help Playgrounds.)
@Sören Even a storyboard with custom @IBDesignable views is much, much faster to render, and that too compiles, instantiates and renders off-process the views, before displaying them in IB.
I know storyboards/xibs are taboo in recent years, but the amount of nonsense that is coming from the Swift/SwiftUI developer community, who many of which, really have no experience with older Apple tech, is just annoying.
I don't think a JIT mode can work, without a major rearcitecture of the runtime, as previews support everything a normal binary supports, such as static and dynamic framework linkage. I don't think it's the compilation that is the issue, but the previews architecture itself. Apple has a tendency to over-engineer their solutions in recent years, and I think this is the case here. They have the Catalyst environment now in all Macs, yet Xcode has to spin up a simulator runtime, that works just like an iOS, meaning to matter how optimized compilation+linking is—just of the changed objects/symbols—the system still has to install the entire app using the normal install process that iOS uses, which is SLOW. It's just not a good design. Even IB doesn't use all this nonsense to render its IBDesignables, or at least didn't in the past.
It's amazing to me that we have such slow tools. Back on the Atari ST, I used TurboAss (8 character program name limit) which would assemble 1.3 million lines of code per minute on an 8Mhz 68000. It was instantaneous even for large programs.
This article summarizes why people like certain languages over others. It's pretty simple: reading code is harder than writing code, and there's less code to maintain in new languages.
Pretty funny the way Haskell is both hated ("They tried and failed? They tried and died!" to quote Dune) and loved (Those who tried and survived it, and those who hope to try!).
I miss the days when Apple actually cared about Interface Builder. Live Previews are nice too but being able to create a design separately and wire it up to code was genius. IMO their size classes were never granular enough for all iOS form factors but they could have taken Interface Builder to the next level but they instead are hell bent on burning it all down and replacing it with this SwiftUI garbage.
Their whole war on Interface Builder is just stupid and sad really.
SwiftUI is awesome for everyone but me, it seems. What am I doing wrong? It keeps punching me in the face every time I try to embrace it.