SwiftUI Introspect 允许您获取 SwiftUI 视图的底层 UIKit 或 AppKit 元素。
例如,使用 SwiftUI Introspect,您可以访问 UITableView 来修改分隔符,或 UINavigationController 来自定义标签栏。
SwiftUI Introspect 的工作原理是在所选视图的顶部添加一个不可见的 IntrospectionView,并在其下方添加一个不可见的“锚点”视图,然后查看两者之间的 UIKit/AppKit 视图层级结构,以找到相关的视图。
例如,当内省一个 ScrollView 时...
ScrollView {
Text("Item 1")
}
.introspect(.scrollView, on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18)) { scrollView in
// do something with UIScrollView
}
... 它将
ScrollView 前后添加标记视图。UIScrollView 实例(如果有)。重要提示
虽然这种内省方法非常可靠,并且本身不太可能被破坏,但未来的操作系统版本需要显式选择加入内省(.iOS(.vXYZ)),考虑到主要操作系统版本之间底层 UIKit/AppKit 视图类型的潜在差异。
默认情况下,.introspect 修饰符直接作用于其接收者。这意味着从您尝试内省的视图内部调用 .introspect 将不会有任何效果。但是,在某些情况下,这是不可能的,或者过于不灵活,在这种情况下,您可以内省一个祖先,但您必须通过覆盖内省 scope 来显式选择加入。
ScrollView {
Text("Item 1")
.introspect(.scrollView, on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18), scope: .ancestor) { scrollView in
// do something with UIScrollView
}
}
SwiftUI Introspect 旨在用于生产环境。它不使用任何私有 API。它仅使用公开可用的方法检查视图层级结构。该库对检查视图层级结构采取防御性方法:没有硬性假设元素以某种方式布局,没有强制转换为 UIKit/AppKit 类,并且如果找不到 UIKit/AppKit 视图,则简单地忽略 .introspect 修饰符。
let package = Package(
dependencies: [
.package(url: "https://github.com/siteline/swiftui-introspect", from: "1.0.0"),
],
targets: [
.target(name: <#Target Name#>, dependencies: [
.product(name: "SwiftUIIntrospect", package: "swiftui-introspect"),
]),
]
)
pod 'SwiftUIIntrospect', '~> 1.0'
ButtonColorPickerDatePickerDatePicker,样式为 .compactDatePicker,样式为 .fieldDatePicker,样式为 .graphicalDatePicker,样式为 .stepperFieldDatePicker,样式为 .wheelFormForm,样式为 .grouped.fullScreenCoverListList,样式为 .borderedList,样式为 .groupedList,样式为 .insetGroupedList,样式为 .insetList,样式为 .sidebarListCellMapNavigationSplitViewNavigationStackNavigationView,样式为 .columnsNavigationView,样式为 .stackPageControlPicker,样式为 .menuPicker,样式为 .segmentedPicker,样式为 .wheel.popoverProgressView,样式为 .circularProgressView,样式为 .linearScrollView.searchableSecureField.sheetSliderStepperTableTabViewTabView,样式为 .pageTextEditorTextFieldTextField,轴向为 .verticalToggleToggle,样式为 buttonToggle,样式为 checkboxToggle,样式为 switchVideoPlayerViewViewControllerWindow缺少元素? 请发起讨论。作为临时解决方案,您可以实现您自己的可内省视图类型。
| SwiftUI | 受影响的框架 | 原因 |
|---|---|---|
| Text | UIKit, AppKit | 不是 UILabel / NSLabel |
| Image | UIKit, AppKit | 不是 UIImageView / NSImageView |
| Button | UIKit | 不是 UIButton |
List {
Text("Item")
}
.introspect(.list, on: .iOS(.v13, .v14, .v15)) { tableView in
tableView.backgroundView = UIView()
tableView.backgroundColor = .cyan
}
.introspect(.list, on: .iOS(.v16, .v17, .v18)) { collectionView in
collectionView.backgroundView = UIView()
collectionView.subviews.dropFirst(1).first?.backgroundColor = .cyan
}
ScrollView {
Text("Item")
}
.introspect(.scrollView, on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18)) { scrollView in
scrollView.backgroundColor = .red
}
NavigationView {
Text("Item")
}
.navigationViewStyle(.stack)
.introspect(.navigationView(style: .stack), on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18)) { navigationController in
navigationController.navigationBar.backgroundColor = .cyan
}
TextField("Text Field", text: <#Binding<String>#>)
.introspect(.textField, on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18)) { textField in
textField.backgroundColor = .red
}
缺少元素? 请发起讨论。
如果 SwiftUI Introspect(不太可能)不支持您正在寻找的 SwiftUI 元素,您可以实现您自己的可内省类型。
例如,以下是该库如何实现可内省的 TextField 类型
import SwiftUI
@_spi(Advanced) import SwiftUIIntrospect
public struct TextFieldType: IntrospectableViewType {}
extension IntrospectableViewType where Self == TextFieldType {
public static var textField: Self { .init() }
}
#if canImport(UIKit)
extension iOSViewVersion<TextFieldType, UITextField> {
public static let v13 = Self(for: .v13)
public static let v14 = Self(for: .v14)
public static let v15 = Self(for: .v15)
public static let v16 = Self(for: .v16)
public static let v17 = Self(for: .v17)
public static let v18 = Self(for: .v18)
}
extension tvOSViewVersion<TextFieldType, UITextField> {
public static let v13 = Self(for: .v13)
public static let v14 = Self(for: .v14)
public static let v15 = Self(for: .v15)
public static let v16 = Self(for: .v16)
public static let v17 = Self(for: .v17)
public static let v18 = Self(for: .v18)
}
extension visionOSViewVersion<TextFieldType, UITextField> {
public static let v1 = Self(for: .v1)
public static let v2 = Self(for: .v2)
}
#elseif canImport(AppKit)
extension macOSViewVersion<TextFieldType, NSTextField> {
public static let v10_15 = Self(for: .v10_15)
public static let v11 = Self(for: .v11)
public static let v12 = Self(for: .v12)
public static let v13 = Self(for: .v13)
public static let v14 = Self(for: .v14)
public static let v15 = Self(for: .v15)
}
#endif
默认情况下,内省应用于每个特定的平台版本。对于定期维护的代码库来说,这是一个合理的默认设置,以实现最大的可预测性,但对于例如库开发人员来说,这并不总是一个好的选择,他们可能希望涵盖尽可能多的未来平台版本,以便为他们的库提供长期未来功能的最佳机会,而无需定期维护。
对于这种情况,SwiftUI Introspect 在 Advanced SPI 背后提供了基于范围的平台版本谓词
import SwiftUI
@_spi(Advanced) import SwiftUIIntrospect
struct ContentView: View {
var body: some View {
ScrollView {
// ...
}
.introspect(.scrollView, on: .iOS(.v13...)) { scrollView in
// ...
}
}
}
请记住,应该谨慎使用此功能,并且充分了解,除非明确可用,否则任何未来的操作系统版本都可能破坏预期的内省类型。例如,如果在上面的示例中,假设 iOS 19 停止在底层使用 UIScrollView,则永远不会在所述平台上调用自定义闭包。
有时,您可能需要将内省实例保留比自定义闭包生命周期更长的时间。在这种情况下,@State 不是一个好的选择,因为它会产生 retain cycles。相反,SwiftUI Introspect 在 Advanced SPI 背后提供了 @Weak 属性包装器
import SwiftUI
@_spi(Advanced) import SwiftUIIntrospect
struct ContentView: View {
@Weak var scrollView: UIScrollView?
var body: some View {
ScrollView {
// ...
}
.introspect(.scrollView, on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18)) { scrollView in
self.scrollView = scrollView
}
}
}
以下是由 SwiftUI Introspect 库驱动的开源库列表
如果您正在开发基于 SwiftUI Introspect 构建的库,或者知道这样的库,请随时提交 PR 以将其添加到列表中。