Skip to content

Commit

Permalink
Merge pull request #1 from allaboutapps/feature/debug-view-package
Browse files Browse the repository at this point in the history
Feature/debug view package
  • Loading branch information
mpoimer authored Jan 18, 2024
2 parents 9936a44 + 1b0bfb8 commit bb05250
Show file tree
Hide file tree
Showing 22 changed files with 815 additions and 1 deletion.
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.DS_Store
/.build
/Packages
xcuserdata/
DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc

Package.resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
25 changes: 25 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "DebugView",
defaultLocalization: "en",
platforms: [.iOS(.v16)],
products: [
.library(
name: "DebugView",
targets: ["DebugView"]
),
],
targets: [
.target(
name: "DebugView"
),
.testTarget(
name: "DebugViewTests",
dependencies: ["DebugView"]
),
]
)
174 changes: 173 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,173 @@
# debugview-ios
# Debug View iOS

The Debug Package lets you easily add values meant for debugging purposes and showing them anywhere in your app.

## Getting Started

`DebugController` is used to add and store values meant for debugging purposes and `DebugScreen` (SwiftUI) or `DebugCorrdinator` (if you are using UIKit based apps with the Coordinator pattern) is used to display them.

## Creating Items
### Values

To create static or dynamic values (either `String` or your own Type which conforms to `CustomDebugStringConvertible` or `CustomStringConvertible`), use either of the initializers of the `DebugValue` struct.

#### Static Values

```swift
DebugValue(
id: "staticValue",
label: "Static Value",
staticValue: "Your static value"
)
```
> ###### Notes
> Static values are evaluated when the `DebugValue` is instantiated.
#### Dynamic Values

```swift
DebugValue(
id: "dynamicValueAutoClosure",
label: "Dynamic Value Auto Closure",
value: String(Int.random(in: 1 ... 6))
)
```

or:

```swift
DebugValue(
id: "dynamicValueClosure",
label: "Dynamic Value Closure",
value: {
String(Int.random(in: 1 ... 6))
}
)
```

> ###### Notes
> Dynamic values are wrapped in a closure with `@autoclosure` or your own closure, and evaluated when the `DebugScreen` is shown. Every time the `DebugScreen` is shown again, values are re-evaluated.
### Buttons

To create buttons which perform a custom action, use the `DebugButton` struct.

```swift
DebugButton(
id: "button",
label: "The button label",
action: {
// your custom action
}
)
```

#### Notes
> Make sure that your `DebugValue` or `DebugButton`s `id`s are unique, as they are used for diffing and identifying the item.
## Sections

Items are grouped by sections, making the information easier to read. Items are added to sections when inserting them into the `DebugController`.

```swift
DebugSection(
id: "app",
label: "App"
)
```

> ###### Notes
> Make sure that your `id`s are unique, as they are used for diffing and identifying the section.
## Adding Items

Add items to the `DebugController` by calling either of these functions:

- `addButton(_ button: DebugButton, toSection section: DebugSection)`
- `addValue(_ value: DebugValue, toSection section: DebugSection)`

> ###### Notes
> - Adding item with the same `id` twice will override the item in the `DebugController`s store.
> - Only items added with any of the above-mentioned methods are shown in the `DebugScreen`.
> - Sections and Values are ordered by time of insert. This means they will appear in the order they were added to the `DebugController`.
## Showing the Debug Screen

For SwiftUI based apps:

```swift
DebugScreen(
controller: debugController,
appearance: debugAppearance
)

```

For UIKit based apps, wrap the `DebugScreen` in a `UIHostingController`.

### Debug Screen Appearance
The `DebugScreen`s appearance can be configured via the `DebugScreenAppearance` struct.

```DebugScreenAppearance(tintColor: .red)```

## Defaults

The following `DebugValue`s exist by default:

