Archive for August 3, 2023

Thursday, August 3, 2023

Building, Testing, and Scaling With SwiftUI

Steve Troughton-Smith:

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.

Rafael Schmitt:

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.

Gustavo Poscidonio:

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!

Gustavo Poscidonio:

You’ll notice that regardless of what kind of view model we pass into PokemonList, we are always using a production view model for PokemonDetailView (notice the lack of Stub 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 of PokemonListView, since PokemonListView 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.

Textual Paint

Isaiah Odhner (via Rhet Turnbull):

MS Paint in your terminal.

This is a TUI (Text User Interface) image editor, inspired by MS Paint, built with Textual.

[…]

Many file formats are supported, including ANSI art, raster images, SVG and HTML.

How NSHostingView Determines Its Sizing

Brian Webster:

I couldn’t get my SwiftUI view to expand to fill up the entire superview that the NSHostingView was being added to.

[…]

The next thing I came across was a property on NSHostingView called sizingOptions, which is described in the documentation as “The options for how the hosting view creates and updates constraints based on the size of its SwiftUI content.” Well that sounds promising! The default setting is all three options, [.minSize, .intrinsicContentSize, .maxSize], so I tried setting it to just [.minSize] and lo and behold, it worked! The Spacer was now growing to take up the whole height of the superview! (setting [.minSize, .maxSize] also worked)

But, there’s just one problem… this property was introduced in macOS 13 and I’m still targeting macOS 12. 😭 But, after seeing this property and how it works, I think I now understand what was going on with the size proposals earlier. I believe what NSHostingView does is to probe its rootView once each so it can set up constraints for a minimum size, intrinsic content size, and maximum size. […] So what I need to do is basically reimplement what the sizingOptions property is doing, which is to ignore the intrinsic content size of the SwiftUI view.

Previously: