Skip to content

The Redacted Modifier (SwiftUI)

A SwiftUI guide for coding agents. Also covers skeleton loading view, redacted placeholder, loading state shimmer, unredacted, redactionreasons environment, hide sensitive data widget.

.redacted(reason: .placeholder) turns a view hierarchy into a skeleton view: text and images get grey overlays while the layout keeps its shape. Use it for loading states and for hiding sensitive data, as widgets do on the lock screen.

Skeleton Loading State

Seed the store with mock rows, render them redacted while loading, and the skeleton matches the real layout exactly because it is the real layout.

final class Store: ObservableObject {
  @Published private(set) var repos: [Repo]
  @Published private(set) var isLoading = false

  init(service: GithubService,
       initialState: [Repo] = Array(repeating: .mock, count: 5)) {
    self.repos = initialState
    self.service = service
  }
}

List(store.repos, id: \.self) { repo in
  RepoView(repo: repo)
}
.onAppear(perform: store.fetch)
.redacted(reason: store.isLoading ? .placeholder : [])

Pass [] as the reason to switch redaction off; RedactionReasons is an option set.

Keep Parts Visible

.unredacted() on a child exempts it from the effect, so a static icon can stay visible while the data around it is redacted.

Image(systemName: "star.fill")
  .unredacted()

Custom Reasons

Extend the option set and read the redactionReasons environment value to redact only some content kinds. SwiftUI draws the skeleton effect only for .placeholder; a custom reason is just a signal you must handle yourself.

extension RedactionReasons {
  static let text = RedactionReasons(rawValue: 1 << 2)
  static let images = RedactionReasons(rawValue: 1 << 4)
}

extension View {
  @ViewBuilder func unredacted(when condition: Bool) -> some View {
    if condition {
      unredacted()
    } else {
      redacted(reason: .placeholder)
    }
  }
}

struct RepoView: View {
  @Environment(\.redactionReasons) var reasons
  let repo: Repo

  var body: some View {
    Text(String(repo.stars))
      .unredacted(when: !reasons.contains(.text))
  }
}

Gotchas

  • Redaction is visual only. A redacted Button still receives taps, so disable interactive controls yourself while the placeholder shows.
  • Only the .placeholder reason draws the built-in skeleton; other reasons render nothing until you implement an effect for them.

Use this guidance in your coding agent

Install the Better Design MCP once. Your agent then loads this page with one call.

get-swiftui-guide({ topic: "swiftui-the-magic-of-redacted-modifier" })
claude mcp add --scope user better-design --transport http https://better-design.com/api/mcp --header "Authorization: Bearer <YOUR_API_KEY>"
Browse related design systems