```swift
public extension DebugValue {
static let appVersion = DebugValue(
id: "appVersion",
label: "Version",
value: Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
)

static let appBuildNumber = DebugValue(
id: "appBuildNumber",
label: "Build Number",
value: Bundle.main.infoDictionary?["CFBundleVersion"] as? String
)

static let appBundleId = DebugValue(
id: "bundleId",
label: "Bundle Identifier",
value: Bundle.main.bundleIdentifier
)

static let userLocale = DebugValue(
id: "locale",
label: "Locale",
value: Locale.autoupdatingCurrent.identifier
)

static let deviceOSVersion = DebugValue(
id: "deviceOSVersion",
label: "OS Version",
value: "\(UIDevice.current.systemName) \(UIDevice.current.systemVersion)"
)

static let deviceOSModel = DebugValue(
id: "deviceOSModel",
label: "Model",
value: UIDevice.current.model
)

static let pushNotificationsRegistered = DebugValue(
id: "pushNotificationsRegistered",
label: "Push Notifications registered",
value: UIApplication.shared.isRegisteredForRemoteNotifications
)
}
```

The following `DebugSection`s exist by default:

```swift
public extension DebugSection {
static let app = DebugSection(id: "app", label: "App")
static let user = DebugSection(id: "user", label: "User")
static let device = DebugSection(id: "device", label: "Device")
static let pushNotifications = DebugSection(id: "pushNotifications", label: "Push Notifications")
}

```
45 changes: 45 additions & 0 deletions Sources/DebugView/DebugController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import Foundation

/// A controller that handles debug items for the Debug feature.
public class DebugController {
// MARK: - Interface

public init() {}

/// Adds a button to the debug store at the corresponding section
public func addButton(_ button: DebugButton, toSection section: DebugSection) {
add(item: .button(button), toSection: section)
}

/// Adds a value to the debug store at the corresponding section
public func addValue(_ value: DebugValue, toSection section: DebugSection) {
add(item: .value(value), toSection: section)
}

// MARK: - Private

private func add(item: DebugItem, toSection section: DebugSection) {
if !containers.contains(where: { $0.section.id == section.id }) {
containers.append(.init(section: section))
}

guard let container = containers.first(where: { $0.section.id == section.id })
else { return }

// if the item already exists, remove it to override it
if container.items.contains(where: { $0.id == item.id }) {
container.items.removeAll(where: { $0.id == item.id })
}

container.items.append(item)
}

private var containers = [DebugContainer]()

// MARK: Helpers

/// Renders sections and values for the debug screen.
func renderDebugSections() -> [FinalDebugContainer] {
containers.map(FinalDebugContainer.init)
}
}
123 changes: 123 additions & 0 deletions Sources/DebugView/DebugScreen.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import Foundation
import SwiftUI

public struct DebugScreen: View {
// MARK: Init

private let sections: [FinalDebugContainer]
private let appearance: DebugScreenAppearance

public init(
controller: DebugController,
appearance: DebugScreenAppearance
) {
self.sections = controller.renderDebugSections()
self.appearance = appearance
}

// MARK: Body

public var body: some View {
List {
ForEach(sections) { section in
Section {
ForEach(section.items) { item in
viewFor(item: item)
}
} header: {
sectionHeader(section: section)
}
}
shareAllButton
}
.tint(appearance.tintColor)
}

// MARK: Helpers

@ViewBuilder
private func viewFor(item: FinalDebugItem) -> some View {
switch item {
case .value(let value):
FinalDebugValueView(value: value)
case .button(let button):
Button(action: {
button.action()
}, label: {
Text(button.label)
})
}
}

private func sectionHeader(section: FinalDebugContainer) -> some View {
HStack(alignment: .center, spacing: .zero) {
Text(section.section.label)

Spacer()

ShareLink(
item: section.shareableText,
label: {
Image(systemName: "square.and.arrow.up")
}
)
}
}

private var shareAllButton: some View {
ShareLink(
item: sections.shareableText,
label: {
HStack {
Image(systemName: "square.and.arrow.up")
Text("Share All")
}
}
)
}
}

// MARK: - Previews

#Preview {
DebugScreen(
controller: DebugController.createTestController(),
appearance: DebugScreenAppearance(tintColor: .green)
)
}

private extension DebugController {
static func createTestController() -> DebugController {
let controller = DebugController()
controller.addValue(
DebugValue(
id: "version",
label: "Version",
staticValue: "1.0.0"
),
toSection: .app
)

controller.addValue(
DebugValue(
id: "diceRoll",
label: "Dice Rolled",
value: String(Int.random(in: 1 ... 6))
),
toSection: .app
)

controller.addButton(
DebugButton(
id: "button",
label: "Button",
action: {
print("Button pressed")
}
),
toSection: .user
)

return controller
}
}
Loading

0 comments on commit bb05250

Please sign in to comment.