10

Up until Xcode 16, it was possible to check in build scripts of your Xcode project via the ENABLE_PREVIEWS environment variable whether a build for a SwiftUI preview is being executed.

This made it possible, for example, to skip build scripts for SwiftUI preview builds or to execute other commands in the scripts. One use case was to skip Linter and code formatter steps so that the preview builds were not infinitely slow.

In addition, with this variable and via macros, code could be conditionally compiled for SwiftUI preview builds or excluded from compilation.

None of this works anymore with Xcode 16 (including the current beta v5) and the new SwiftUI Preview compilation system that Apple has introduced.

Has anyone already found an alternative?

You can easily observe the new behavior in Xcode 16 by switching on the option to display the environment variables for build scripts. With the new build system, the value of ENABLE_PREVIEWS will always be NO.

I know that you can currently still switch to the old SwiftUI preview system with the "Use Legacy Previews Execution" option, but Apple describes in the Xcode 16 beta release notes that this method will be removed in the near future.

2 Answers 2

8

Since I received an “official” response from Apple Engineers on the subject, I wanted to share the findings on this and the answer to my questions:

First of all, there is no actual replacement for ENABLE_PREVIEWS with Xcode 16 and the new build system.

The change has been made deliberately, and the following quote explains why:

New in Xcode 16, Previews now shares build products with normal debug builds. There's no difference anymore. This effort drastically reduces preview time since there’s no need to completely rebuild the entire project. This also resolves bugs when previewing in large package graphs and positions us to maintain fidelity with the normal build-and-run behavior. All debug builds are now “enabled for previews” because there's no distinction.

One can also observe that the build behavior of SwiftUI previews has changed with Xcode 16. For example, a build for a SwiftUI preview may no longer be triggered if the artifacts of a previous debug build are already available and no code has changed for the preview.

With regard to the new SwiftUI build system, this means:

1. Conditionally compiled code for SwiftUI previews is no longer possible

As already described in the question, special code could previously exist in a project (e.g. mocks / extensions etc.), which was compiled exclusively for SwiftUI Previews and was not available in other builds.

This code could therefore not be used “by mistake” in productive parts of the project.

The only thing that is still possible in this respect is to rely on the dead code stripping feature, which removes unused code from builds. Of course, this feature does not prevent a developer from using code that may only be used for previews and should not end up in the production app.

2. Prevention of code execution in SwiftUI previews at runtime

Since SwiftUI Previews are known to start the app in the background with every view / refresh, code is also executed that you may not want to execute. This can include, for example, network calls in the AppDelegate. Information about the developer systems that should not be disclosed may be leaked this way.

ENABLE_PREVIEWS made it possible to use a compilation conditional in a simple way to prevent such executions in previews.

Fortunately, at this point there is a possibility at runtime to identify the execution for previews via an environment variable:

ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"]

Therefore, without ENABLE_PREVIEWS there is a way that certain code for previews is not executed.

3. Build scripts and SwiftUI preview builds

In many large iOS projects, various build scripts are often used that should only be executed for certain build variants for various reasons. In general, such scripts may slow down the build process considerably. (e.g. formatter and linter scripts)

ENABLE_PREVIEWS was a very useful tool in the past to significantly speed up and optimize the build process for SwiftUI previews, as these could sometimes become extremely slow in large projects.

There is no direct replacement here either. It might be possible to use the default "Debug" build configuration (which Apple says is used by SwiftUI previews) exclusively for SwiftUI previews and use or create a separate debug build configuration with a different name for regular debug builds.

However, I have not yet tried this in practice.

Sign up to request clarification or add additional context in comments.

3 Comments

This is very sad and hopeless :/
As mentioned in the extension of view in this answer: stackoverflow.com/a/77215542/129202 we can still use XCODE_RUNNING_FOR_PREVIEWS it seems. However for some reason it doesn't seem like AppDelegate/SceneDelegate are called anymore (or maybe it's just me)?
If the purpose is the speed up preview builds (it is, for me): In Xcode 26.0 (or recently) there is a new setting COMPILATION_CACHE_ENABLE_CACHING that might be able to speed up builds. I haven't gotten around to testing if that really works or if it improves Preview builds yet though, but might be worth looking into.
0

I compared all the build settings used for normal build and previews build. the only difference I found is target device env TARGET_DEVICE_IDENTIFIER

Here is my temporary solution instead of using ENABLE_PREVIEWS, you have to find your destination id used by Previews (I got this value from "Show environment variables in build log")

if [ "TARGET_DEVICE_IDENTIFIER" = "<simulator id>"]; then
    exit 0
fi

3 Comments

I have since received an answer from Apple developers and can say that there is currently simply no direct replacement for ENABLE_PREVIEWS. Apple has deliberately changed it so that the normal build process is used for previews. Your solution is only applicable to a very limited extent, as it affects not only preview builds, but all builds for the specified simulator device.
@ITGuy, yes you are right, it is very limited. Because of that, I added code to AppDelegate that is running in DEBUG on simulator and save to the file in project_dir the value of ProcessInfo.processInfo.environment["SIMULATOR_UDID"]. After that, I can use this UDID in build phases. Not the perfect solution, but I can change simulators and logic is working fine on 2nd+ app run.
Thanks for sharing the solution that works in your use case. Even if it is not a general solution, it may help other developers.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.