【问题标题】:Streamlining SwiftUI Previews in Xcode简化 Xcode 中的 SwiftUI 预览
【发布时间】:2020-06-04 14:51:18
【问题描述】:

有很多我想在预览中跳过的应用程序通常运行的代码。耗时且没有可见效果的代码(例如初始化音频设备)。我正在尝试弄清楚如何跳过它进行预览。

有一种简单的方法可以使用 DEBUG 宏仅在应用的生产版本中运行代码。但我不知道非预览版有什么类似的东西(因为预览版可能会构建与非预览版相同的代码)。

我认为在我的 ViewModel 中设置一个变量 previewMode 会起作用。这样我只能在 PreviewProvider 中将其设置为 true:

struct MainView_Previews: PreviewProvider {

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

当我在 SceneDelegate 中创建 ViewModel 时,我可以将 previewMode 设置为 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()
    }
}

这样我就可以在if !previewMode { ••• }中附上我不想运行的任何预览代码

不幸的是,代码仍在运行。显然,每当我的预览更新时,都会调用 scene() 函数。 :(

如何指定不运行预览的代码?

谢谢!

【问题讨论】:

标签: xcode swiftui preview


【解决方案1】:

实际上实时预览模式运行时与模拟器调试模式运行时没有太大区别。当然,这旨在为我们提供代码执行的快速(尽可能)反馈。

无论如何,这里有一些发现...对于某些非常需要检测预览的情况,它们可能用作解决方案/解决方法。

所以从头开始创建 SwiftUI Xcode 模板项目并在生成的实体的所有功能中添加print(#function) 指令。

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()
    }
}

执行调试预览并查看输出:

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

很明显,应用启动的完整工作流程已在开始 AppDelegate &gt; SceneDelegate &gt; ContentView &gt; Window 时执行,仅在此之后 PreviewProvider 部分。

在后面的部分我们看到了一些有趣的东西——ContentView 在预览模式下的所有函数都有 __preview 前缀(除了 init)!!

所以,最后,这里有一个可能的解决方法(免责声明!!! - 风险自负 - 仅限演示)

以下变体

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!")
        }
    }
}

给这个

【讨论】:

  • 谢谢你,@Asperi!我会试一试的。我不担心您建议的危险,因为您的方法只能在允许预览模式下的非预览行为(这是我已经拥有的)的方向上出错,而​​不是在非预览模式下预览行为(其中会是个大问题)。但在实践中,我想在预览模式下简化的函数实际上是在场景中调用的 init 函数(_:willConnectTo:options:)……所以我会看看这是否适用。而且,无论如何,我不知道#function,所以已经谢谢你教我了!
  • 我能够对此进行测试。是的,它似乎适用于 Views 的所有代码。不幸的是,我编写的代码总是在场景(_:willConnectTo:options:) 内触发初始化——我想避免运行的动作。这样,初始化的结果在创建并附加到窗口时作为 environmentObjects 或初始化器参数附加到根视图。我可以更改代码以在根视图的 onAppear() 中触发初始化……尽管结果我必须重新组织一些东西,这并不理想。因此,仍然欢迎其他想法 - 但感谢您!
【解决方案2】:

我发现的唯一可行的解​​决方案是使用 ProcessInfo.processInfo.environment 值作为键 XCODE_RUNNING_FOR_PREVIEWS。仅在预览模式下运行时设置为“1”:

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

this post

【讨论】:

    猜你喜欢
    • 2022-08-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-28
    • 2022-10-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多