Inspector 是一个用 Swift 编写的调试库。
Inspector
界面,并使用 键盘命令 在使用 Simulator.app(以及在 iPad 上)时。Swift 包管理器是一种用于自动分发 Swift 代码的工具,并集成到 Swift 编译器中。 它还处于早期开发阶段,但 Inspector 确实支持在支持的平台上使用它。
设置好 Swift 包后,将 Inspector
添加为依赖项就像将其添加到 Package.swift
的 dependencies 值一样简单。
// Add to Package.swift
// For projects with iOS 11+ support
dependencies: [
.package(url: "https://github.com/ipedro/Inspector.git", .upToNextMajor(from: "2.0.0"))
]
// For projects with iOS 14+ support
dependencies: [
.package(url: "https://github.com/ipedro/Inspector.git", .upToNextMajor(from: "3.0.0"))
]
在成功安装后,您需要做的就是在您的应用程序在SceneDelegate.swift
或 AppDelegate.swift
中完成启动后启动 Inspector。 您可以选择添加自己的自定义内容,并调整一些配置。
// Scene Delegate Example
import UIKit
// Your application will not be rejected if you include the Inspector framework in your final bundle, however it's recommended that you import it only when debugging.
#if DEBUG
import Inspector
#endif
final class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let _ = (scene as? UIWindowScene) else { return }
(...)
#if DEBUG
Inspector.setConfiguration(...) // Optional. Add link to InspectorConfiguration
Inspector.setCustomization(...) // Optional. Pass an object that conforms to the `InspectorCustomizationProviding` protocol.
Inspector.start()
#endif
}
(...)
}
// App Delegate Example
import UIKit
// Your application will not be rejected if you include the Inspector framework in your final bundle, however it's recommended that you import it only when debugging.
#if DEBUG
import Inspector
#endif
final class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
(...)
#if DEBUG
Inspector.setConfiguration(...) // Optional. Add link to InspectorConfiguration
Inspector.setCustomization(...) // Optional. Pass an object that conforms to the `InspectorCustomizationProviding` protocol.
Inspector.start()
#endif
return true
}
(...)
}
请注意,SwiftUI 支持尚处于早期阶段,欢迎提供任何反馈。
// Add to your main view, or another view of your choosing
import Inspector
import SwiftUI
struct ContentView: View {
@State var text = "Hello, world!"
@State var date = Date()
@State var isInspecting = false
var body: some View {
NavigationView {
ScrollView {
VStack(spacing: 15) {
DatePicker("Date", selection: $date)
.datePickerStyle(GraphicalDatePickerStyle())
TextField("text field", text: $text)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
Button("Inspect") {
isInspecting.toggle()
}
.padding()
}
.padding(20)
}
.inspect(
isPresented: $isInspecting,
viewHierarchyLayers: nil,
elementColorProvider: nil,
commandGroups: nil,
elementLibraries: nil
)
.navigationTitle("SwiftUI Inspector")
}
}
}
扩展根视图控制器类以启用 Inspector
键盘命令。
// Add to your root view controller.
#if DEBUG
override var keyCommands: [UIKeyCommand]? {
return Inspector.keyCommands
}
#endif
在您的应用程序目标中
New Run Script Phase
作为最后一个阶段。Inspector
相关的文件。# Run Script Phase that removes `Inspector` and all its dependecies from release builds.
if [ $CONFIGURATION == "Release" ]; then
echo "Removing Inspector and dependencies from $TARGET_BUILD_DIR/$FULL_PRODUCT_NAME/"
find $TARGET_BUILD_DIR/$FULL_PRODUCT_NAME -name "Inspector*" | grep . | xargs rm -rf
find $TARGET_BUILD_DIR/$FULL_PRODUCT_NAME -name "UIKeyCommandTableView*" | grep . | xargs rm -rf
find $TARGET_BUILD_DIR/$FULL_PRODUCT_NAME -name "UIKeyboardAnimatable*" | grep . | xargs rm -rf
find $TARGET_BUILD_DIR/$FULL_PRODUCT_NAME -name "UIKitOptions*" | grep . | xargs rm -rf
find $TARGET_BUILD_DIR/$FULL_PRODUCT_NAME -name "Coordinator*" | grep . | xargs rm -rf
fi
可以通过调用 presentInspector(animated:_:)
方法从任何视图控制器或窗口实例呈现检查器。您可以通过各种创造性的方式实现这一点,这里有一些建议。
在启用键盘命令支持后,使用 Simulator.app 或真正的 iPad,您可以
通过按 Ctrl + Shift + 0 调用 Inspector
。
通过按 Ctrl + Shift + 1-8 在显示/隐藏视图层之间切换。
通过按 Ctrl + Shift + 9 显示/隐藏所有图层。
使用您想要的任何键盘命令触发 自定义命令。
您还可以使用手势(例如摇动设备)来呈现 Inspector
。 这样就不需要引入 UI。 一种方便的方法是用以下代码对 UIWindow
进行子类化(或扩展)
// Declare inside a subclass or UIWindow extension.
#if DEBUG
open override func motionBegan(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
super.motionBegan(motion, with: event)
guard motion == .motionShake else { return }
Inspector.present()
}
#endif
在您的应用程序上创建自定义界面(例如浮动按钮或您选择的任何其他控件)后,您可以自己调用 Inspector.present(animated:)
。
// Add to any view controller if your view inherits from `UIControl`
var myControl: MyControl
override func viewDidLoad() {
super.viewDidLoad()
myControl.addTarget(self, action: #selector(tap), for: .touchUpInside)
}
@objc
private func tap(_ sender: Any) {
Inspector.present(animated: true)
}
Inspector
允许您通过 InspectorCustomizationProviding
协议自定义并在特定于您的代码库的视图上引入新的行为。
var elementIconProvider: Inspector.ElementIconProvider? { get }
var viewHierarchyLayers: [Inspector.ViewHierarchyLayer]? { get }
var elementColorProvider: Inspector.ElementColorProvider? { get }
var commandGroups: [Inspector.CommandGroup]? { get }
var elementLibraries: [Inspector.ElementPanelType: [InspectorElementLibraryProtocol]] { get }
ViewHierarchyLayer
是可切换的,并在 Inspector 界面的 Highlight views
部分显示,也可以使用 Ctrl + Shift + 1 - 8 触发。 您可以使用默认的图层之一或创建自己的图层。
默认的视图层级结构图层:
activityIndicators
: *显示活动指示器视图。*buttons
: *显示按钮。*collectionViews
: *显示集合视图。*containerViews
: *显示所有容器视图。*controls
: *显示所有控件。*images
: *显示所有图像视图。*maps
: *显示所有地图视图。*pickers
: *显示所有选择器视图。*progressIndicators
: *显示所有进度指示器视图。*scrollViews
: *显示所有滚动视图。*segmentedControls
: *显示所有分段控件。*spacerViews
: *显示所有间隔视图。*stackViews
: *显示所有堆栈视图。*tableViewCells
: *显示所有表视图单元格。*collectionViewReusableVies
: *显示所有集合可重用视图。*collectionViewCells
: *显示所有集合视图单元格。*staticTexts
: *显示所有静态文本。*switches
: *显示所有开关。*tables
: *显示所有表视图。*textFields
: *显示所有文本字段。*textViews
: *显示所有文本视图。*textInputs
: *显示所有文本输入。*webViews
: *显示所有 Web 视图。*allViews
: *突出显示所有视图。*systemContainers
: *突出显示所有系统容器。*withIdentifier
: *突出显示具有辅助功能标识符的视图。*withoutIdentifier
: *突出显示没有辅助功能标识符的视图。*wireframes
: *显示所有视图的框架。*internalViews
: *突出显示所有。*// Example
var viewHierarchyLayers: [Inspector.ViewHierarchyLayer]? {
[
.controls,
.buttons,
.staticTexts + .images,
.layer(
name: "Without accessibility identifiers",
filter: { element in
guard let accessibilityIdentifier = element.accessibilityIdentifier?.trimmingCharacters(in: .whitespacesAndNewlines) else {
return true
}
return accessibilityIdentifier.isEmpty
}
)
]
}
Return your own icons for custom classes or override exsiting ones. Preferred size is 32 x 32
// Example
var elementIconProvider: Inspector.ElementIconProvider? {
.init { view in
switch view {
case is MyView:
return UIImage(named: "my-view-icon-32")
default:
// you can alwayws fallback to default icons
return nil
}
}
}
返回您自己的层级结构标签颜色方案,而不是(或扩展)默认颜色方案。
// Example
var elementColorProvider: Inspector.ElementColorProvider? {
.init { view in
switch view {
case is MyView:
return .systemPink
default:
// you can alwayws fallback to default color scheme if needed
return nil
}
}
}
命令组显示为主 Inspector
UI 上的部分,并且可以具有与之关联的键盘命令快捷方式,您可以拥有任意数量的组,以及任意数量的命令。
// Example
var commandGroups: [Inspector.CommandGroup]? {
guard let window = window else { return [] }
[
.group(
commands: [
.command(
title: "Reset",
icon: .exampleCommandIcon,
keyCommand: .control(.shift(.key("r"))),
closure: {
// Instantiates a new initial view controller on a Storyboard application.
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateInitialViewController()
// set new instance as the root view controller
window.rootViewController = vc
// restart inspector
Inspector.restart()
}
)
]
)
]
}
元素库是符合 InspectorElementLibraryProtocol
的实体,并且每个都绑定到唯一的类型。 *专业提示:使用枚举。*
// Example
var elementLibraries: [Inspector.ElementPanelType: [InspectorElementLibraryProtocol]] {
[.attributes: ExampleElementLibrary.allCases]
}
// Element Library Example
import UIKit
import Inspector
enum ExampleAttributesLibrary: InspectorElementLibraryProtocol, CaseIterable {
case roundedButton
var targetClass: AnyClass {
switch self {
case .roundedButton:
return RoundedButton.self
}
}
func sections(for object: NSObject) -> InspectorElementSections {
switch self {
case .roundedButton:
return .init(with: RoundedButtonAttributesSectionDataSource(with: object))
}
}
}
// Element Section Data Source
#if DEBUG
import UIKit
import Inspector
final class RoundedButtonAttributesSectionDataSource: InspectorElementSectionDataSource {
var state: InspectorElementSectionState = .collapsed
var title: String = "Rounded Button"
let roundedButton: RoundedButton
init?(with object: NSObject) {
guard let roundedButton = object as? RoundedButton else {
return nil
}
self.roundedButton = roundedButton
}
enum Properties: String, CaseIterable {
case animateOnTouch = "Animate On Touch"
case cornerRadius = "Round Corners"
case backgroundColor = "Background Color"
}
var properties: [InspectorElementProperty] {
Properties.allCases.map { property in
switch property {
case .animateOnTouch:
return .switch(
title: property.rawValue,
isOn: { self.roundedButton.animateOnTouch }
) { animateOnTouch in
self.roundedButton.animateOnTouch = animateOnTouch
}
case .cornerRadius:
return .switch(
title: property.rawValue,
isOn: { self.roundedButton.roundCorners }
) { roundCorners in
self.roundedButton.roundCorners = roundCorners
}
case .backgroundColor:
return .colorPicker(
title: property.rawValue,
color: { self.roundedButton.backgroundColor }
) { newBackgroundColor in
self.roundedButton.backgroundColor = newBackgroundColor
}
}
}
}
}
#endif
您可以通过 PayPal 支持开发。
Inspector
由 Pedro Almeida 拥有和维护。 您可以在 Twitter 上关注他 @ipedro 以获取项目更新和发布。
Inspector
在 MIT 许可证下发布。 请参阅 LICENSE 了解详细信息。