Fixing the dreaded SwiftUI paused previews

Tomáš Kafka
2 min readSep 22, 2021

--

Many people report that they

have to repeatedly click ‘resume’ for SwiftUI previews to actually appear

or

preview fails with a cryptical ‘Command CompileSwiftSources failed with a nonzero exit code’ error, even when previous compile commands can be run in a shell with zero exit code

For me, this happened in a project with a shell script set as a build step before compilation.

What happened

For some reason, previews are building in a non-standard environment, and my shell script was failing. Xcode 13 however didn’t check the error code after running the script, but it continued with a compile phase, and then showed the script’s error code as an error code coming from build step.

I guess this can be very confusing in lot of other cases when you have a failing pre-build step.

Another issue is that I was copying the files into the project with cp, and this always changed the file modification time, leading Xcode to rebuild the project and invalidate the caches every time the build script ran.

Here is what Apple said about that (FB9634311):

If a project has a shell script build phase that modifies the source, what happens is:

1. Previews kicks off a build
2. The build modifies the project’s source
3. Previews detects the changes to the project’s source and kicks off another update
4. But the build from # 1 didn’t finish, so we cancel and do it again
5. Which then modifies the project’s source again…
6. (and the earth melts)

While the breakage is surfaced the worst with previews, it’s also silently and constantly invalidating your build cache and making incremental builds impossible for the build system, so it’s a good thing to fix in the long term.

Solution, part 1: skip ‘run script‘ phase when building previews

This prevented generating errors during the ‘Build for previews’.

You can detect if the project is being built in preview mode, and handle it in script. In my case, I would assume that the changes were made in preceding build already, so I just terminated the script prematurely.

And to be extra sure, I cleaned up the error code at the end of the script:

This seems to have fixed one part of problem.

Solution, part 2: use rsync -t instead of cp

Instead of cp, which modifies the destination file’s modification timestamp, and causes Xcode to think that the file changed, I used rsync -t (it’s just a drop-in replacement: cp sourceFile destinationFilersync -t sourceFile destinationFile).

This preserves the original file’s timestamp, so when a build step is run with a same parameters as before, the file doesn’t appear changed, and Xcode is happy reusing it’s caches.

--

--