7

There's a lot of code my app normally runs that I would like to skip in Previews. Code that is time-consuming and has no visible effects (such as initializing audio devices). I'm trying to figure out how skip it for previews.

There is an easy way to run code only in the production build of an app using the DEBUG macro. But I don't know of anything similar for non-Preview builds (because Previews presumably build the same code as non-Previews).

I thought that setting a variable, previewMode, within my ViewModel, would work. That way I could set it to true only within the PreviewProvider:

struct MainView_Previews: PreviewProvider {

    static var previews: some View {
        let vm = ViewModel(previewMode: true)
        return MainView(viewModel: vm)
    }
}

and when I created the ViewModel within the SceneDelegate, I could set previewMode to false:

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

    let vm = ViewModel(previewMode: false)
    let mainView = MainView(viewModel: vm)

    // Use a UIHostingController as window root view controller.
    if let windowScene = scene as? UIWindowScene {
        let window = UIWindow(windowScene: windowScene)
        window.rootViewController = UIHostingController(rootView: mainView)
        self.window = window
        window.makeKeyAndVisible()
    }
}

so that I can enclose any code I don't want to run for previews in if !previewMode { ••• }

Unfortunately the code is still running. Evidently the scene() function is getting called whenever my preview updates. :(

How can I specify code to not run for previews?

thanks!

1
  • The answer is available here. Commented Oct 13, 2020 at 17:35

2 Answers 2

5

The only working solution I've found is to use the ProcessInfo.processInfo.environment value for key XCODE_RUNNING_FOR_PREVIEWS. It's set to "1" only when running in preview mode:

let previewMode: Bool = ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1"

See this post.

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

Comments

2
+50

Practically Live-Preview mode run-time does not differ much from Simulator Debug mode run-time. And this, of course, as intended to give us quick (as possible) feedback of our code execution.

Anyway here are some findings... that might be used as solution/workaround for some cases that detection of Preview is highly desirable.

So created from scratch SwiftUI Xcode template project and in all functions of generated entities add print(#function) instruction.

ContentView.swift

import SwiftUI

struct ContentView: View {
    init() {
        print(#function)
    }

    var body: some View {
        print(#function)
        return someView()
            .onAppear {
                print(#function)
            }
    }

    private func someView() -> some View {
        print(#function)
        return Text("Hello, World!")
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        print(#function)
        return ContentView()
    }
}

Perform Debug Preview and see output:

application(_:didFinishLaunchingWithOptions:)
application(_:configurationForConnecting:options:)
scene(_:willConnectTo:options:)
init()
sceneWillEnterForeground(_:)
sceneDidBecomeActive(_:)
2020-06-12 16:08:14.460096+0300 TestPreview[70945:1547508] [Agent] Received remote injection
2020-06-12 16:08:14.460513+0300 TestPreview[70945:1547508] [Agent] Create remote injection Mach transport: 6000026c1500
2020-06-12 16:08:14.460945+0300 TestPreview[70945:1547482] [Agent] No global connection handler, using shared user agent
2020-06-12 16:08:14.461216+0300 TestPreview[70945:1547482] [Agent] Received connection, creating agent
2020-06-12 16:08:15.355019+0300 TestPreview[70945:1547482] [Agent] Received message: < DTXMessage 0x6000029c94a0 : i2.0e c0 object:(__NSDictionaryI*) {
    "updates" : <NSArray 0x7fff8062cc40 | 0 objects>
    "id" : [0]
    "scaleFactorHint" : [3]
    "providerName" : "11TestPreview20ContentView_PreviewsV"
    "products" : <NSArray 0x600000fcc650 | 1 objects>
} > {
    "serviceCommand" : "forwardMessage"
    "type" : "display"
}
__preview__previews
init()
__preview__body
__preview__someView()
__preview__body
__preview__body
__preview__someView()
__preview__body

As it is clear complete workflow of app launching has been performed at start AppDelegate > SceneDelegate > ContentView > Window and only after this the PreviewProvider part.

And in this latter part we see something interesting - all functions of ContentView in Preview mode have __preview prefix (except init)!!

So, finally, here is possible workaround (DISCLAIMER!!! - on your own risk - only demo)

The following variant

struct ContentView: View {

    var body: some View {
        return someView()
            .onAppear {
                if #function.hasPrefix("__preview") {
                    print("Hello Preview!")
                } else {
                    print("Hello World!")
                }
            }
    }

    private func someView() -> some View {
        if #function.hasPrefix("__preview") {
            return Text("Hello Preview!")
        } else {
            return Text("Hello World!")
        }
    }
}

Gives this

demo

2 Comments

Thank you, @Asperi! I'll give it a go. I'm not worried about dangers for what you propose because your method can only err in the direction of allowing non-preview behaviors in preview mode (which is what I've got already) rather than preview behaviors in non-preview mode (which would be a big problem). But in practice the functions that I want to simplify in preview mode are actually the init functions called in scene(_:willConnectTo:options:)… so I'll see whether this will work for that. And, in any case, I didn't know about #function so thank you already for teaching me that!
I was able to test this. Yes, it seems to work in all the code for Views. Unfortunately the code I write always triggers initializations—the actions I want to avoid running—within scene(_:willConnectTo:options:). That way the results of the initializations are attached as environmentObjects or initializer arguments to the root view when it is created & attached to the window. I could change the code to trigger initializations in the root View's onAppear()… though I'd have to reorganize some things as a result, and it wouldn't be ideal. So, other ideas are still welcome—but thanks for this!

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.