在 Xcode 中简化 SwiftUI 预览
Streamlining SwiftUI Previews in Xcode
我的应用程序通常 运行 有很多代码我想在预览中跳过。耗时且没有可见效果的代码(例如初始化音频设备)。我想弄清楚如何跳过它进行预览。
有一种简单的方法可以 运行 使用 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 { ••• }
中附上任何我不想 运行 预览的代码
不幸的是代码仍然是 运行ning。显然,每当我的预览更新时,都会调用 scene()
函数。 :(
如何指定代码不 运行 进行预览?
谢谢!
实际上实时预览模式 运行-time 与模拟器调试模式 运行-time 没有太大区别。当然,这旨在为我们提供代码执行的快速(尽可能)反馈。
无论如何,这里有一些发现...在某些情况下 solution/workaround 非常需要检测预览。
因此从头开始创建 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 > SceneDelegate > ContentView > 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!")
}
}
}
给这个
我找到的唯一可行的解决方案是使用键 XCODE_RUNNING_FOR_PREVIEWS
的 ProcessInfo.processInfo.environment
值。仅当 运行 处于预览模式时才设置为“1”:
let previewMode: Bool = ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1"
参见。
我的应用程序通常 运行 有很多代码我想在预览中跳过。耗时且没有可见效果的代码(例如初始化音频设备)。我想弄清楚如何跳过它进行预览。
有一种简单的方法可以 运行 使用 DEBUG 宏仅在应用程序的生产构建中编码。但是我不知道非预览版本有什么类似的东西(因为预览可能会构建与非预览相同的代码)。
我认为在我的 ViewModel 中设置一个变量 previewMode
是可行的。这样我就可以仅在 PreviewProvider:
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 { ••• }
不幸的是代码仍然是 运行ning。显然,每当我的预览更新时,都会调用 scene()
函数。 :(
如何指定代码不 运行 进行预览?
谢谢!
实际上实时预览模式 运行-time 与模拟器调试模式 运行-time 没有太大区别。当然,这旨在为我们提供代码执行的快速(尽可能)反馈。
无论如何,这里有一些发现...在某些情况下 solution/workaround 非常需要检测预览。
因此从头开始创建 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 > SceneDelegate > ContentView > 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!")
}
}
}
给这个
我找到的唯一可行的解决方案是使用键 XCODE_RUNNING_FOR_PREVIEWS
的 ProcessInfo.processInfo.environment
值。仅当 运行 处于预览模式时才设置为“1”:
let previewMode: Bool = ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1"
参见