• SwiftUI
  • iOS 14+ · tvOS · watchOS · visionOS
  • Zero dependencies

Every environment.
One preview.

One view modifier renders your SwiftUI view under every environment you care about, side by side and captioned: light and dark, right-to-left, the smallest and largest dynamic type, any locale, and 30 more environment values.

ProfileCard.swift
import SwiftUI
import EnvironmentVariationPreview

@available(iOS 15.0, *) // DynamicTypeSize
struct ProfileCard_Previews: PreviewProvider {
  static var previews: some View {
    ProfileCard()
      .environmentVariations([
        .colorScheme(.light),
        .colorScheme(.dark),
        .layoutDirection(.rightToLeft),
        .dynamicTypeSize(DynamicTypeSize.xSmall),
        .dynamicTypeSize(DynamicTypeSize.xxxLarge),
        .locale(Locale(identifier: "fr_FR")),
      ])
  }
}

The problem

Your view works. In one environment.

Each preview shows one combination of settings. The bugs live in the others: the label that truncates at the largest text size, the chevron that points the wrong way right-to-left, the gray-on-black that vanishes in dark mode.

12

Dynamic type sizes

From xSmall to accessibility5. Large text is where layouts break, and it is the setting you are least likely to have on.

2 × 2

Schemes and directions

Light and dark, left-to-right and right-to-left. A preview per combination means copying the same view four times.

40

Environment values

Locale, size classes, legibility weight, redaction, line limits, symbol variants… each one a .environment(\.key, value) you have to remember.

How it works

One modifier. One list. Captioned renders.

  1. List the environments

    An array literal of EnvironmentValueItem cases: .colorScheme(.dark), .locale(…), .horizontalSizeClass(.compact).

  2. Apply .environmentVariations

    On the view in your preview. With no argument it shows light and dark.

  3. Read the canvas

    One render per value, each with a black caption naming the case and its value, on a background that follows its color scheme. Ready for a snapshot test, too.

Defaults, alignment and spacing
// Light and dark, the default.
ProfileCard().environmentVariations()

// Leading-aligned captions, 16 points between view and caption.
ProfileCard().environmentVariations(
  [.horizontalSizeClass(.compact), .horizontalSizeClass(.regular)],
  .leading,
  16
)

// Values whose type is newer than iOS 14 take the type's name.
ProfileCard().environmentVariations([
  .dynamicTypeSize(DynamicTypeSize.accessibility5),
  .legibilityWeight(.bold),
  .redactionReasons(.placeholder),
])

Why this library

Small, safe to depend on.

Nothing ships

Every file is wrapped in #if targetEnvironment(simulator). Device and Mac builds compile an empty module, so linking it from a UI package costs your App Store binary nothing.

Zero dependencies

Foundation, SwiftUI and CoreData (for managedObjectContext). Nothing else to resolve, audit or update.

iOS 14 and up

Values newer than the floor, like DynamicTypeSize or Material, apply under #available and fall back to the unchanged view on older systems.

Captions you can trust

Each render is labelled with its case and value, so screenshots and snapshot diffs say which environment broke.

Every Apple simulator

CI builds for iOS, tvOS, watchOS and visionOS simulators and runs the tests on an iPhone simulator.

Checked conventions

The library's rules (simulator guard, imports, availability, captions) run as swift-llm-check tests. Read them.

Supported values

40 environment values, one enum.

Each is a case of EnvironmentValueItem named after its EnvironmentValues key.

Appearance

  • colorScheme
  • backgroundMaterial
  • backgroundStyle
  • displayScale
  • imageScale
  • symbolVariants
  • symbolRenderingMode
  • isLuminanceReduced
  • redactionReasons

Text

  • dynamicTypeSize
  • font
  • legibilityWeight
  • lineLimit
  • lineSpacing
  • minimumScaleFactor
  • multilineTextAlignment
  • truncationMode
  • allowsTightening
  • textCase

Locale and layout

  • locale
  • calendar
  • timeZone
  • layoutDirection
  • horizontalSizeClass
  • verticalSizeClass
  • defaultMinListRowHeight
  • defaultMinListHeaderHeight
  • headerProminence

Behaviour

  • isEnabled
  • accessibilityEnabled
  • isScrollEnabled
  • autocorrectionDisabled
  • disableAutocorrection
  • scenePhase
  • openURL
  • managedObjectContext
  • menuOrder
  • contentTransition
  • contentTransitionAddsDrawingGroup
  • scrollDismissesKeyboardMode

Install

Add it with Swift Package Manager.

Package.swift
dependencies: [
  .package(
    url: "https://github.com/mackoj/EnvironmentVariationPreview.git",
    from: "1.1.4"
  ),
],
targets: [
  .target(
    name: "MyUI",
    dependencies: ["EnvironmentVariationPreview"]
  ),
]

In Xcode

File › Add Package Dependencies…, paste https://github.com/mackoj/EnvironmentVariationPreview.git, and add the EnvironmentVariationPreview library to the target that holds your views.

Requirements

Swift 5.7.1 or later (Xcode 14.1+). iOS 14, macOS 11, tvOS 14, watchOS 7 or visionOS, running in a simulator or the preview canvas.

Simulator only, on purpose. Outside the simulator the module is empty, so a call to .environmentVariations belongs in a preview or a test, which only build for the simulator. Wrap any other use in #if targetEnvironment(simulator).