{"id":25764681,"url":"https://github.com/ipedro/inspector","last_synced_at":"2025-02-26T21:19:50.205Z","repository":{"id":39642971,"uuid":"300834125","full_name":"ipedro/Inspector","owner":"ipedro","description":"Inspector is a debugging library written in Swift.","archived":false,"fork":false,"pushed_at":"2023-12-11T19:51:57.000Z","size":53923,"stargazers_count":159,"open_issues_count":0,"forks_count":13,"subscribers_count":5,"default_branch":"develop","last_synced_at":"2025-02-19T22:48:07.064Z","etag":null,"topics":["debugging-tool","inspector","ios","ios-swift","spm","swift","swiftui","ui","ui-debugger","uikit","visual-debugging","xcode"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ipedro.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-10-03T08:35:22.000Z","updated_at":"2024-11-22T06:19:22.000Z","dependencies_parsed_at":"2023-01-31T08:45:42.170Z","dependency_job_id":null,"html_url":"https://github.com/ipedro/Inspector","commit_stats":null,"previous_names":[],"tags_count":32,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ipedro%2FInspector","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ipedro%2FInspector/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ipedro%2FInspector/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ipedro%2FInspector/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ipedro","download_url":"https://codeload.github.com/ipedro/Inspector/tar.gz/refs/heads/develop","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240935082,"owners_count":19881096,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["debugging-tool","inspector","ios","ios-swift","spm","swift","swiftui","ui","ui-debugger","uikit","visual-debugging","xcode"],"created_at":"2025-02-26T21:19:49.543Z","updated_at":"2025-02-26T21:19:50.189Z","avatar_url":"https://github.com/ipedro.png","language":"Swift","funding_links":["https://www.paypal.com/donate?hosted_button_id=LJU86LQ4NUYGN"],"categories":[],"sub_categories":[],"readme":"\n[![](https://img.shields.io/github/license/ipedro/Inspector)](https://github.com/ipedro/Inspector/blob/main/LICENSE) \n![](https://img.shields.io/github/v/tag/ipedro/Inspector?label=Swift%20Package\u0026sort=semver)\n![](https://img.shields.io/badge/platform-iOS-lightgrey)\n\n\n# 🕵🏽‍♂️ Inspector\n\nInspector is a debugging library written in Swift.\n\n![Header](https://github.com/ipedro/Inspector/raw/develop/Documentation/inspector_header.png)\n![Demo GIF](https://github.com/ipedro/Inspector/raw/develop/Documentation/inspector_demo.gif)\n\n## Contents\n* [Why use it?](#why-use-it)\n    * [Improve development experience](#improve-development-experience)\n    * [Improve QA and Designer feedback with a reverse Zeplin](#improve-qa-and-designer-feedback-with-a-reverse-zeplin)\n* [Requirements](#requirements)\n* [Installation](#installation)\n    * [Swift Package Manager](#swift-package-manager)\n* [Setup](#setup)\n    * [Scene Delegate](#scenedelegate.swift)\n    * [App Delegate](#appdelegate.swift)\n    * [SwiftUI (Beta)](#swiftui-beta)\n    * [Enable Key Commands *(Recommended)*](#enable-key-commands-recommended)\n    * [Remove framework files from release builds *(Optional)*](#remove-framework-files-from-release-builds-optional)\n* [Presenting the Inspector](#presenting-the-inspector)\n    * [Using built-in Key Commands (Simulator and iPads)](#using-built-in-key-commands-available-on-simulator-and-ipads)\n    * [With motion gestures](#with-motion-gestures)\n    * [Adding custom UI](#adding-custom-ui)\n* [Customization](#Customization)\n    * [InspectorCustomizationProviding Protocol](#inspectorcustomizationproviding-protocol)\n        * [Custom element libraries](#var-inspectorelementlibraries-inspectorelementlibraryprotocol--get-)\n        * [Custom Element icons](#var-elementIconProvider-inspectorelementiconprovider--get-)\n        * [View hierarchy layers](#var-inspectorviewhierarchylayers-inspectorviewhierarchylayer--get-)\n        * [View hierarchy color scheme](#var-inspectorviewhierarchycolorscheme-inspectorviewhierarchycolorscheme--get-)\n        * [Custom commands](#var-inspectorcommandgroups-inspectorcommandgroup--get-)\n* [Donate](#donate)\n* [Credits](#credits)\n* [License](#license)\n\n---\n\n## Requirements\n\n* iOS 11+\n* Xcode 13+\n* Swift 5.4+\n\n---\n\n## Why use it?\n\n### Improve development experience\n\n* Add your own [custom commands](#var-inspectorcommandgroups-inspectorcommandgroup--get-) to the main `Inspector` interface and make use of [key commands](#using-built-in-key-commands-available-on-simulator-and-ipads) while using the Simulator.app (and also on iPad).\n* [Create layer views](#var-inspectorviewhierarchylayers-inspectorviewhierarchylayer--get-) by any criteria you choose to help you visualize application state: class, a property, anything.\n* Inspect view hierarchy faster then using Xcode's built-in one, or\n* Inspect view hierarchy without Xcode.\n* Test changes and fix views live.\n\n### Improve QA and Designer feedback with a reverse [Zeplin](https://zeplin.io)\n* Inspect view hierarchy without Xcode.\n* Test changes and fix views live.\n* Easily validate specific state behaviors.\n* Better understanding of the inner-workings of components\n* Give more accurate feedback for developers. \n\n---\n## Installation\n\n## Swift Package Manager\n\nThe [Swift Package Manager](https://swift.org/package-manager/) is a tool for automating the distribution of Swift code and is integrated into the swift compiler. It is in early development, but Inspector does support its use on supported platforms.\n\nOnce you have your Swift package set up, adding `Inspector` as a dependency is as easy as adding it to the dependencies value of your `Package.swift`.\n\n```swift\n// Add to Package.swift\n\n// For projects with iOS 11+ support\ndependencies: [\n    .package(url: \"https://github.com/ipedro/Inspector.git\", .upToNextMajor(from: \"2.0.0\"))\n]\n\n// For projects with iOS 14+ support\ndependencies: [\n    .package(url: \"https://github.com/ipedro/Inspector.git\", .upToNextMajor(from: \"3.0.0\"))\n]\n```\n---\n## Setup\n\nAfter a [successful installation](#installation), all you need to do is start the Inspector after your app finishes launching in [`SceneDelegate.swift`](#scene-delegate-example) or [`AppDelegate.swift`](#app-delegate-example). You can optionally add your own custom content, and tweak some configurations.\n\n### SceneDelegate.swift\n\n```swift\n// Scene Delegate Example\n\nimport UIKit\n\n// 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.\n\n#if DEBUG\nimport Inspector\n#endif\n\nfinal class SceneDelegate: UIResponder, UIWindowSceneDelegate {\n    var window: UIWindow?\n\n    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {\n        guard let _ = (scene as? UIWindowScene) else { return }\n\n        (...)\n        \n        #if DEBUG\n        Inspector.setConfiguration(...) // Optional. Add link to InspectorConfiguration\n        Inspector.setCustomization(...) // Optional. Pass an object that conforms to the `InspectorCustomizationProviding` protocol.\n        Inspector.start()\n        #endif\n    }\n\n    (...)\n}\n```\n### AppDelegate.swift\n\n```swift\n// App Delegate Example\n\nimport UIKit\n\n// 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.\n\n#if DEBUG\nimport Inspector\n#endif\n\nfinal class AppDelegate: UIResponder, UIApplicationDelegate {\n    var window: UIWindow?\n    \n    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -\u003e Bool {        \n        (...)\n\n        #if DEBUG\n        Inspector.setConfiguration(...) // Optional. Add link to InspectorConfiguration\n        Inspector.setCustomization(...) // Optional. Pass an object that conforms to the `InspectorCustomizationProviding` protocol.\n        Inspector.start()\n        #endif\n\n        return true\n    }\n\n    (...)\n}\n```\n\n### SwiftUI (Beta)\n\n**Please note that SwiftUI support is in early stages and any feedback is welcome.**\n\n\n```swift\n// Add to your main view, or another view of your choosing\n\nimport Inspector\nimport SwiftUI\n\nstruct ContentView: View {\n    @State var text = \"Hello, world!\"\n    @State var date = Date()\n    @State var isInspecting = false\n\n    var body: some View {\n        NavigationView {\n            ScrollView {\n                VStack(spacing: 15) {\n                    DatePicker(\"Date\", selection: $date)\n                        .datePickerStyle(GraphicalDatePickerStyle())\n\n                    TextField(\"text field\", text: $text)\n                        .textFieldStyle(RoundedBorderTextFieldStyle())\n                        .padding()\n\n                    Button(\"Inspect\") {\n                        isInspecting.toggle()\n                    }\n                    .padding()\n                }\n                .padding(20)\n            }\n            .inspect(\n                isPresented: $isInspecting,\n                viewHierarchyLayers: nil,\n                elementColorProvider: nil,\n                commandGroups: nil,\n                elementLibraries: nil\n            )\n            .navigationTitle(\"SwiftUI Inspector\")\n        }\n    }\n}\n```\n\n### Enable Key Commands *(Recommended)*\n\nExtend the root view controller class to enable `Inspector` key commands.\n\n```swift\n// Add to your root view controller.\n\n#if DEBUG\noverride var keyCommands: [UIKeyCommand]? {\n    return Inspector.keyCommands\n}\n#endif\n```\n    \n### Remove framework files from release builds *(Recommended)* \nIn your app target: \n- Add a `New Run Script Phase` as the last phase.\n- Then paste the script below  to remove all `Inspector` related files from your release builds.\n\n``` sh\n# Run Script Phase that removes `Inspector` and all its dependecies from release builds.\n\nif [ $CONFIGURATION == \"Release\" ]; then\n    echo \"Removing Inspector and dependencies from $TARGET_BUILD_DIR/$FULL_PRODUCT_NAME/\"\n\n    find $TARGET_BUILD_DIR/$FULL_PRODUCT_NAME -name \"Inspector*\" | grep . | xargs rm -rf\n    find $TARGET_BUILD_DIR/$FULL_PRODUCT_NAME -name \"UIKeyCommandTableView*\" | grep . | xargs rm -rf\n    find $TARGET_BUILD_DIR/$FULL_PRODUCT_NAME -name \"UIKeyboardAnimatable*\" | grep . | xargs rm -rf\n    find $TARGET_BUILD_DIR/$FULL_PRODUCT_NAME -name \"UIKitOptions*\" | grep . | xargs rm -rf\n    find $TARGET_BUILD_DIR/$FULL_PRODUCT_NAME -name \"Coordinator*\" | grep . | xargs rm -rf\nfi\n\n```\n---\n\n## Presenting the Inspector\n\nThe inspector can be presented from any view controller or window instance by calling the `presentInspector(animated:_:)` method. And that you can achieve in all sorts of creative ways, heres some suggestions.\n\n### Using built-in Key Commands (Available on Simulator and iPads)\n\n![](Documentation/inspector_key-commands.jpg)\n\nAfter [enabling Key Commands support](#enable-key-commands-recommended), using the Simulator.app or a real iPad, you can:\n\n- Invoke `Inspector` by pressing \u003ckbd\u003eCtrl\u003c/kbd\u003e + \u003ckbd\u003eShift\u003c/kbd\u003e + \u003ckbd\u003e0\u003c/kbd\u003e.\n\n- Toggle between showing/hiding view layers by pressing \u003ckbd\u003eCtrl\u003c/kbd\u003e + \u003ckbd\u003eShift\u003c/kbd\u003e + \u003ckbd\u003e1-8\u003c/kbd\u003e.\n\n- Showing/hide all layers by pressing \u003ckbd\u003eCtrl\u003c/kbd\u003e + \u003ckbd\u003eShift\u003c/kbd\u003e + \u003ckbd\u003e9\u003c/kbd\u003e.\n\n- Trigger [custom commands](#var-inspectorcommandgroups-inspectorcommandgroup--get-) with any key command you want.\n\n\n### With motion gestures\n\nYou can also present `Inspector` using a gesture, like shaking the device. That way no UI needs to be introduced. One convienient way to do it is subclassing (or extending) `UIWindow` with the following code:\n\n```swift\n// Declare inside a subclass or UIWindow extension.\n\n#if DEBUG\nopen override func motionBegan(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {\n    super.motionBegan(motion, with: event)\n\n    guard motion == .motionShake else { return }\n\n    Inspector.present()\n}\n#endif\n```\n\n### Adding custom UI\n\nAfter creating a custom interface on your app, such as a floating button, or any other control of your choosing, you can call `Inspector.present(animated:)` yourself.\n\n```swift \n// Add to any view controller if your view inherits from `UIControl`\n\nvar myControl: MyControl\n\noverride func viewDidLoad() {\n    super.viewDidLoad()\n\n    myControl.addTarget(self, action: #selector(tap), for: .touchUpInside)\n}\n\n@objc \nprivate func tap(_ sender: Any) {\n    Inspector.present(animated: true)\n}\n```\n\n---\n\n## Customization\n\n`Inspector` allows you to customize and introduce new behavior on views specific to your codebase, through the `InspectorCustomizationProviding` Protocol.\n\n## InspectorCustomizationProviding Protocol\n* [`var elementIconProvider: Inspector.ElementIconProvider? { get }`](#var-elementIconProvider-elementiconprovider--get-)\n* [`var viewHierarchyLayers: [Inspector.ViewHierarchyLayer]? { get }`](#var-viewhierarchylayers-viewhierarchylayer--get-)\n* [`var elementColorProvider: Inspector.ElementColorProvider? { get }`](#var-viewhierarchycolorscheme-viewhierarchycolorscheme--get-)\n* [`var commandGroups: [Inspector.CommandGroup]? { get }`](#var-commandgroups-commandgroup--get-)\n* [`var elementLibraries: [Inspector.ElementPanelType: [InspectorElementLibraryProtocol]] { get }`](#var-elementlibraries-inspectorelementlibraryprotocol--get-)\n\n---\n\n#### `var viewHierarchyLayers: [Inspector.ViewHierarchyLayer]? { get }`\n\n`ViewHierarchyLayer` are toggleable and shown in the `Highlight views` section on the Inspector interface, and also can be triggered with \u003ckbd\u003eCtrl\u003c/kbd\u003e + \u003ckbd\u003eShift\u003c/kbd\u003e + \u003ckbd\u003e1 - 8\u003c/kbd\u003e. You can use one of the default ones or create your own.\n\n**Default View Hierarchy Layers**:\n\n- `activityIndicators`: *Shows activity indicator views.*\n- `buttons`: *Shows buttons.*\n- `collectionViews`: *Shows collection views.*\n- `containerViews`: *Shows all container views.*\n- `controls`: *Shows all controls.*\n- `images`: *Shows all image views.*\n- `maps`: *Shows all map views.*\n- `pickers`: *Shows all picker views.*\n- `progressIndicators`: *Shows all progress indicator views.*\n- `scrollViews`: *Shows all scroll views.*\n- `segmentedControls`: *Shows all segmented controls.*\n- `spacerViews`: *Shows all spacer views.*\n- `stackViews`: *Shows all stack views.*\n- `tableViewCells`: *Shows all table view cells.*\n- `collectionViewReusableVies`: *Shows all collection resusable views.*\n- `collectionViewCells`: *Shows all collection view cells.*\n- `staticTexts`: *Shows all static texts.*\n- `switches`: *Shows all switches.*\n- `tables`: *Shows all table views.*\n- `textFields`: *Shows all text fields.*\n- `textViews`: *Shows all text views.*\n- `textInputs`: *Shows all text inputs.*\n- `webViews`: *Shows all web views.*\n- `allViews`: *Highlights all views.*\n- `systemContainers`: *Highlights all system containers.*\n- `withIdentifier`: *Highlights views with an accessbility identifier.*\n- `withoutIdentifier`: *Highlights views without an accessbility identifier.*\n- `wireframes`: *Shows frames of all views.*\n- `internalViews`: *Highlights all.*\n\n\n```swift\n// Example\n\nvar viewHierarchyLayers: [Inspector.ViewHierarchyLayer]? {\n    [\n        .controls,\n        .buttons,\n        .staticTexts + .images,\n        .layer(\n            name: \"Without accessibility identifiers\",\n            filter: { element in\n                guard let accessibilityIdentifier = element.accessibilityIdentifier?.trimmingCharacters(in: .whitespacesAndNewlines) else {\n                    return true\n                }\n                return accessibilityIdentifier.isEmpty\n            }\n        )\n    ]\n}\n\n```\n\n---\n\n#### `var elementIconProvider: Inspector.ElementIconProvider? { get }`\n\n    Return your own icons for custom classes or override exsiting ones. Preferred size is 32 x 32\n\n```swift\n// Example\n\nvar elementIconProvider: Inspector.ElementIconProvider? {\n    .init { view in\n        switch view {\n        case is MyView:\n            return UIImage(named: \"my-view-icon-32\")\n            \n        default:\n            // you can alwayws fallback to default icons\n            return nil\n        }\n    }\n}\n```\n---\n\n#### `var elementColorProvider: Inspector.ElementColorProvider? { get }`\n\nReturn your own color scheme for the hierarchy label colors, instead of (or to extend) the default color scheme.\n\n```swift\n// Example\n\nvar elementColorProvider: Inspector.ElementColorProvider? {\n    .init { view in\n        switch view {\n        case is MyView:\n            return .systemPink\n            \n        default:\n            // you can alwayws fallback to default color scheme if needed\n            return nil\n        }\n    }\n}\n```\n---\n\n#### `var commandGroups: [Inspector.CommandGroup]? { get }`\n\nCommand groups appear as sections on the main `Inspector` UI and can have key command shortcuts associated with them, you can have as many groups, with as many commands as you want.\n\n```swift\n// Example\n\nvar commandGroups: [Inspector.CommandGroup]? {\n    guard let window = window else { return [] }\n    \n    [\n        .group(\n            commands: [\n                .command(\n                    title: \"Reset\",\n                    icon: .exampleCommandIcon,\n                    keyCommand: .control(.shift(.key(\"r\"))),\n                    closure: {\n                        // Instantiates a new initial view controller on a Storyboard application.\n                        let storyboard = UIStoryboard(name: \"Main\", bundle: nil)\n                        let vc = storyboard.instantiateInitialViewController()\n\n                        // set new instance as the root view controller\n                        window.rootViewController = vc\n                        \n                        // restart inspector\n                        Inspector.restart()\n                    }\n                )\n            ]\n        )\n    ]\n}\n```\n\n---\n\n#### `var elementLibraries: [Inspector.ElementPanelType: [InspectorElementLibraryProtocol]] { get }`\n\nElement Libraries are entities that conform to `InspectorElementLibraryProtocol` and are each tied to a unique type. *Pro-tip: Use enumerations.*\n\n```swift \n// Example\n\nvar elementLibraries: [Inspector.ElementPanelType: [InspectorElementLibraryProtocol]] {\n    [.attributes: ExampleElementLibrary.allCases]\n}\n```\n\n```swift \n// Element Library Example\n\nimport UIKit\nimport Inspector\n\nenum ExampleAttributesLibrary: InspectorElementLibraryProtocol, CaseIterable {\n    case roundedButton\n\n    var targetClass: AnyClass {\n        switch self {\n        case .roundedButton:\n            return RoundedButton.self\n        }\n    }\n\n    func sections(for object: NSObject) -\u003e InspectorElementSections {\n        switch self {\n        case .roundedButton:\n            return .init(with: RoundedButtonAttributesSectionDataSource(with: object))\n        }\n    }\n}\n```\n\n```swift\n// Element Section Data Source\n\n#if DEBUG\nimport UIKit\nimport Inspector\n\nfinal class RoundedButtonAttributesSectionDataSource: InspectorElementSectionDataSource {\n    var state: InspectorElementSectionState = .collapsed\n\n    var title: String = \"Rounded Button\"\n\n    let roundedButton: RoundedButton\n\n    init?(with object: NSObject) {\n        guard let roundedButton = object as? RoundedButton else {\n            return nil\n        }\n        self.roundedButton = roundedButton\n    }\n\n    enum Properties: String, CaseIterable {\n        case animateOnTouch = \"Animate On Touch\"\n        case cornerRadius = \"Round Corners\"\n        case backgroundColor = \"Background Color\"\n    }\n\n    var properties: [InspectorElementProperty] {\n        Properties.allCases.map { property in\n            switch property {\n            case .animateOnTouch:\n                return .switch(\n                    title: property.rawValue,\n                    isOn: { self.roundedButton.animateOnTouch }\n                ) { animateOnTouch in\n                    self.roundedButton.animateOnTouch = animateOnTouch\n                }\n\n            case .cornerRadius:\n                return .switch(\n                    title: property.rawValue,\n                    isOn: { self.roundedButton.roundCorners }\n                ) { roundCorners in\n                    self.roundedButton.roundCorners = roundCorners\n                }\n\n            case .backgroundColor:\n                return .colorPicker(\n                    title: property.rawValue,\n                    color: { self.roundedButton.backgroundColor }\n                ) { newBackgroundColor in\n                    self.roundedButton.backgroundColor = newBackgroundColor\n                }\n            }\n        }\n    }\n}\n\n#endif\n```\n---\n\n## Donate\nYou can support development with PayPal.\n\n[![](https://www.paypalobjects.com/en_US/DK/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/donate?hosted_button_id=LJU86LQ4NUYGN) \n\n---\n\n## Credits\n\n`Inspector` is owned and maintained by [Pedro Almeida](https://pedro.am). You can follow him on Twitter at [@ipedro](https://twitter.com/ipedro) for project updates and releases.\n\n---\n\n## License\n\n`Inspector` is released under the MIT license. [See LICENSE](https://github.com/ipedro/Inspector/blob/master/LICENSE) for details.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fipedro%2Finspector","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fipedro%2Finspector","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fipedro%2Finspector/lists"}