Skip to content

nthState/DesignReviewToolkit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

44 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

DesignReviewToolkit

๐Ÿ“š Table of Contents

Overview

DesignReviewToolkit takes a SwiftUI View, then generates an image containing it's Style & Accessibility modifiers. This allows you to see everything all at once. You can identify areas where you are missing Accessibility. Image generation is deterministic, so if the underlying style/accessibility data changes, the image will change.

Original SwiftUI View Generated (Style+Accessibility) Generated (Style Only) Generated (Accessibility Only) Generated (Size Only)

Accessibility

We suport modifiers like sortPriority, label, hidden etc. The sortPriority modifier for example has a ID rendered over the top of the view, Hidden renders diagonal red lines.

Style

We support basic style modifiers like, background, foreground style etc. The frame modifier for example lets us render a size and spacing lines over the top of the image.

Quick Start

Follow the steps to generate your first image.

  1. Add the package
  2. Add a test
  3. Run the PreProcess Plugin supplied by the package
  4. Run your Unit Test (Add breakpoints etc to view the generated image, or write it to disk)
  5. Run the Clean Plugin to tidy up modifcations to your source code

Add the Package

Add the package to your Xcode Project or Swift Package

.package(url: "https://github.com/nthState/DesignReviewToolkit", exact: "0.0.1")

Add a Unit Test

Add a Unit Test for the View you wish to capture

  import DesignReviewToolkit

  @Test() func `ContentView has accessibility annotations`() async throws {

    let view = ContentView()
    
    let generator = Generator()
    let image = try await generator.generate(from: view)

    #expect(image.width > 0 && image.height > 0)
  }

Note: Add the following expression to the debugger to see the image inside Xcode

UIImage(cgImage: image)

Image Diff

The Generator creates deterministic images, which means you can diff them, the below code is an example of how one might do it.

  @Test() func `ContentView View is the same as a version saved to disk`() async throws {
    
    let view = ContentView()
      .environment(\.colorScheme, .dark)
    
    let generator = Generator()
    let image = try await generator.generate(from: view)
    
    let testingRoot = URL(fileURLWithPath: "/Users/chrisdavis/Developer/DesignReviewToolkitSample/DesignFilesOutput")
    llet comparisonImageURL = testingRoot.appending(path: "ContentView.png")
    
    let imagesMatch = try await generator.hasMatchingData(image, to: comparisonImageURL)
    
    #expect(imagesMatch)
  }

Run the Command in Xcode

Then it's a quick three step process:

  1. Right click on your project and select PreProcess under DesignReviewToolkit
  2. Run your tests (CMD+U)
  3. Right click on the project and select Clean under DesignReviewToolkit

Skipping files

If you want a particular file to be skipped and not pre-processed, you can add this comment in your file.

// designreviewtoolkit:skip

Configuration

You can pass a configuration file to the Generator and CLI.

We try and search for a file called .designreviewtoolkit.json in your root, but you can pass in any file you wish.

The general structure looks like this, all parameters are optional.

{
  "version": "0.0.1",
  "views": [
    "MyCustomSwiftUIView"
  ],
  "spacing": {
    "horizontal": 64,
    "vertical": 48
  },
  "includeAccessibility": [
    "accessibilitySortPriority",
    "accessibilityHidden"
  ],
  "includeStyle": [
    "frame",
    "background"
  ],
  "showStyle": true,
  "showAccessiility": true,
  "author": "nthState Ltd",
  "convergeLines": false
}
  • version: The version of the configuration file
  • views: If you have a custom SwiftUI view that you add accessibility/style onto, add them here. By default we scan for default views like VStack, HStack etc
  • spacing: Horizontal and Vertical space between the app and the callouts
  • includeAccessibility: You may only want certain accessibility items drawn, otherwise show all
  • showAccessibility: If you want accessibility callouts drawn
  • includeStyle: You may only want certain style items drawn, otherwise show all
  • showStyle: If you want style callouts drawn
  • author: The name to show in the footer
  • convergeLines: Do lines converge on a mid-point, or spread so they are clearer

Continuous Design Review

You may want to let CI handle the generation of the images, if so, here's a rough outline of a GitHub Action to do it for you.

jobs:
  designreview:
    name: Design Review
    timeout-minutes: 5
    steps:

      - name: Checkout
        uses: actions/checkout@v6

      - name: Pre process files
        run: |
            swift run --package-path DesignReviewToolkit/Source/DesignReviewToolkitCLI DesignReviewToolkitCLI pre-process files "DesignReviewToolkitSample/DemoView1.swift"
        
      - name: Generate Screenshots
        run: |
            xcodebuild clean test -project "DesignReviewKitSample.xcodeproj" -scheme "DesignReviewKitSample" -destination "platform=iOS Simulator,name=iPhone 17 Pro Max,OS=latest"
          
      - name: Clean up
        run: |
            swift run --package-path DesignReviewToolkit/Source/DesignReviewToolkitCLI DesignReviewToolkitCLI clean files "DesignReviewToolkitSample/DemoView1.swift"

I don't like the design, can I change it?

Yes, You can pass in your own custom SwiftUI views into the generator.

Generator(configuration: Configuration) { String, Configuration in
  Text("My Custom Title")
} footer: { Configuration in
  Text("My Custom Footer")
} callout: { ItemData, DataType, Configuration in
  Text("My Custom CallOut")
} hidden: { ItemData, Configuration in
  Text("My Custom Hidden Overlay")
} highlight: { ItemData, Configuration in
  Text("My Custom Highlight")
} sortPriority: { Int, Configuration in
  Text("My Custom Sort Priority View")
} lines: { [Line], Configuration in
  Text("My Custom Connection Lines")
} wrapper: { content in
  content
}

Ideal Solution

At the moment, you have to do some pre-processing and cleanup, ideally, this would be a button in Xcode Previews that you could toggle - a one-click process

Limitations

Currently ImageRenderer fails to render NavigationView & TabView - possibly others, the workaround is to Generate an image of those views contents instead.

Ackowledgements

Thanks to Alasdair to alerting me to the European Accessibility Act 2025 Without you, I wouldn't have been frustrated enough to make this tool.

About me

I'm Chris Davis, I love writing software. Feel free to contact me

About

Create screenshots for Design Reviews

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages