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.
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
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.
From xSmall to accessibility5. Large text is where layouts break, and it is the setting you are least likely to have on.
Light and dark, left-to-right and right-to-left. A preview per combination means copying the same view four times.
Locale, size classes, legibility weight, redaction, line limits, symbol variants… each one a .environment(\.key, value) you have to remember.
How it works
An array literal of EnvironmentValueItem cases: .colorScheme(.dark), .locale(…), .horizontalSizeClass(.compact).
.environmentVariationsOn the view in your preview. With no argument it shows light and dark.
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.
// 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
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.
Foundation, SwiftUI and CoreData (for managedObjectContext). Nothing else to resolve, audit or update.
Values newer than the floor, like DynamicTypeSize or Material, apply under #available and fall back to the unchanged view on older systems.
Each render is labelled with its case and value, so screenshots and snapshot diffs say which environment broke.
CI builds for iOS, tvOS, watchOS and visionOS simulators and runs the tests on an iPhone simulator.
The library's rules (simulator guard, imports, availability, captions) run as swift-llm-check tests. Read them.
Supported values
Each is a case of EnvironmentValueItem named after its EnvironmentValues key.
colorSchemebackgroundMaterialbackgroundStyledisplayScaleimageScalesymbolVariantssymbolRenderingModeisLuminanceReducedredactionReasonsdynamicTypeSizefontlegibilityWeightlineLimitlineSpacingminimumScaleFactormultilineTextAlignmenttruncationModeallowsTighteningtextCaselocalecalendartimeZonelayoutDirectionhorizontalSizeClassverticalSizeClassdefaultMinListRowHeightdefaultMinListHeaderHeightheaderProminenceisEnabledaccessibilityEnabledisScrollEnabledautocorrectionDisableddisableAutocorrectionscenePhaseopenURLmanagedObjectContextmenuOrdercontentTransitioncontentTransitionAddsDrawingGroupscrollDismissesKeyboardModeInstall
dependencies: [ .package( url: "https://github.com/mackoj/EnvironmentVariationPreview.git", from: "1.1.4" ), ], targets: [ .target( name: "MyUI", dependencies: ["EnvironmentVariationPreview"] ), ]
File › Add Package Dependencies…, paste https://github.com/mackoj/EnvironmentVariationPreview.git, and add the EnvironmentVariationPreview library to the target that holds your views.
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).