├── Sources ├── Snapshotting │ ├── include │ │ └── .gitignore │ └── Initializer.m ├── SnapshottingTestsObjc │ ├── include │ │ └── .gitignore │ └── EMGInvocationCreator.mm ├── PreviewGallery │ ├── Array+Filter.swift │ ├── Preview+FullScreen.swift │ ├── TitleSubtitleRow.swift │ ├── Colors.swift │ ├── PreviewsDetail.swift │ ├── PreviewCell.swift │ ├── ModuleScreens.swift │ ├── Checkerboard.swift │ ├── ModulePreviews.swift │ ├── PreviewGallery.swift │ └── PreviewData.swift ├── SnapshotPreviewsCore │ ├── UIImage+EMG.swift │ ├── NSImage+EMG.swift │ ├── Orientation.swift │ ├── PreferredColorSchemeWrapper.swift │ ├── UIViewWrapper.swift │ ├── ViewSelector.swift │ ├── RenderingStrategy.swift │ ├── SwiftUIRenderingStrategy.swift │ ├── String+Split.swift │ ├── ModifierFinder.swift │ ├── MetadataTypes.swift │ ├── ScrollExpansion.swift │ ├── ExpandingViewController.swift │ └── UIKitRenderingStrategy.swift ├── SnapshottingSwift │ └── Initializer.swift ├── SnapshottingTests │ ├── DiscoveredPreview+PreviewType.swift │ ├── PreviewFilters.swift │ └── PreviewLayoutTest.swift ├── SnapshotSharedModels │ └── RenderingMode.swift └── SnapshotPreferences │ ├── ExpansionPreference.swift │ ├── PrecisionPreference.swift │ ├── AppStoreSnapshotPreference.swift │ ├── AccessibiltyPreference.swift │ ├── RenderingModePreference.swift │ └── EmergeModifierFinder.swift ├── images ├── image1.png ├── image2.png └── testOutput.png ├── PreviewsSupport ├── PreviewsSupport.xcframework │ ├── macos-arm64_x86_64 │ │ └── PreviewsSupport.framework │ │ │ ├── Versions │ │ │ ├── Current │ │ │ └── A │ │ │ │ ├── PreviewsSupport │ │ │ │ ├── Modules │ │ │ │ ├── module.modulemap │ │ │ │ └── PreviewsSupport.swiftmodule │ │ │ │ │ ├── arm64-apple-macos.swiftdoc │ │ │ │ │ ├── x86_64-apple-macos.swiftdoc │ │ │ │ │ ├── arm64-apple-macos.swiftinterface │ │ │ │ │ └── x86_64-apple-macos.swiftinterface │ │ │ │ ├── Headers │ │ │ │ └── PreviewsSupport.h │ │ │ │ └── Resources │ │ │ │ └── Info.plist │ │ │ ├── Headers │ │ │ ├── Modules │ │ │ ├── Resources │ │ │ └── PreviewsSupport │ ├── ios-arm64_x86_64-maccatalyst │ │ └── PreviewsSupport.framework │ │ │ ├── Versions │ │ │ ├── Current │ │ │ └── A │ │ │ │ ├── PreviewsSupport │ │ │ │ ├── Modules │ │ │ │ ├── module.modulemap │ │ │ │ └── PreviewsSupport.swiftmodule │ │ │ │ │ ├── arm64-apple-ios-macabi.swiftdoc │ │ │ │ │ ├── x86_64-apple-ios-macabi.swiftdoc │ │ │ │ │ ├── arm64-apple-ios-macabi.swiftinterface │ │ │ │ │ └── x86_64-apple-ios-macabi.swiftinterface │ │ │ │ ├── Headers │ │ │ │ └── PreviewsSupport.h │ │ │ │ └── Resources │ │ │ │ └── Info.plist │ │ │ ├── Headers │ │ │ ├── Modules │ │ │ ├── Resources │ │ │ └── PreviewsSupport │ ├── ios-arm64 │ │ └── PreviewsSupport.framework │ │ │ ├── Info.plist │ │ │ ├── PreviewsSupport │ │ │ ├── Modules │ │ │ ├── PreviewsSupport.swiftmodule │ │ │ │ ├── arm64-apple-ios.swiftdoc │ │ │ │ └── arm64-apple-ios.swiftinterface │ │ │ └── module.modulemap │ │ │ └── Headers │ │ │ └── PreviewsSupport.h │ ├── tvos-arm64 │ │ └── PreviewsSupport.framework │ │ │ ├── Info.plist │ │ │ ├── PreviewsSupport │ │ │ ├── Modules │ │ │ ├── PreviewsSupport.swiftmodule │ │ │ │ ├── arm64-apple-tvos.swiftdoc │ │ │ │ └── arm64-apple-tvos.swiftinterface │ │ │ └── module.modulemap │ │ │ └── Headers │ │ │ └── PreviewsSupport.h │ ├── xros-arm64 │ │ └── PreviewsSupport.framework │ │ │ ├── Info.plist │ │ │ ├── PreviewsSupport │ │ │ ├── Modules │ │ │ ├── PreviewsSupport.swiftmodule │ │ │ │ ├── arm64-apple-xros.swiftdoc │ │ │ │ └── arm64-apple-xros.swiftinterface │ │ │ └── module.modulemap │ │ │ └── Headers │ │ │ └── PreviewsSupport.h │ ├── ios-arm64_x86_64-simulator │ │ └── PreviewsSupport.framework │ │ │ ├── Info.plist │ │ │ ├── PreviewsSupport │ │ │ ├── Modules │ │ │ ├── module.modulemap │ │ │ └── PreviewsSupport.swiftmodule │ │ │ │ ├── arm64-apple-ios-simulator.swiftdoc │ │ │ │ ├── x86_64-apple-ios-simulator.swiftdoc │ │ │ │ ├── arm64-apple-ios-simulator.swiftinterface │ │ │ │ └── x86_64-apple-ios-simulator.swiftinterface │ │ │ └── Headers │ │ │ └── PreviewsSupport.h │ ├── tvos-arm64_x86_64-simulator │ │ └── PreviewsSupport.framework │ │ │ ├── Info.plist │ │ │ ├── PreviewsSupport │ │ │ ├── Modules │ │ │ ├── module.modulemap │ │ │ └── PreviewsSupport.swiftmodule │ │ │ │ ├── arm64-apple-tvos-simulator.swiftdoc │ │ │ │ ├── x86_64-apple-tvos-simulator.swiftdoc │ │ │ │ ├── arm64-apple-tvos-simulator.swiftinterface │ │ │ │ └── x86_64-apple-tvos-simulator.swiftinterface │ │ │ └── Headers │ │ │ └── PreviewsSupport.h │ ├── xros-arm64_x86_64-simulator │ │ └── PreviewsSupport.framework │ │ │ ├── Info.plist │ │ │ ├── PreviewsSupport │ │ │ ├── Modules │ │ │ ├── module.modulemap │ │ │ └── PreviewsSupport.swiftmodule │ │ │ │ ├── arm64-apple-xros-simulator.swiftdoc │ │ │ │ ├── x86_64-apple-xros-simulator.swiftdoc │ │ │ │ ├── arm64-apple-xros-simulator.swiftinterface │ │ │ │ └── x86_64-apple-xros-simulator.swiftinterface │ │ │ └── Headers │ │ │ └── PreviewsSupport.h │ ├── watchos-arm64_arm64_32_armv7k │ │ └── PreviewsSupport.framework │ │ │ ├── Info.plist │ │ │ ├── PreviewsSupport │ │ │ ├── Modules │ │ │ ├── module.modulemap │ │ │ └── PreviewsSupport.swiftmodule │ │ │ │ ├── arm64-apple-watchos.swiftdoc │ │ │ │ ├── arm64_32-apple-watchos.swiftdoc │ │ │ │ ├── armv7k-apple-watchos.swiftdoc │ │ │ │ ├── arm64-apple-watchos.swiftinterface │ │ │ │ ├── armv7k-apple-watchos.swiftinterface │ │ │ │ └── arm64_32-apple-watchos.swiftinterface │ │ │ └── Headers │ │ │ └── PreviewsSupport.h │ └── watchos-arm64_x86_64-simulator │ │ └── PreviewsSupport.framework │ │ ├── Info.plist │ │ ├── PreviewsSupport │ │ ├── Modules │ │ ├── module.modulemap │ │ └── PreviewsSupport.swiftmodule │ │ │ ├── arm64-apple-watchos-simulator.swiftdoc │ │ │ ├── x86_64-apple-watchos-simulator.swiftdoc │ │ │ ├── arm64-apple-watchos-simulator.swiftinterface │ │ │ └── x86_64-apple-watchos-simulator.swiftinterface │ │ └── Headers │ │ └── PreviewsSupport.h ├── PreviewsSupport.xcodeproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── PreviewsSupport │ ├── PreviewsSupport.h │ └── PreviewsSupport.swift ├── README.md ├── scripts │ └── update_framework_interface.rb └── build.sh ├── Examples ├── DemoApp │ ├── DemoApp │ │ ├── Assets.xcassets │ │ │ ├── Contents.json │ │ │ ├── product-image.imageset │ │ │ │ ├── blog15.jpg │ │ │ │ └── Contents.json │ │ │ ├── AppIcon.appiconset │ │ │ │ ├── Emerge-Avatar Optimized.heic │ │ │ │ ├── Emerge-Avatar Optimized 1.heic │ │ │ │ ├── Emerge-Avatar Optimized 10.heic │ │ │ │ ├── Emerge-Avatar Optimized 2.heic │ │ │ │ ├── Emerge-Avatar Optimized 3.heic │ │ │ │ ├── Emerge-Avatar Optimized 4.heic │ │ │ │ ├── Emerge-Avatar Optimized 5.heic │ │ │ │ ├── Emerge-Avatar Optimized 6.heic │ │ │ │ ├── Emerge-Avatar Optimized 7.heic │ │ │ │ ├── Emerge-Avatar Optimized 8.heic │ │ │ │ ├── Emerge-Avatar Optimized 9.heic │ │ │ │ └── Contents.json │ │ │ └── AccentColor.colorset │ │ │ │ └── Contents.json │ │ ├── Preview Content │ │ │ └── Preview Assets.xcassets │ │ │ │ └── Contents.json │ │ ├── DemoApp.entitlements │ │ ├── DemoAppApp.swift │ │ ├── EMGTestHandler.swift │ │ ├── TestViews │ │ │ ├── AnyViewPreview.swift │ │ │ ├── TextView.swift │ │ │ ├── EmptyView.swift │ │ │ ├── ArrayBuilder.swift │ │ │ ├── StateView.swift │ │ │ ├── UIViewPreviews.swift │ │ │ ├── CodeEntryView.swift │ │ │ ├── OSVersionView.swift │ │ │ ├── ExpandingView.swift │ │ │ ├── RideOptionsView.swift │ │ │ ├── RideShareButton.swift │ │ │ └── PreviewVariants.swift │ │ └── ContentView.swift │ ├── Demo Watch App │ │ ├── Assets.xcassets │ │ │ ├── Contents.json │ │ │ ├── AccentColor.colorset │ │ │ │ └── Contents.json │ │ │ └── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ ├── Preview Content │ │ │ └── Preview Assets.xcassets │ │ │ │ └── Contents.json │ │ ├── DemoApp.swift │ │ └── ContentView.swift │ ├── DemoApp.xcodeproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ ├── DemoAppTests.xcscheme │ │ │ └── DemoApp.xcscheme │ ├── Demo Watch AppTests │ │ ├── DemoWatchPreviewTest.swift │ │ └── DemoWatchSnapshotTest.swift │ ├── DemoModule │ │ ├── RatingPreviews.swift │ │ ├── DateView.swift │ │ ├── DemoModule.h │ │ ├── Color.swift │ │ ├── MessageView.swift │ │ ├── ButtonView.swift │ │ ├── FeatureCardView.swift │ │ ├── ProductCard.swift │ │ ├── TripCardView.swift │ │ ├── MessageRow.swift │ │ ├── InfoCardView.swift │ │ ├── HomeMapView.swift │ │ ├── RowView.swift │ │ ├── ConversationMessageView.swift │ │ └── RatingView.swift │ ├── DemoWatchTests │ │ └── DemoWatchAccessibilityPreviewTest.swift │ ├── DemoAppTests │ │ ├── DemoAppPreviewTest.swift │ │ └── DemoAppSnapshotTest.swift │ ├── DemoApp.xctestplan │ ├── DemoAppTests.xctestplan │ └── DemoAppUITests │ │ └── DemoAppAccessibilityPreviewTest.swift └── UnitTestMigration │ ├── UnitTestMigration │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ ├── UnitTestMigrationApp.swift │ ├── ExampleSnapshotTest_Migrated.swift │ ├── ContentView.swift │ └── SnapshotTest.swift │ ├── UnitTestMigration.xcodeproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata │ ├── UnitTestMigrationTests │ └── ExampleSnapshotTest.swift │ └── README.md ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── .spi.yml ├── .gitignore ├── .github └── workflows │ ├── build-xcframework.yml │ └── release.yaml ├── Tests └── SnapshotPreviewsTests │ └── ETBrowserTests.swift ├── LICENSE └── Package.swift /Sources/Snapshotting/include/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /images/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/images/image1.png -------------------------------------------------------------------------------- /images/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/images/image2.png -------------------------------------------------------------------------------- /Sources/SnapshottingTestsObjc/include/.gitignore: -------------------------------------------------------------------------------- 1 | # This file is just so git sees the empty directory -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/macos-arm64_x86_64/PreviewsSupport.framework/Versions/Current: -------------------------------------------------------------------------------- 1 | A -------------------------------------------------------------------------------- /images/testOutput.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/images/testOutput.png -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/ios-arm64_x86_64-maccatalyst/PreviewsSupport.framework/Versions/Current: -------------------------------------------------------------------------------- 1 | A -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/macos-arm64_x86_64/PreviewsSupport.framework/Headers: -------------------------------------------------------------------------------- 1 | Versions/Current/Headers -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/macos-arm64_x86_64/PreviewsSupport.framework/Modules: -------------------------------------------------------------------------------- 1 | Versions/Current/Modules -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/macos-arm64_x86_64/PreviewsSupport.framework/Resources: -------------------------------------------------------------------------------- 1 | Versions/Current/Resources -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/ios-arm64_x86_64-maccatalyst/PreviewsSupport.framework/Headers: -------------------------------------------------------------------------------- 1 | Versions/Current/Headers -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/ios-arm64_x86_64-maccatalyst/PreviewsSupport.framework/Modules: -------------------------------------------------------------------------------- 1 | Versions/Current/Modules -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/ios-arm64_x86_64-maccatalyst/PreviewsSupport.framework/Resources: -------------------------------------------------------------------------------- 1 | Versions/Current/Resources -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/macos-arm64_x86_64/PreviewsSupport.framework/PreviewsSupport: -------------------------------------------------------------------------------- 1 | Versions/Current/PreviewsSupport -------------------------------------------------------------------------------- /Examples/DemoApp/DemoApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/DemoApp/Demo Watch App/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/ios-arm64_x86_64-maccatalyst/PreviewsSupport.framework/PreviewsSupport: -------------------------------------------------------------------------------- 1 | Versions/Current/PreviewsSupport -------------------------------------------------------------------------------- /Examples/UnitTestMigration/UnitTestMigration/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/DemoApp/DemoApp/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/DemoApp/Demo Watch App/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/UnitTestMigration/UnitTestMigration/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/DemoApp/DemoApp/Assets.xcassets/product-image.imageset/blog15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/Examples/DemoApp/DemoApp/Assets.xcassets/product-image.imageset/blog15.jpg -------------------------------------------------------------------------------- /Examples/DemoApp/DemoApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Examples/DemoApp/DemoApp/Assets.xcassets/AppIcon.appiconset/Emerge-Avatar Optimized.heic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/Examples/DemoApp/DemoApp/Assets.xcassets/AppIcon.appiconset/Emerge-Avatar Optimized.heic -------------------------------------------------------------------------------- /Examples/DemoApp/DemoApp/Assets.xcassets/AppIcon.appiconset/Emerge-Avatar Optimized 1.heic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/Examples/DemoApp/DemoApp/Assets.xcassets/AppIcon.appiconset/Emerge-Avatar Optimized 1.heic -------------------------------------------------------------------------------- /Examples/DemoApp/DemoApp/Assets.xcassets/AppIcon.appiconset/Emerge-Avatar Optimized 10.heic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/Examples/DemoApp/DemoApp/Assets.xcassets/AppIcon.appiconset/Emerge-Avatar Optimized 10.heic -------------------------------------------------------------------------------- /Examples/DemoApp/DemoApp/Assets.xcassets/AppIcon.appiconset/Emerge-Avatar Optimized 2.heic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/Examples/DemoApp/DemoApp/Assets.xcassets/AppIcon.appiconset/Emerge-Avatar Optimized 2.heic -------------------------------------------------------------------------------- /Examples/DemoApp/DemoApp/Assets.xcassets/AppIcon.appiconset/Emerge-Avatar Optimized 3.heic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/Examples/DemoApp/DemoApp/Assets.xcassets/AppIcon.appiconset/Emerge-Avatar Optimized 3.heic -------------------------------------------------------------------------------- /Examples/DemoApp/DemoApp/Assets.xcassets/AppIcon.appiconset/Emerge-Avatar Optimized 4.heic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/Examples/DemoApp/DemoApp/Assets.xcassets/AppIcon.appiconset/Emerge-Avatar Optimized 4.heic -------------------------------------------------------------------------------- /Examples/DemoApp/DemoApp/Assets.xcassets/AppIcon.appiconset/Emerge-Avatar Optimized 5.heic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/Examples/DemoApp/DemoApp/Assets.xcassets/AppIcon.appiconset/Emerge-Avatar Optimized 5.heic -------------------------------------------------------------------------------- /Examples/DemoApp/DemoApp/Assets.xcassets/AppIcon.appiconset/Emerge-Avatar Optimized 6.heic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/Examples/DemoApp/DemoApp/Assets.xcassets/AppIcon.appiconset/Emerge-Avatar Optimized 6.heic -------------------------------------------------------------------------------- /Examples/DemoApp/DemoApp/Assets.xcassets/AppIcon.appiconset/Emerge-Avatar Optimized 7.heic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/Examples/DemoApp/DemoApp/Assets.xcassets/AppIcon.appiconset/Emerge-Avatar Optimized 7.heic -------------------------------------------------------------------------------- /Examples/DemoApp/DemoApp/Assets.xcassets/AppIcon.appiconset/Emerge-Avatar Optimized 8.heic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/Examples/DemoApp/DemoApp/Assets.xcassets/AppIcon.appiconset/Emerge-Avatar Optimized 8.heic -------------------------------------------------------------------------------- /Examples/DemoApp/DemoApp/Assets.xcassets/AppIcon.appiconset/Emerge-Avatar Optimized 9.heic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/Examples/DemoApp/DemoApp/Assets.xcassets/AppIcon.appiconset/Emerge-Avatar Optimized 9.heic -------------------------------------------------------------------------------- /Examples/DemoApp/DemoApp/DemoApp.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/ios-arm64/PreviewsSupport.framework/Info.plist: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/PreviewsSupport/PreviewsSupport.xcframework/ios-arm64/PreviewsSupport.framework/Info.plist -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/tvos-arm64/PreviewsSupport.framework/Info.plist: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/PreviewsSupport/PreviewsSupport.xcframework/tvos-arm64/PreviewsSupport.framework/Info.plist -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/xros-arm64/PreviewsSupport.framework/Info.plist: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/PreviewsSupport/PreviewsSupport.xcframework/xros-arm64/PreviewsSupport.framework/Info.plist -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Examples/DemoApp/DemoApp/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/ios-arm64/PreviewsSupport.framework/PreviewsSupport: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/PreviewsSupport/PreviewsSupport.xcframework/ios-arm64/PreviewsSupport.framework/PreviewsSupport -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/tvos-arm64/PreviewsSupport.framework/PreviewsSupport: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/PreviewsSupport/PreviewsSupport.xcframework/tvos-arm64/PreviewsSupport.framework/PreviewsSupport -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/xros-arm64/PreviewsSupport.framework/PreviewsSupport: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/PreviewsSupport/PreviewsSupport.xcframework/xros-arm64/PreviewsSupport.framework/PreviewsSupport -------------------------------------------------------------------------------- /Examples/DemoApp/Demo Watch App/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Examples/UnitTestMigration/UnitTestMigration.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Examples/UnitTestMigration/UnitTestMigration/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/ios-arm64_x86_64-simulator/PreviewsSupport.framework/Info.plist: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/PreviewsSupport/PreviewsSupport.xcframework/ios-arm64_x86_64-simulator/PreviewsSupport.framework/Info.plist -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/tvos-arm64_x86_64-simulator/PreviewsSupport.framework/Info.plist: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/PreviewsSupport/PreviewsSupport.xcframework/tvos-arm64_x86_64-simulator/PreviewsSupport.framework/Info.plist -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/xros-arm64_x86_64-simulator/PreviewsSupport.framework/Info.plist: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/PreviewsSupport/PreviewsSupport.xcframework/xros-arm64_x86_64-simulator/PreviewsSupport.framework/Info.plist -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/ios-arm64_x86_64-simulator/PreviewsSupport.framework/PreviewsSupport: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/PreviewsSupport/PreviewsSupport.xcframework/ios-arm64_x86_64-simulator/PreviewsSupport.framework/PreviewsSupport -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/watchos-arm64_arm64_32_armv7k/PreviewsSupport.framework/Info.plist: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/PreviewsSupport/PreviewsSupport.xcframework/watchos-arm64_arm64_32_armv7k/PreviewsSupport.framework/Info.plist -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/watchos-arm64_x86_64-simulator/PreviewsSupport.framework/Info.plist: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/PreviewsSupport/PreviewsSupport.xcframework/watchos-arm64_x86_64-simulator/PreviewsSupport.framework/Info.plist -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/tvos-arm64_x86_64-simulator/PreviewsSupport.framework/PreviewsSupport: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/PreviewsSupport/PreviewsSupport.xcframework/tvos-arm64_x86_64-simulator/PreviewsSupport.framework/PreviewsSupport -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/xros-arm64_x86_64-simulator/PreviewsSupport.framework/PreviewsSupport: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/PreviewsSupport/PreviewsSupport.xcframework/xros-arm64_x86_64-simulator/PreviewsSupport.framework/PreviewsSupport -------------------------------------------------------------------------------- /Examples/DemoApp/DemoApp/Assets.xcassets/product-image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "blog15.jpg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/macos-arm64_x86_64/PreviewsSupport.framework/Versions/A/PreviewsSupport: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/PreviewsSupport/PreviewsSupport.xcframework/macos-arm64_x86_64/PreviewsSupport.framework/Versions/A/PreviewsSupport -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/watchos-arm64_arm64_32_armv7k/PreviewsSupport.framework/PreviewsSupport: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/PreviewsSupport/PreviewsSupport.xcframework/watchos-arm64_arm64_32_armv7k/PreviewsSupport.framework/PreviewsSupport -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/watchos-arm64_x86_64-simulator/PreviewsSupport.framework/PreviewsSupport: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/PreviewsSupport/PreviewsSupport.xcframework/watchos-arm64_x86_64-simulator/PreviewsSupport.framework/PreviewsSupport -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/ios-arm64_x86_64-maccatalyst/PreviewsSupport.framework/Versions/A/PreviewsSupport: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/PreviewsSupport/PreviewsSupport.xcframework/ios-arm64_x86_64-maccatalyst/PreviewsSupport.framework/Versions/A/PreviewsSupport -------------------------------------------------------------------------------- /Examples/DemoApp/Demo Watch App/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "watchos", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Examples/DemoApp/Demo Watch AppTests/DemoWatchPreviewTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoWatchPreviewTest.swift 3 | // Demo Watch AppTests 4 | // 5 | // Created by Noah Martin on 8/10/24. 6 | // 7 | 8 | import XCTest 9 | import SnapshottingTests 10 | 11 | final class DemoWatchPreviewTest: PreviewLayoutTest { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /Examples/DemoApp/Demo Watch AppTests/DemoWatchSnapshotTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoWatchSnapshotTest.swift 3 | // Demo Watch AppTests 4 | // 5 | // Created by Noah Martin on 8/10/24. 6 | // 7 | 8 | import Foundation 9 | import SnapshottingTests 10 | 11 | final class DemoWatchSnapshotTest: SnapshotTest { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Examples/DemoApp/DemoApp/DemoAppApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoAppApp.swift 3 | // DemoApp 4 | // 5 | // Created by Noah Martin on 7/3/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct DemoAppApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/ios-arm64/PreviewsSupport.framework/Modules/PreviewsSupport.swiftmodule/arm64-apple-ios.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/PreviewsSupport/PreviewsSupport.xcframework/ios-arm64/PreviewsSupport.framework/Modules/PreviewsSupport.swiftmodule/arm64-apple-ios.swiftdoc -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/tvos-arm64/PreviewsSupport.framework/Modules/PreviewsSupport.swiftmodule/arm64-apple-tvos.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/PreviewsSupport/PreviewsSupport.xcframework/tvos-arm64/PreviewsSupport.framework/Modules/PreviewsSupport.swiftmodule/arm64-apple-tvos.swiftdoc -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/xros-arm64/PreviewsSupport.framework/Modules/PreviewsSupport.swiftmodule/arm64-apple-xros.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/PreviewsSupport/PreviewsSupport.xcframework/xros-arm64/PreviewsSupport.framework/Modules/PreviewsSupport.swiftmodule/arm64-apple-xros.swiftdoc -------------------------------------------------------------------------------- /Examples/DemoApp/DemoApp/EMGTestHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EMGTestHandler.swift 3 | // DemoApp 4 | // 5 | // Created by Noah Martin on 9/26/23. 6 | // 7 | 8 | import Foundation 9 | 10 | @objc(EMGTestHandler) 11 | class EMGTestHandler: NSObject { 12 | @objc 13 | static func setup() { 14 | print("Setup called.") 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/ios-arm64/PreviewsSupport.framework/Modules/module.modulemap: -------------------------------------------------------------------------------- 1 | framework module PreviewsSupport { 2 | umbrella header "PreviewsSupport.h" 3 | export * 4 | 5 | module * { export * } 6 | } 7 | 8 | module PreviewsSupport.Swift { 9 | header "PreviewsSupport-Swift.h" 10 | requires objc 11 | } 12 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/tvos-arm64/PreviewsSupport.framework/Modules/module.modulemap: -------------------------------------------------------------------------------- 1 | framework module PreviewsSupport { 2 | umbrella header "PreviewsSupport.h" 3 | export * 4 | 5 | module * { export * } 6 | } 7 | 8 | module PreviewsSupport.Swift { 9 | header "PreviewsSupport-Swift.h" 10 | requires objc 11 | } 12 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/xros-arm64/PreviewsSupport.framework/Modules/module.modulemap: -------------------------------------------------------------------------------- 1 | framework module PreviewsSupport { 2 | umbrella header "PreviewsSupport.h" 3 | export * 4 | 5 | module * { export * } 6 | } 7 | 8 | module PreviewsSupport.Swift { 9 | header "PreviewsSupport-Swift.h" 10 | requires objc 11 | } 12 | -------------------------------------------------------------------------------- /Examples/DemoApp/DemoApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/ios-arm64_x86_64-simulator/PreviewsSupport.framework/Modules/module.modulemap: -------------------------------------------------------------------------------- 1 | framework module PreviewsSupport { 2 | umbrella header "PreviewsSupport.h" 3 | export * 4 | 5 | module * { export * } 6 | } 7 | 8 | module PreviewsSupport.Swift { 9 | header "PreviewsSupport-Swift.h" 10 | requires objc 11 | } 12 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/macos-arm64_x86_64/PreviewsSupport.framework/Versions/A/Modules/module.modulemap: -------------------------------------------------------------------------------- 1 | framework module PreviewsSupport { 2 | umbrella header "PreviewsSupport.h" 3 | export * 4 | 5 | module * { export * } 6 | } 7 | 8 | module PreviewsSupport.Swift { 9 | header "PreviewsSupport-Swift.h" 10 | requires objc 11 | } 12 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/tvos-arm64_x86_64-simulator/PreviewsSupport.framework/Modules/module.modulemap: -------------------------------------------------------------------------------- 1 | framework module PreviewsSupport { 2 | umbrella header "PreviewsSupport.h" 3 | export * 4 | 5 | module * { export * } 6 | } 7 | 8 | module PreviewsSupport.Swift { 9 | header "PreviewsSupport-Swift.h" 10 | requires objc 11 | } 12 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/watchos-arm64_arm64_32_armv7k/PreviewsSupport.framework/Modules/module.modulemap: -------------------------------------------------------------------------------- 1 | framework module PreviewsSupport { 2 | umbrella header "PreviewsSupport.h" 3 | export * 4 | 5 | module * { export * } 6 | } 7 | 8 | module PreviewsSupport.Swift { 9 | header "PreviewsSupport-Swift.h" 10 | requires objc 11 | } 12 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/watchos-arm64_x86_64-simulator/PreviewsSupport.framework/Modules/module.modulemap: -------------------------------------------------------------------------------- 1 | framework module PreviewsSupport { 2 | umbrella header "PreviewsSupport.h" 3 | export * 4 | 5 | module * { export * } 6 | } 7 | 8 | module PreviewsSupport.Swift { 9 | header "PreviewsSupport-Swift.h" 10 | requires objc 11 | } 12 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/xros-arm64_x86_64-simulator/PreviewsSupport.framework/Modules/module.modulemap: -------------------------------------------------------------------------------- 1 | framework module PreviewsSupport { 2 | umbrella header "PreviewsSupport.h" 3 | export * 4 | 5 | module * { export * } 6 | } 7 | 8 | module PreviewsSupport.Swift { 9 | header "PreviewsSupport-Swift.h" 10 | requires objc 11 | } 12 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/ios-arm64_x86_64-maccatalyst/PreviewsSupport.framework/Versions/A/Modules/module.modulemap: -------------------------------------------------------------------------------- 1 | framework module PreviewsSupport { 2 | umbrella header "PreviewsSupport.h" 3 | export * 4 | 5 | module * { export * } 6 | } 7 | 8 | module PreviewsSupport.Swift { 9 | header "PreviewsSupport-Swift.h" 10 | requires objc 11 | } 12 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/macos-arm64_x86_64/PreviewsSupport.framework/Versions/A/Modules/PreviewsSupport.swiftmodule/arm64-apple-macos.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/PreviewsSupport/PreviewsSupport.xcframework/macos-arm64_x86_64/PreviewsSupport.framework/Versions/A/Modules/PreviewsSupport.swiftmodule/arm64-apple-macos.swiftdoc -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/macos-arm64_x86_64/PreviewsSupport.framework/Versions/A/Modules/PreviewsSupport.swiftmodule/x86_64-apple-macos.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/PreviewsSupport/PreviewsSupport.xcframework/macos-arm64_x86_64/PreviewsSupport.framework/Versions/A/Modules/PreviewsSupport.swiftmodule/x86_64-apple-macos.swiftdoc -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/watchos-arm64_arm64_32_armv7k/PreviewsSupport.framework/Modules/PreviewsSupport.swiftmodule/arm64-apple-watchos.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/PreviewsSupport/PreviewsSupport.xcframework/watchos-arm64_arm64_32_armv7k/PreviewsSupport.framework/Modules/PreviewsSupport.swiftmodule/arm64-apple-watchos.swiftdoc -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/ios-arm64_x86_64-simulator/PreviewsSupport.framework/Modules/PreviewsSupport.swiftmodule/arm64-apple-ios-simulator.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/PreviewsSupport/PreviewsSupport.xcframework/ios-arm64_x86_64-simulator/PreviewsSupport.framework/Modules/PreviewsSupport.swiftmodule/arm64-apple-ios-simulator.swiftdoc -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/watchos-arm64_arm64_32_armv7k/PreviewsSupport.framework/Modules/PreviewsSupport.swiftmodule/arm64_32-apple-watchos.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/PreviewsSupport/PreviewsSupport.xcframework/watchos-arm64_arm64_32_armv7k/PreviewsSupport.framework/Modules/PreviewsSupport.swiftmodule/arm64_32-apple-watchos.swiftdoc -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/watchos-arm64_arm64_32_armv7k/PreviewsSupport.framework/Modules/PreviewsSupport.swiftmodule/armv7k-apple-watchos.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/PreviewsSupport/PreviewsSupport.xcframework/watchos-arm64_arm64_32_armv7k/PreviewsSupport.framework/Modules/PreviewsSupport.swiftmodule/armv7k-apple-watchos.swiftdoc -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/ios-arm64_x86_64-simulator/PreviewsSupport.framework/Modules/PreviewsSupport.swiftmodule/x86_64-apple-ios-simulator.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/PreviewsSupport/PreviewsSupport.xcframework/ios-arm64_x86_64-simulator/PreviewsSupport.framework/Modules/PreviewsSupport.swiftmodule/x86_64-apple-ios-simulator.swiftdoc -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/tvos-arm64_x86_64-simulator/PreviewsSupport.framework/Modules/PreviewsSupport.swiftmodule/arm64-apple-tvos-simulator.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/PreviewsSupport/PreviewsSupport.xcframework/tvos-arm64_x86_64-simulator/PreviewsSupport.framework/Modules/PreviewsSupport.swiftmodule/arm64-apple-tvos-simulator.swiftdoc -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/xros-arm64_x86_64-simulator/PreviewsSupport.framework/Modules/PreviewsSupport.swiftmodule/arm64-apple-xros-simulator.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/PreviewsSupport/PreviewsSupport.xcframework/xros-arm64_x86_64-simulator/PreviewsSupport.framework/Modules/PreviewsSupport.swiftmodule/arm64-apple-xros-simulator.swiftdoc -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/tvos-arm64_x86_64-simulator/PreviewsSupport.framework/Modules/PreviewsSupport.swiftmodule/x86_64-apple-tvos-simulator.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/PreviewsSupport/PreviewsSupport.xcframework/tvos-arm64_x86_64-simulator/PreviewsSupport.framework/Modules/PreviewsSupport.swiftmodule/x86_64-apple-tvos-simulator.swiftdoc -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/xros-arm64_x86_64-simulator/PreviewsSupport.framework/Modules/PreviewsSupport.swiftmodule/x86_64-apple-xros-simulator.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/PreviewsSupport/PreviewsSupport.xcframework/xros-arm64_x86_64-simulator/PreviewsSupport.framework/Modules/PreviewsSupport.swiftmodule/x86_64-apple-xros-simulator.swiftdoc -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/ios-arm64_x86_64-maccatalyst/PreviewsSupport.framework/Versions/A/Modules/PreviewsSupport.swiftmodule/arm64-apple-ios-macabi.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/PreviewsSupport/PreviewsSupport.xcframework/ios-arm64_x86_64-maccatalyst/PreviewsSupport.framework/Versions/A/Modules/PreviewsSupport.swiftmodule/arm64-apple-ios-macabi.swiftdoc -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/watchos-arm64_x86_64-simulator/PreviewsSupport.framework/Modules/PreviewsSupport.swiftmodule/arm64-apple-watchos-simulator.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/PreviewsSupport/PreviewsSupport.xcframework/watchos-arm64_x86_64-simulator/PreviewsSupport.framework/Modules/PreviewsSupport.swiftmodule/arm64-apple-watchos-simulator.swiftdoc -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/watchos-arm64_x86_64-simulator/PreviewsSupport.framework/Modules/PreviewsSupport.swiftmodule/x86_64-apple-watchos-simulator.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/PreviewsSupport/PreviewsSupport.xcframework/watchos-arm64_x86_64-simulator/PreviewsSupport.framework/Modules/PreviewsSupport.swiftmodule/x86_64-apple-watchos-simulator.swiftdoc -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/ios-arm64_x86_64-maccatalyst/PreviewsSupport.framework/Versions/A/Modules/PreviewsSupport.swiftmodule/x86_64-apple-ios-macabi.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmergeTools/SnapshotPreviews/HEAD/PreviewsSupport/PreviewsSupport.xcframework/ios-arm64_x86_64-maccatalyst/PreviewsSupport.framework/Versions/A/Modules/PreviewsSupport.swiftmodule/x86_64-apple-ios-macabi.swiftdoc -------------------------------------------------------------------------------- /.spi.yml: -------------------------------------------------------------------------------- 1 | # This is manifest file for the Swift Package Index for it to auto-generate and 2 | # host DocC documentation. 3 | # 4 | # For reference see https://swiftpackageindex.com/swiftpackageindex/spimanifest/documentation/spimanifest/commonusecases#Host-DocC-documentation-in-the-Swift-Package-Index 5 | version: 1 6 | builder: 7 | configs: 8 | - documentation_targets: [SnapshotPreferences, Snapshotting, SnapshottingTests] 9 | -------------------------------------------------------------------------------- /Examples/DemoApp/Demo Watch App/DemoApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoApp.swift 3 | // Demo Watch App 4 | // 5 | // Created by Noah Martin on 7/5/24. 6 | // 7 | 8 | import SwiftUI 9 | import PreviewGallery 10 | 11 | @main 12 | struct Demo_Watch_AppApp: App { 13 | var body: some Scene { 14 | WindowGroup { 15 | NavigationStack { 16 | PreviewGallery() 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | xcuserdata/ 5 | DerivedData/ 6 | .swiftpm/configuration/registries.json 7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 8 | .netrc 9 | Examples/DemoApp/DemoApp.xcodeproj/xcuserdata/ 10 | .xcuserstate 11 | PreviewsSupport/PreviewsSupport.xcframework/**/*.private.swiftinterface 12 | 13 | 14 | Examples/DemoApp/DemoApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/ -------------------------------------------------------------------------------- /Examples/UnitTestMigration/UnitTestMigrationTests/ExampleSnapshotTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExampleSnapshotTest.swift 3 | // UnitTestMigrationTests 4 | // 5 | // Created by Noah Martin on 9/19/24. 6 | // 7 | 8 | import XCTest 9 | import SnapshotTesting 10 | @testable import UnitTestMigration 11 | 12 | class ExampleSnapshotTest: XCTestCase { 13 | func testContentViewSnapshot() { 14 | assertSnapshot(of: ContentView(), as: .image) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Examples/DemoApp/DemoModule/RatingPreviews.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RatingPreviews.swift 3 | // DemoModule 4 | // 5 | // Created by Noah Martin on 7/5/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct RatingViews_Previews: PreviewProvider { 11 | static var previews: some View { 12 | ForEach(0..<20) { i in 13 | RatingView(rating: Double(i)) 14 | .previewLayout(.sizeThatFits) 15 | .padding() 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/PreviewGallery/Array+Filter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Array+Filter.swift 3 | // SnapshotPreviews 4 | // 5 | // Created by Itay Brenner on 23/1/25. 6 | // 7 | 8 | extension Array { 9 | func filterWithText(_ text: String, _ nameForElement: @escaping ((Element) -> String)) -> [Element] { 10 | return self.filter { element in 11 | text.isEmpty ? true : nameForElement(element).lowercased().contains(text.lowercased()) 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.github/workflows/build-xcframework.yml: -------------------------------------------------------------------------------- 1 | name: Build XCFramework 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build: 10 | runs-on: macos-14 11 | 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v2 15 | - name: Xcode select 16 | run: sudo xcode-select -s '/Applications/Xcode_15.4.app/Contents/Developer' 17 | - name: Build xcframework 18 | run: sh build.sh 19 | -------------------------------------------------------------------------------- /Examples/DemoApp/DemoWatchTests/DemoWatchAccessibilityPreviewTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoWatchAccessibilityPreviewTest.swift 3 | // Demo 4 | // 5 | // Created by Noah Martin on 7/5/24. 6 | // 7 | 8 | import Snapshotting 9 | import SnapshottingTests 10 | import XCTest 11 | 12 | final class DemoWatchAccessibilityPreviewTest: AccessibilityPreviewTest { 13 | 14 | override class func getApp() -> XCUIApplication { 15 | return XCUIApplication() 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Tests/SnapshotPreviewsTests/ETBrowserTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import SnapshotPreviewsCore 3 | 4 | final class SnapshotPreviewsTest: XCTestCase { 5 | func testExample() throws { 6 | // XCTest Documentation 7 | // https://developer.apple.com/documentation/xctest 8 | 9 | // Defining Test Cases and Test Methods 10 | // https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Examples/UnitTestMigration/UnitTestMigration/UnitTestMigrationApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UnitTestMigrationApp.swift 3 | // UnitTestMigration 4 | // 5 | // Created by Noah Martin on 9/19/24. 6 | // 7 | 8 | import SwiftUI 9 | import PreviewGallery 10 | 11 | @main 12 | struct UnitTestMigrationApp: App { 13 | var body: some Scene { 14 | WindowGroup { 15 | NavigationStack { 16 | PreviewGallery() 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Examples/DemoApp/DemoAppTests/DemoAppPreviewTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoAppPreviewTest.swift 3 | // DemoAppTests 4 | // 5 | // Created by Noah Martin on 8/9/24. 6 | // 7 | 8 | import XCTest 9 | import SnapshottingTests 10 | 11 | final class DemoAppPreviewTest: PreviewLayoutTest { 12 | override class func snapshotPreviews() -> [String]? { 13 | return nil 14 | } 15 | 16 | override class func excludedSnapshotPreviews() -> [String]? { 17 | return nil 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Examples/UnitTestMigration/UnitTestMigration/ExampleSnapshotTest_Migrated.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExampleSnapshotTest_Migrated.swift 3 | // UnitTestMigration 4 | // 5 | // Created by Noah Martin on 9/19/24. 6 | // 7 | 8 | import SwiftUI 9 | 10 | // Add the conformance to `PreviewProvider` when extending `SnapshotTest` 11 | class ExampleSnapshotTest: SnapshotTest, PreviewProvider { 12 | 13 | func testContentViewSnapshot() { 14 | assertSnapshot(of: ContentView(), as: .image) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/Snapshotting/Initializer.m: -------------------------------------------------------------------------------- 1 | // 2 | // Initializer.m 3 | // 4 | // 5 | // Created by Noah Martin on 7/12/23. 6 | // 7 | 8 | #import 9 | @import SnapshottingSwift; 10 | 11 | __attribute__((constructor)) static void setup(void); 12 | __attribute__((constructor)) static void setup(void) { 13 | char* str = getenv("EMERGE_IS_RUNNING_FOR_SNAPSHOTS"); 14 | if (str && strcmp(str, "1") == 0) { 15 | Initializer *swiftBridge = [Initializer shared]; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Examples/UnitTestMigration/UnitTestMigration/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // UnitTestMigration 4 | // 5 | // Created by Noah Martin on 9/19/24. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ContentView: View { 11 | var body: some View { 12 | VStack { 13 | Image(systemName: "globe") 14 | .imageScale(.large) 15 | .foregroundStyle(.tint) 16 | Text("Hello, world!") 17 | } 18 | .padding() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Examples/DemoApp/DemoModule/DateView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DateView.swift 3 | // DemoApp 4 | // 5 | // Created by Noah Martin on 12/28/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct DateView: View { 11 | let currentDate = Date() 12 | 13 | var body: some View { 14 | Text(currentDate, style: .date) 15 | .font(.title) 16 | .padding() 17 | } 18 | } 19 | 20 | struct DateView_Previews: PreviewProvider { 21 | static var previews: some View { 22 | DateView() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Examples/DemoApp/DemoApp.xctestplan: -------------------------------------------------------------------------------- 1 | { 2 | "configurations" : [ 3 | { 4 | "id" : "FF846110-BFA2-419E-BF99-A9FC438920E2", 5 | "name" : "Configuration 1", 6 | "options" : { 7 | 8 | } 9 | } 10 | ], 11 | "defaultOptions" : { 12 | 13 | }, 14 | "testTargets" : [ 15 | { 16 | "target" : { 17 | "containerPath" : "container:DemoApp.xcodeproj", 18 | "identifier" : "FAC167E62A5F41C500D79C23", 19 | "name" : "DemoAppUITests" 20 | } 21 | } 22 | ], 23 | "version" : 1 24 | } 25 | -------------------------------------------------------------------------------- /Examples/DemoApp/DemoAppTests.xctestplan: -------------------------------------------------------------------------------- 1 | { 2 | "configurations" : [ 3 | { 4 | "id" : "B1D580A7-404D-4E1D-80D8-0E56CDD58281", 5 | "name" : "Test Scheme Action", 6 | "options" : { 7 | 8 | } 9 | } 10 | ], 11 | "defaultOptions" : { 12 | 13 | }, 14 | "testTargets" : [ 15 | { 16 | "target" : { 17 | "containerPath" : "container:DemoApp.xcodeproj", 18 | "identifier" : "FA8F8B7A2C66C4C4007CEA33", 19 | "name" : "DemoAppTests" 20 | } 21 | } 22 | ], 23 | "version" : 1 24 | } 25 | -------------------------------------------------------------------------------- /Examples/DemoApp/DemoModule/DemoModule.h: -------------------------------------------------------------------------------- 1 | // 2 | // DemoModule.h 3 | // DemoModule 4 | // 5 | // Created by Noah Martin on 7/3/23. 6 | // 7 | 8 | #import 9 | 10 | //! Project version number for DemoModule. 11 | FOUNDATION_EXPORT double DemoModuleVersionNumber; 12 | 13 | //! Project version string for DemoModule. 14 | FOUNDATION_EXPORT const unsigned char DemoModuleVersionString[]; 15 | 16 | // In this header, you should import all the public headers of your framework using statements like #import 17 | 18 | 19 | -------------------------------------------------------------------------------- /Examples/DemoApp/DemoApp/TestViews/AnyViewPreview.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnyViewPreview.swift 3 | // DemoApp 4 | // 5 | // Created by Noah Martin on 8/4/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | struct AnyView_Previews: PreviewProvider { 12 | static var previews: some View { 13 | AnyView( 14 | Group { 15 | Text("Hello") 16 | .previewDisplayName("Hello") 17 | Text("World") 18 | .previewDisplayName("World") 19 | }) 20 | .environment(\.sizeCategory, .accessibilityExtraExtraLarge) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Examples/DemoApp/DemoModule/Color.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Color.swift 3 | // DemoModule 4 | // 5 | // Created by Noah Martin on 12/11/23. 6 | // 7 | 8 | import Foundation 9 | 10 | #if canImport(UIKit) 11 | import UIKit 12 | public typealias PlatformColor = UIColor 13 | #else 14 | import AppKit 15 | 16 | public typealias PlatformColor = NSColor 17 | 18 | extension NSColor { 19 | public static var systemBackground: NSColor { 20 | NSColor.windowBackgroundColor 21 | } 22 | 23 | public static var label: NSColor { 24 | NSColor.labelColor 25 | } 26 | } 27 | #endif 28 | -------------------------------------------------------------------------------- /Sources/SnapshotPreviewsCore/UIImage+EMG.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+EMG.swift 3 | // 4 | // 5 | // Created by Noah Martin on 8/8/24. 6 | // 7 | 8 | #if canImport(UIKit) 9 | import UIKit 10 | 11 | public extension UIImage { 12 | var emg: UIImageSnapshotsNamespace { 13 | .init(image: self) 14 | } 15 | 16 | struct UIImageSnapshotsNamespace { 17 | private let image: UIImage 18 | 19 | init(image: UIImage) { 20 | self.image = image 21 | } 22 | 23 | public func pngData() -> Data? { 24 | image.pngData() 25 | } 26 | } 27 | } 28 | #endif 29 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport/PreviewsSupport.h: -------------------------------------------------------------------------------- 1 | // 2 | // PreviewsSupport.h 3 | // PreviewsSupport 4 | // 5 | // Created by Noah Martin on 10/18/23. 6 | // 7 | 8 | #import 9 | 10 | //! Project version number for PreviewsSupport. 11 | FOUNDATION_EXPORT double PreviewsSupportVersionNumber; 12 | 13 | //! Project version string for PreviewsSupport. 14 | FOUNDATION_EXPORT const unsigned char PreviewsSupportVersionString[]; 15 | 16 | // In this header, you should import all the public headers of your framework using statements like #import 17 | 18 | 19 | -------------------------------------------------------------------------------- /Sources/PreviewGallery/Preview+FullScreen.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Preview+FullScreen.swift 3 | // 4 | // 5 | // Created by Noah Martin on 8/31/23. 6 | // 7 | 8 | import Foundation 9 | import SnapshotPreviewsCore 10 | 11 | extension Preview { 12 | 13 | var requiresFullScreen: Bool { 14 | switch layout { 15 | case .device: 16 | return true 17 | default: 18 | return false 19 | } 20 | } 21 | 22 | } 23 | 24 | extension PreviewType { 25 | func previews(requiringFullscreen: Bool) -> [Preview] { 26 | previews.filter { $0.requiresFullScreen == requiringFullscreen } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Examples/DemoApp/DemoApp/TestViews/TextView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextView.swift 3 | // DemoApp 4 | // 5 | // Created by Noah Martin on 9/1/23. 6 | // 7 | 8 | #if canImport(UIKit) 9 | import Foundation 10 | import SwiftUI 11 | 12 | struct TextView: UIViewRepresentable { 13 | func makeUIView(context: Context) -> UITextView { 14 | let view = UITextView() 15 | view.text = "Some text" 16 | return view 17 | } 18 | 19 | func updateUIView(_ uiView: UITextView, context: Context) { } 20 | } 21 | 22 | struct TextView_Previews: PreviewProvider { 23 | static var previews: some View { 24 | TextView() 25 | } 26 | } 27 | #endif 28 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/ios-arm64/PreviewsSupport.framework/Headers/PreviewsSupport.h: -------------------------------------------------------------------------------- 1 | // 2 | // PreviewsSupport.h 3 | // PreviewsSupport 4 | // 5 | // Created by Noah Martin on 10/18/23. 6 | // 7 | 8 | #import 9 | 10 | //! Project version number for PreviewsSupport. 11 | FOUNDATION_EXPORT double PreviewsSupportVersionNumber; 12 | 13 | //! Project version string for PreviewsSupport. 14 | FOUNDATION_EXPORT const unsigned char PreviewsSupportVersionString[]; 15 | 16 | // In this header, you should import all the public headers of your framework using statements like #import 17 | 18 | 19 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/tvos-arm64/PreviewsSupport.framework/Headers/PreviewsSupport.h: -------------------------------------------------------------------------------- 1 | // 2 | // PreviewsSupport.h 3 | // PreviewsSupport 4 | // 5 | // Created by Noah Martin on 10/18/23. 6 | // 7 | 8 | #import 9 | 10 | //! Project version number for PreviewsSupport. 11 | FOUNDATION_EXPORT double PreviewsSupportVersionNumber; 12 | 13 | //! Project version string for PreviewsSupport. 14 | FOUNDATION_EXPORT const unsigned char PreviewsSupportVersionString[]; 15 | 16 | // In this header, you should import all the public headers of your framework using statements like #import 17 | 18 | 19 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/xros-arm64/PreviewsSupport.framework/Headers/PreviewsSupport.h: -------------------------------------------------------------------------------- 1 | // 2 | // PreviewsSupport.h 3 | // PreviewsSupport 4 | // 5 | // Created by Noah Martin on 10/18/23. 6 | // 7 | 8 | #import 9 | 10 | //! Project version number for PreviewsSupport. 11 | FOUNDATION_EXPORT double PreviewsSupportVersionNumber; 12 | 13 | //! Project version string for PreviewsSupport. 14 | FOUNDATION_EXPORT const unsigned char PreviewsSupportVersionString[]; 15 | 16 | // In this header, you should import all the public headers of your framework using statements like #import 17 | 18 | 19 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/ios-arm64_x86_64-simulator/PreviewsSupport.framework/Headers/PreviewsSupport.h: -------------------------------------------------------------------------------- 1 | // 2 | // PreviewsSupport.h 3 | // PreviewsSupport 4 | // 5 | // Created by Noah Martin on 10/18/23. 6 | // 7 | 8 | #import 9 | 10 | //! Project version number for PreviewsSupport. 11 | FOUNDATION_EXPORT double PreviewsSupportVersionNumber; 12 | 13 | //! Project version string for PreviewsSupport. 14 | FOUNDATION_EXPORT const unsigned char PreviewsSupportVersionString[]; 15 | 16 | // In this header, you should import all the public headers of your framework using statements like #import 17 | 18 | 19 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/tvos-arm64_x86_64-simulator/PreviewsSupport.framework/Headers/PreviewsSupport.h: -------------------------------------------------------------------------------- 1 | // 2 | // PreviewsSupport.h 3 | // PreviewsSupport 4 | // 5 | // Created by Noah Martin on 10/18/23. 6 | // 7 | 8 | #import 9 | 10 | //! Project version number for PreviewsSupport. 11 | FOUNDATION_EXPORT double PreviewsSupportVersionNumber; 12 | 13 | //! Project version string for PreviewsSupport. 14 | FOUNDATION_EXPORT const unsigned char PreviewsSupportVersionString[]; 15 | 16 | // In this header, you should import all the public headers of your framework using statements like #import 17 | 18 | 19 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/xros-arm64_x86_64-simulator/PreviewsSupport.framework/Headers/PreviewsSupport.h: -------------------------------------------------------------------------------- 1 | // 2 | // PreviewsSupport.h 3 | // PreviewsSupport 4 | // 5 | // Created by Noah Martin on 10/18/23. 6 | // 7 | 8 | #import 9 | 10 | //! Project version number for PreviewsSupport. 11 | FOUNDATION_EXPORT double PreviewsSupportVersionNumber; 12 | 13 | //! Project version string for PreviewsSupport. 14 | FOUNDATION_EXPORT const unsigned char PreviewsSupportVersionString[]; 15 | 16 | // In this header, you should import all the public headers of your framework using statements like #import 17 | 18 | 19 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/macos-arm64_x86_64/PreviewsSupport.framework/Versions/A/Headers/PreviewsSupport.h: -------------------------------------------------------------------------------- 1 | // 2 | // PreviewsSupport.h 3 | // PreviewsSupport 4 | // 5 | // Created by Noah Martin on 10/18/23. 6 | // 7 | 8 | #import 9 | 10 | //! Project version number for PreviewsSupport. 11 | FOUNDATION_EXPORT double PreviewsSupportVersionNumber; 12 | 13 | //! Project version string for PreviewsSupport. 14 | FOUNDATION_EXPORT const unsigned char PreviewsSupportVersionString[]; 15 | 16 | // In this header, you should import all the public headers of your framework using statements like #import 17 | 18 | 19 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/watchos-arm64_arm64_32_armv7k/PreviewsSupport.framework/Headers/PreviewsSupport.h: -------------------------------------------------------------------------------- 1 | // 2 | // PreviewsSupport.h 3 | // PreviewsSupport 4 | // 5 | // Created by Noah Martin on 10/18/23. 6 | // 7 | 8 | #import 9 | 10 | //! Project version number for PreviewsSupport. 11 | FOUNDATION_EXPORT double PreviewsSupportVersionNumber; 12 | 13 | //! Project version string for PreviewsSupport. 14 | FOUNDATION_EXPORT const unsigned char PreviewsSupportVersionString[]; 15 | 16 | // In this header, you should import all the public headers of your framework using statements like #import 17 | 18 | 19 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/watchos-arm64_x86_64-simulator/PreviewsSupport.framework/Headers/PreviewsSupport.h: -------------------------------------------------------------------------------- 1 | // 2 | // PreviewsSupport.h 3 | // PreviewsSupport 4 | // 5 | // Created by Noah Martin on 10/18/23. 6 | // 7 | 8 | #import 9 | 10 | //! Project version number for PreviewsSupport. 11 | FOUNDATION_EXPORT double PreviewsSupportVersionNumber; 12 | 13 | //! Project version string for PreviewsSupport. 14 | FOUNDATION_EXPORT const unsigned char PreviewsSupportVersionString[]; 15 | 16 | // In this header, you should import all the public headers of your framework using statements like #import 17 | 18 | 19 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/ios-arm64_x86_64-maccatalyst/PreviewsSupport.framework/Versions/A/Headers/PreviewsSupport.h: -------------------------------------------------------------------------------- 1 | // 2 | // PreviewsSupport.h 3 | // PreviewsSupport 4 | // 5 | // Created by Noah Martin on 10/18/23. 6 | // 7 | 8 | #import 9 | 10 | //! Project version number for PreviewsSupport. 11 | FOUNDATION_EXPORT double PreviewsSupportVersionNumber; 12 | 13 | //! Project version string for PreviewsSupport. 14 | FOUNDATION_EXPORT const unsigned char PreviewsSupportVersionString[]; 15 | 16 | // In this header, you should import all the public headers of your framework using statements like #import 17 | 18 | 19 | -------------------------------------------------------------------------------- /Examples/DemoApp/DemoApp/TestViews/EmptyView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmptyView.swift 3 | // DemoApp 4 | // 5 | // Created by Noah Martin on 8/22/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | struct EmptyViewTest: View { 12 | var body: some View { 13 | let test = true 14 | if test { 15 | EmptyView() 16 | } else { 17 | Text("Hello") 18 | } 19 | Text("World") 20 | } 21 | } 22 | 23 | struct EmptyIfTest: View { 24 | var body: some View { 25 | if true { 26 | EmptyView() 27 | } 28 | Text("Hello World") 29 | } 30 | } 31 | 32 | struct EmptyViewTest_Previews: PreviewProvider { 33 | static var previews: some View { 34 | EmptyViewTest() 35 | EmptyIfTest() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/PreviewGallery/TitleSubtitleRow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TitleSubtitleRow.swift 3 | // 4 | // 5 | // Created by Noah Martin on 8/31/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | struct TitleSubtitleRow: View { 12 | let title: String 13 | let subtitle: String 14 | 15 | var body: some View { 16 | HStack { 17 | VStack(alignment: .leading) { 18 | Text(title) 19 | .font(.headline) 20 | .foregroundStyle(Color.primary) 21 | 22 | Text(subtitle) 23 | .font(.subheadline) 24 | .foregroundStyle(Color.secondary) 25 | } 26 | Spacer() 27 | Image(systemName: "chevron.right") 28 | .foregroundColor(Color.secondary) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/SnapshottingSwift/Initializer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Noah Martin on 7/12/23. 6 | // 7 | 8 | import Foundation 9 | #if canImport(UIKit) 10 | import UIKit 11 | #endif 12 | 13 | @objc 14 | public class Initializer: NSObject { 15 | 16 | @objc 17 | static public let shared = Initializer() 18 | 19 | override init() { 20 | super.init() 21 | 22 | #if !canImport(UIKit) || os(watchOS) 23 | snapshots = Snapshots() 24 | #else 25 | NotificationCenter.default.addObserver(forName: UIApplication.didBecomeActiveNotification, object: nil, queue: .main) { [weak self] notification in 26 | self?.snapshots = Snapshots() 27 | } 28 | #endif 29 | } 30 | var snapshots: Snapshots? 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Sources/SnapshottingTests/DiscoveredPreview+PreviewType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DiscoveredPreview+PreviewType.swift 3 | // 4 | // 5 | // Created by Noah Martin on 8/9/24. 6 | // 7 | 8 | import Foundation 9 | @_implementationOnly import SnapshotPreviewsCore 10 | 11 | extension DiscoveredPreview { 12 | static func from(previewType: PreviewType) -> DiscoveredPreview { 13 | return DiscoveredPreview(typeName: previewType.typeName, 14 | displayName: previewType.displayName, 15 | devices: previewType.previews.map { $0.device?.rawValue ?? "" }, 16 | orientations: previewType.previews.map { $0.orientation.id }, 17 | numberOfPreviews: previewType.previews.count) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Examples/UnitTestMigration/UnitTestMigration/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | }, 8 | { 9 | "appearances" : [ 10 | { 11 | "appearance" : "luminosity", 12 | "value" : "dark" 13 | } 14 | ], 15 | "idiom" : "universal", 16 | "platform" : "ios", 17 | "size" : "1024x1024" 18 | }, 19 | { 20 | "appearances" : [ 21 | { 22 | "appearance" : "luminosity", 23 | "value" : "tinted" 24 | } 25 | ], 26 | "idiom" : "universal", 27 | "platform" : "ios", 28 | "size" : "1024x1024" 29 | } 30 | ], 31 | "info" : { 32 | "author" : "xcode", 33 | "version" : 1 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/macos-arm64_x86_64/PreviewsSupport.framework/Versions/A/Modules/PreviewsSupport.swiftmodule/arm64-apple-macos.swiftinterface: -------------------------------------------------------------------------------- 1 | // swift-interface-format-version: 1.0 2 | // swift-compiler-version: Apple Swift version 6.0 effective-5.10 (swiftlang-6.0.0.9.10 clang-1600.0.26.2) 3 | // swift-module-flags: -target arm64-apple-macos12.0 -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -enable-bare-slash-regex -module-name PreviewsSupport 4 | // swift-module-flags-ignorable: -no-verify-emitted-module-interface 5 | @_exported import PreviewsSupport 6 | import Swift 7 | import SwiftUI 8 | import _Concurrency 9 | import _StringProcessing 10 | import _SwiftConcurrencyShims 11 | public protocol MakeViewProvider { 12 | var makeView: @_Concurrency.MainActor () -> any SwiftUI.View { get } 13 | } 14 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/macos-arm64_x86_64/PreviewsSupport.framework/Versions/A/Modules/PreviewsSupport.swiftmodule/x86_64-apple-macos.swiftinterface: -------------------------------------------------------------------------------- 1 | // swift-interface-format-version: 1.0 2 | // swift-compiler-version: Apple Swift version 6.0 effective-5.10 (swiftlang-6.0.0.9.10 clang-1600.0.26.2) 3 | // swift-module-flags: -target x86_64-apple-macos12.0 -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -enable-bare-slash-regex -module-name PreviewsSupport 4 | // swift-module-flags-ignorable: -no-verify-emitted-module-interface 5 | @_exported import PreviewsSupport 6 | import Swift 7 | import SwiftUI 8 | import _Concurrency 9 | import _StringProcessing 10 | import _SwiftConcurrencyShims 11 | public protocol MakeViewProvider { 12 | var makeView: @_Concurrency.MainActor () -> any SwiftUI.View { get } 13 | } 14 | -------------------------------------------------------------------------------- /Examples/DemoApp/DemoAppUITests/DemoAppAccessibilityPreviewTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoAppAccessibilityPreviewTest.swift 3 | // DemoAppUITests 4 | // 5 | // Created by Noah Martin on 7/14/23. 6 | // 7 | 8 | import Foundation 9 | import XCTest 10 | import SnapshottingTests 11 | 12 | @available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) 13 | class DemoAppAccessibilityPreviewTest: AccessibilityPreviewTest { 14 | 15 | override class func snapshotPreviews() -> [String]? { 16 | return nil 17 | } 18 | 19 | override class func excludedSnapshotPreviews() -> [String]? { 20 | return nil 21 | } 22 | 23 | @available(iOS 17.0, *) 24 | override func auditType() -> XCUIAccessibilityAuditType { 25 | return .all 26 | } 27 | 28 | @available(iOS 17.0, *) 29 | override func handle(issue: XCUIAccessibilityAuditIssue) -> Bool { 30 | return false 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/watchos-arm64_arm64_32_armv7k/PreviewsSupport.framework/Modules/PreviewsSupport.swiftmodule/arm64-apple-watchos.swiftinterface: -------------------------------------------------------------------------------- 1 | // swift-interface-format-version: 1.0 2 | // swift-compiler-version: Apple Swift version 6.0 effective-5.10 (swiftlang-6.0.0.9.10 clang-1600.0.26.2) 3 | // swift-module-flags: -target arm64-apple-watchos8.0 -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -enable-bare-slash-regex -module-name PreviewsSupport 4 | // swift-module-flags-ignorable: -no-verify-emitted-module-interface 5 | @_exported import PreviewsSupport 6 | import Swift 7 | import SwiftUI 8 | import UIKit 9 | import _Concurrency 10 | import _StringProcessing 11 | import _SwiftConcurrencyShims 12 | public protocol MakeViewProvider { 13 | var makeView: @_Concurrency.MainActor () -> any SwiftUI.View { get } 14 | } 15 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/watchos-arm64_arm64_32_armv7k/PreviewsSupport.framework/Modules/PreviewsSupport.swiftmodule/armv7k-apple-watchos.swiftinterface: -------------------------------------------------------------------------------- 1 | // swift-interface-format-version: 1.0 2 | // swift-compiler-version: Apple Swift version 6.0 effective-5.10 (swiftlang-6.0.0.9.10 clang-1600.0.26.2) 3 | // swift-module-flags: -target armv7k-apple-watchos8.0 -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -enable-bare-slash-regex -module-name PreviewsSupport 4 | // swift-module-flags-ignorable: -no-verify-emitted-module-interface 5 | @_exported import PreviewsSupport 6 | import Swift 7 | import SwiftUI 8 | import UIKit 9 | import _Concurrency 10 | import _StringProcessing 11 | import _SwiftConcurrencyShims 12 | public protocol MakeViewProvider { 13 | var makeView: @_Concurrency.MainActor () -> any SwiftUI.View { get } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/SnapshotPreviewsCore/NSImage+EMG.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSImage+EMG.swift 3 | // 4 | // 5 | // Created by Noah Martin on 8/2/24. 6 | // 7 | 8 | #if canImport(AppKit) && !targetEnvironment(macCatalyst) 9 | import AppKit 10 | 11 | public extension NSImage { 12 | var emg: NSImageSnapshotsNamespace { 13 | .init(image: self) 14 | } 15 | 16 | struct NSImageSnapshotsNamespace { 17 | private let image: NSImage 18 | 19 | init(image: NSImage) { 20 | self.image = image 21 | } 22 | 23 | public func pngData() -> NSData? { 24 | guard let tiffData = image.tiffRepresentation, 25 | let bitmapImageRep = NSBitmapImageRep(data: tiffData) else { 26 | return nil 27 | } 28 | 29 | let pngData = bitmapImageRep.representation(using: .png, properties: [:]) 30 | return pngData as NSData? 31 | } 32 | } 33 | } 34 | #endif 35 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/watchos-arm64_arm64_32_armv7k/PreviewsSupport.framework/Modules/PreviewsSupport.swiftmodule/arm64_32-apple-watchos.swiftinterface: -------------------------------------------------------------------------------- 1 | // swift-interface-format-version: 1.0 2 | // swift-compiler-version: Apple Swift version 6.0 effective-5.10 (swiftlang-6.0.0.9.10 clang-1600.0.26.2) 3 | // swift-module-flags: -target arm64_32-apple-watchos8.0 -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -enable-bare-slash-regex -module-name PreviewsSupport 4 | // swift-module-flags-ignorable: -no-verify-emitted-module-interface 5 | @_exported import PreviewsSupport 6 | import Swift 7 | import SwiftUI 8 | import UIKit 9 | import _Concurrency 10 | import _StringProcessing 11 | import _SwiftConcurrencyShims 12 | public protocol MakeViewProvider { 13 | var makeView: @_Concurrency.MainActor () -> any SwiftUI.View { get } 14 | } 15 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/watchos-arm64_x86_64-simulator/PreviewsSupport.framework/Modules/PreviewsSupport.swiftmodule/arm64-apple-watchos-simulator.swiftinterface: -------------------------------------------------------------------------------- 1 | // swift-interface-format-version: 1.0 2 | // swift-compiler-version: Apple Swift version 6.0 effective-5.10 (swiftlang-6.0.0.9.10 clang-1600.0.26.2) 3 | // swift-module-flags: -target arm64-apple-watchos8.0-simulator -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -enable-bare-slash-regex -module-name PreviewsSupport 4 | // swift-module-flags-ignorable: -no-verify-emitted-module-interface 5 | @_exported import PreviewsSupport 6 | import Swift 7 | import SwiftUI 8 | import UIKit 9 | import _Concurrency 10 | import _StringProcessing 11 | import _SwiftConcurrencyShims 12 | public protocol MakeViewProvider { 13 | var makeView: @_Concurrency.MainActor () -> any SwiftUI.View { get } 14 | } 15 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/watchos-arm64_x86_64-simulator/PreviewsSupport.framework/Modules/PreviewsSupport.swiftmodule/x86_64-apple-watchos-simulator.swiftinterface: -------------------------------------------------------------------------------- 1 | // swift-interface-format-version: 1.0 2 | // swift-compiler-version: Apple Swift version 6.0 effective-5.10 (swiftlang-6.0.0.9.10 clang-1600.0.26.2) 3 | // swift-module-flags: -target x86_64-apple-watchos8.0-simulator -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -enable-bare-slash-regex -module-name PreviewsSupport 4 | // swift-module-flags-ignorable: -no-verify-emitted-module-interface 5 | @_exported import PreviewsSupport 6 | import Swift 7 | import SwiftUI 8 | import UIKit 9 | import _Concurrency 10 | import _StringProcessing 11 | import _SwiftConcurrencyShims 12 | public protocol MakeViewProvider { 13 | var makeView: @_Concurrency.MainActor () -> any SwiftUI.View { get } 14 | } 15 | -------------------------------------------------------------------------------- /Examples/DemoApp/DemoApp/TestViews/ArrayBuilder.swift: -------------------------------------------------------------------------------- 1 | @resultBuilder 2 | struct ArrayBuilder { 3 | static func buildPartialBlock(first: Element) -> [Element] { [first] } 4 | static func buildPartialBlock(first: [Element]) -> [Element] { first } 5 | static func buildPartialBlock(accumulated: [Element], next: Element) -> [Element] { accumulated + [next] } 6 | static func buildPartialBlock(accumulated: [Element], next: [Element]) -> [Element] { accumulated + next } 7 | 8 | // Empty Case 9 | static func buildBlock() -> [Element] { [] } 10 | // If/Else 11 | static func buildEither(first: [Element]) -> [Element] { first } 12 | static func buildEither(second: [Element]) -> [Element] { second } 13 | // Just ifs 14 | static func buildIf(_ element: [Element]?) -> [Element] { element ?? [] } 15 | // fatalError() 16 | static func buildPartialBlock(first: Never) -> [Element] {} 17 | } 18 | -------------------------------------------------------------------------------- /Examples/DemoApp/DemoApp/TestViews/StateView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StateView.swift 3 | // DemoApp 4 | // 5 | // Created by Noah Martin on 8/22/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | struct StateView: View { 12 | @State var state = false 13 | 14 | var body: some View { 15 | VStack { 16 | Text("Hello") 17 | Text("World") 18 | if state { 19 | Text("State set in root") 20 | } else { 21 | Text("State not set in root") 22 | } 23 | SubView(state: state) 24 | }.onAppear { 25 | state = true 26 | } 27 | } 28 | } 29 | 30 | struct SubView: View { 31 | var state = false 32 | 33 | var body: some View { 34 | if state { 35 | Text("State set in subview") 36 | } else { 37 | Text("State not set in subview") 38 | } 39 | } 40 | } 41 | 42 | struct StateView_Previews: PreviewProvider { 43 | static var previews: some View { 44 | StateView() 45 | StateView() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Examples/DemoApp/DemoApp/TestViews/UIViewPreviews.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewPreviews.swift 3 | // DemoModule 4 | // 5 | // Created by Noah Martin on 10/9/23. 6 | // 7 | 8 | #if canImport(UIKit) 9 | import Foundation 10 | import UIKit 11 | 12 | @available(iOS 17.0, *) 13 | #Preview("View test") { 14 | let label = UILabel() 15 | label.text = "Hello world" 16 | return label 17 | } 18 | 19 | class TestViewController: UIViewController { 20 | let label = UILabel() 21 | 22 | override func viewDidLoad() { 23 | super.viewDidLoad() 24 | label.text = "Hello world" 25 | label.translatesAutoresizingMaskIntoConstraints = false 26 | view.addSubview(label) 27 | label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true 28 | label.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true 29 | } 30 | } 31 | 32 | @available(iOS 17.0, *) 33 | #Preview("View controller test", body: { 34 | return TestViewController() as UIViewController 35 | }) 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /Examples/DemoApp/DemoApp/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // DemoApp 4 | // 5 | // Created by Noah Martin on 7/3/23. 6 | // 7 | 8 | import SwiftUI 9 | import DemoModule 10 | import PreviewGallery 11 | 12 | struct ContentView: View { 13 | var body: some View { 14 | NavigationStack { 15 | VStack { 16 | NavigationLink("Open Gallery") { 17 | LazyView(PreviewGallery()) 18 | } 19 | Image(systemName: "globe") 20 | .imageScale(.large) 21 | .foregroundStyle(.tint) 22 | Text("Hello, world!") 23 | } 24 | .padding() 25 | } 26 | } 27 | } 28 | 29 | struct LazyView: View { 30 | private let build: () -> Content 31 | init(_ build: @autoclosure @escaping () -> Content) { 32 | self.build = build 33 | } 34 | var body: Content { 35 | build() 36 | } 37 | } 38 | 39 | struct ContentView_Previews: PreviewProvider { 40 | 41 | static var previews: some View { 42 | ContentView() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Examples/DemoApp/DemoApp/TestViews/CodeEntryView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CodeEntryView.swift 3 | // DemoApp 4 | // 5 | // Created by Noah Martin on 7/27/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | struct CodeEntryView: View { 12 | var body: some View { 13 | HStack(spacing: 10) { 14 | ForEach(0..<6, id: \.self) { index in 15 | CodeDigitView() 16 | .frame(height: 40) 17 | } 18 | } 19 | } 20 | } 21 | 22 | struct CodeDigitView: View { 23 | @State var size: CGFloat = 0 24 | 25 | var body: some View { 26 | Rectangle().frame(width: size).background { 27 | GeometryReader { geometry in 28 | Color.clear.onAppear { 29 | size = geometry.size.height 30 | } 31 | } 32 | } 33 | } 34 | } 35 | 36 | struct CodeEntryView_Previews: PreviewProvider { 37 | static var previews: some View { 38 | CodeEntryView() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/SnapshotPreviewsCore/Orientation.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) && !os(watchOS) && !os(visionOS) && !os(tvOS) 2 | import Foundation 3 | import UIKit 4 | import SwiftUI 5 | 6 | extension UIInterfaceOrientation { 7 | func toInterfaceOrientationMask() -> UIInterfaceOrientationMask { 8 | switch self { 9 | case .portraitUpsideDown: 10 | return .portraitUpsideDown 11 | case .landscapeLeft: 12 | return .landscapeLeft 13 | case .landscapeRight: 14 | return .landscapeRight 15 | default: 16 | return .portrait 17 | } 18 | } 19 | } 20 | 21 | extension InterfaceOrientation { 22 | func toInterfaceOrientation() -> UIInterfaceOrientation { 23 | switch self { 24 | case .portraitUpsideDown: 25 | return .portraitUpsideDown 26 | case .landscapeLeft: 27 | return .landscapeLeft 28 | case .landscapeRight: 29 | return .landscapeRight 30 | default: 31 | return .portrait 32 | } 33 | } 34 | } 35 | #endif 36 | -------------------------------------------------------------------------------- /Sources/SnapshotSharedModels/RenderingMode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmergeRenderingMode.swift 3 | // 4 | // 5 | // Created by Noah Martin on 10/6/23. 6 | // 7 | 8 | import Foundation 9 | 10 | 11 | /// Specifies the rendering mode for the Emerge framework. 12 | /// 13 | /// This enum defines different methods for rendering views, 14 | /// allowing you to choose between Core Animation and UIView-based rendering. 15 | public enum EmergeRenderingMode: Int { 16 | /// Renders using `CALayer.render(in:)`. 17 | case coreAnimation 18 | 19 | /// Renders using `UIView.drawHierarchy(in:afterScreenUpdates:true)`. 20 | @available(macOS, unavailable) 21 | case uiView 22 | 23 | /// Renders using `NSView.bitmapImageRepForCachingDisplay`. 24 | @available(iOS, unavailable) 25 | case nsView 26 | 27 | /// Renders the entire window instead of the previewed view. 28 | /// This uses `UIWindow.drawHierarchy(in: window.bounds, afterScreenUpdates: true)` on iOS 29 | /// This uses `CGWindowListCreateImage` on macOS. 30 | case window 31 | } 32 | -------------------------------------------------------------------------------- /Sources/SnapshotPreviewsCore/PreferredColorSchemeWrapper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Noah Martin on 10/11/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | public struct PreferredColorSchemeWrapper: View { 12 | 13 | @State var preferredColorScheme: ColorScheme? = nil 14 | @Environment(\.colorScheme) var colorScheme 15 | 16 | let content: Content 17 | let colorSchemeUpdater: ((ColorScheme?) -> Void)? 18 | 19 | public init(@ViewBuilder _ content: () -> Content, colorSchemeUpdater: ((ColorScheme?) -> Void)? = nil) { 20 | self.content = content() 21 | self.colorSchemeUpdater = colorSchemeUpdater 22 | } 23 | 24 | public var body: some View { 25 | content 26 | .onPreferenceChange(PreferredColorSchemeKey.self, perform: { value in 27 | preferredColorScheme = value 28 | colorSchemeUpdater?(value) 29 | }) 30 | .environment(\.colorScheme, preferredColorScheme ?? colorScheme) 31 | .preferredColorScheme(nil) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/SnapshotPreviewsCore/UIViewWrapper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Noah Martin on 7/3/23. 6 | // 7 | 8 | #if canImport(UIKit) && !os(watchOS) 9 | import Foundation 10 | import UIKit 11 | import SwiftUI 12 | 13 | struct UIViewControllerWrapper: UIViewControllerRepresentable { 14 | let builder: @MainActor () -> UIViewController 15 | 16 | init(_ builder: @escaping @MainActor () -> UIViewController) { 17 | self.builder = builder 18 | } 19 | 20 | func makeUIViewController(context: Context) -> UIViewController { 21 | builder() 22 | } 23 | 24 | func updateUIViewController(_ uiViewController: UIViewController, context: Context) { } 25 | } 26 | 27 | struct UIViewWrapper: UIViewRepresentable { 28 | 29 | let builder: @MainActor () -> UIView 30 | 31 | init(_ builder: @escaping @MainActor () -> UIView) { 32 | self.builder = builder 33 | } 34 | 35 | func makeUIView(context: Context) -> UIView { 36 | builder() 37 | } 38 | 39 | func updateUIView(_ uiView: UIView, context: Context) { } 40 | } 41 | #endif 42 | -------------------------------------------------------------------------------- /Examples/DemoApp/DemoApp/TestViews/OSVersionView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OSVersionView.swift 3 | // DemoApp 4 | // 5 | // Created by Noah Martin on 9/4/24. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | struct OSVersionView: View { 12 | var body: some View { 13 | VStack { 14 | Text("Current OS Version:") 15 | .font(.headline) 16 | Text(getOSVersion()) 17 | .font(.body) 18 | .padding() 19 | } 20 | .padding() 21 | } 22 | 23 | func getOSVersion() -> String { 24 | #if os(iOS) 25 | return UIDevice.current.systemVersion 26 | #elseif os(macOS) 27 | let osVersion = ProcessInfo.processInfo.operatingSystemVersion 28 | return "\(osVersion.majorVersion).\(osVersion.minorVersion).\(osVersion.patchVersion)" 29 | #else 30 | return "Unsupported OS" 31 | #endif 32 | } 33 | } 34 | 35 | struct OSVersionView_Previews: PreviewProvider { 36 | static var previews: some View { 37 | OSVersionView() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/ios-arm64/PreviewsSupport.framework/Modules/PreviewsSupport.swiftmodule/arm64-apple-ios.swiftinterface: -------------------------------------------------------------------------------- 1 | // swift-interface-format-version: 1.0 2 | // swift-compiler-version: Apple Swift version 6.0 effective-5.10 (swiftlang-6.0.0.9.10 clang-1600.0.26.2) 3 | // swift-module-flags: -target arm64-apple-ios15.0 -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -enable-bare-slash-regex -module-name PreviewsSupport 4 | // swift-module-flags-ignorable: -no-verify-emitted-module-interface 5 | @_exported import PreviewsSupport 6 | import Swift 7 | import SwiftUI 8 | import UIKit 9 | import _Concurrency 10 | import _StringProcessing 11 | import _SwiftConcurrencyShims 12 | public protocol MakeViewProvider { 13 | var makeView: @_Concurrency.MainActor () -> any SwiftUI.View { get } 14 | } 15 | public protocol MakeUIViewProvider { 16 | var makeView: @_Concurrency.MainActor () -> UIKit.UIView { get } 17 | } 18 | public protocol MakeViewControllerProvider { 19 | var makeViewController: @_Concurrency.MainActor () -> UIKit.UIViewController { get } 20 | } 21 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/xros-arm64/PreviewsSupport.framework/Modules/PreviewsSupport.swiftmodule/arm64-apple-xros.swiftinterface: -------------------------------------------------------------------------------- 1 | // swift-interface-format-version: 1.0 2 | // swift-compiler-version: Apple Swift version 6.0 effective-5.10 (swiftlang-6.0.0.9.10 clang-1600.0.26.2) 3 | // swift-module-flags: -target arm64-apple-xros2.0 -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -enable-bare-slash-regex -module-name PreviewsSupport 4 | // swift-module-flags-ignorable: -no-verify-emitted-module-interface 5 | @_exported import PreviewsSupport 6 | import Swift 7 | import SwiftUI 8 | import UIKit 9 | import _Concurrency 10 | import _StringProcessing 11 | import _SwiftConcurrencyShims 12 | public protocol MakeViewProvider { 13 | var makeView: @_Concurrency.MainActor () -> any SwiftUI.View { get } 14 | } 15 | public protocol MakeUIViewProvider { 16 | var makeView: @_Concurrency.MainActor () -> UIKit.UIView { get } 17 | } 18 | public protocol MakeViewControllerProvider { 19 | var makeViewController: @_Concurrency.MainActor () -> UIKit.UIViewController { get } 20 | } 21 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/tvos-arm64/PreviewsSupport.framework/Modules/PreviewsSupport.swiftmodule/arm64-apple-tvos.swiftinterface: -------------------------------------------------------------------------------- 1 | // swift-interface-format-version: 1.0 2 | // swift-compiler-version: Apple Swift version 6.0 effective-5.10 (swiftlang-6.0.0.9.10 clang-1600.0.26.2) 3 | // swift-module-flags: -target arm64-apple-tvos15.0 -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -enable-bare-slash-regex -module-name PreviewsSupport 4 | // swift-module-flags-ignorable: -no-verify-emitted-module-interface 5 | @_exported import PreviewsSupport 6 | import Swift 7 | import SwiftUI 8 | import UIKit 9 | import _Concurrency 10 | import _StringProcessing 11 | import _SwiftConcurrencyShims 12 | public protocol MakeViewProvider { 13 | var makeView: @_Concurrency.MainActor () -> any SwiftUI.View { get } 14 | } 15 | public protocol MakeUIViewProvider { 16 | var makeView: @_Concurrency.MainActor () -> UIKit.UIView { get } 17 | } 18 | public protocol MakeViewControllerProvider { 19 | var makeViewController: @_Concurrency.MainActor () -> UIKit.UIViewController { get } 20 | } 21 | -------------------------------------------------------------------------------- /Examples/DemoApp/DemoModule/MessageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MessageView.swift 3 | // DemoModule 4 | // 5 | // Created by Noah Martin on 7/3/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct MessagingView: View { 11 | 12 | @State private var newMessage = "" 13 | 14 | var body: some View { 15 | 16 | HStack { 17 | TextField("Type your message...", text: $newMessage) 18 | .textFieldStyle(RoundedBorderTextFieldStyle()) 19 | .padding(.horizontal) 20 | 21 | Button(action: sendMessage) { 22 | Image(systemName: "paperplane.fill") 23 | .font(.title) 24 | .foregroundColor(.blue) 25 | } 26 | .padding(.trailing) 27 | } 28 | .padding(.vertical) 29 | } 30 | 31 | func sendMessage() { 32 | if !newMessage.isEmpty { 33 | newMessage = "" 34 | } 35 | } 36 | } 37 | 38 | struct MessageView_Previews: PreviewProvider { 39 | static var previews: some View { 40 | MessagingView() 41 | .previewLayout(.sizeThatFits) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Emerge Tools 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Sources/SnapshotPreviewsCore/ViewSelector.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewSelector.swift 3 | // 4 | // 5 | // Created by Noah Martin on 7/6/23. 6 | // 7 | 8 | import SwiftUI 9 | import Combine 10 | 11 | fileprivate struct ViewSelector: _VariadicView_MultiViewRoot { 12 | let position: Int 13 | func body(children: _VariadicView.Children) -> some View { 14 | children[position] 15 | } 16 | } 17 | 18 | public class SnapshotViewModel: ObservableObject { 19 | @Published public var index: Int 20 | 21 | init(index: Int) { 22 | self.index = index 23 | } 24 | } 25 | 26 | public protocol SnapshotViewModelProviding: View { 27 | var viewModel: SnapshotViewModel { get } 28 | } 29 | 30 | struct ViewSelectorTree: SnapshotViewModelProviding { 31 | 32 | @ObservedObject var viewModel: SnapshotViewModel 33 | let content: Content 34 | 35 | init(_ model: SnapshotViewModel, @ViewBuilder _ content: () -> Content) { 36 | self.viewModel = model 37 | self.content = content() 38 | } 39 | 40 | var body: some View { 41 | _VariadicView.Tree(ViewSelector(position: viewModel.index)) { 42 | content 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/ios-arm64_x86_64-simulator/PreviewsSupport.framework/Modules/PreviewsSupport.swiftmodule/arm64-apple-ios-simulator.swiftinterface: -------------------------------------------------------------------------------- 1 | // swift-interface-format-version: 1.0 2 | // swift-compiler-version: Apple Swift version 6.0 effective-5.10 (swiftlang-6.0.0.9.10 clang-1600.0.26.2) 3 | // swift-module-flags: -target arm64-apple-ios15.0-simulator -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -enable-bare-slash-regex -module-name PreviewsSupport 4 | // swift-module-flags-ignorable: -no-verify-emitted-module-interface 5 | @_exported import PreviewsSupport 6 | import Swift 7 | import SwiftUI 8 | import UIKit 9 | import _Concurrency 10 | import _StringProcessing 11 | import _SwiftConcurrencyShims 12 | public protocol MakeViewProvider { 13 | var makeView: @_Concurrency.MainActor () -> any SwiftUI.View { get } 14 | } 15 | public protocol MakeUIViewProvider { 16 | var makeView: @_Concurrency.MainActor () -> UIKit.UIView { get } 17 | } 18 | public protocol MakeViewControllerProvider { 19 | var makeViewController: @_Concurrency.MainActor () -> UIKit.UIViewController { get } 20 | } 21 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/ios-arm64_x86_64-simulator/PreviewsSupport.framework/Modules/PreviewsSupport.swiftmodule/x86_64-apple-ios-simulator.swiftinterface: -------------------------------------------------------------------------------- 1 | // swift-interface-format-version: 1.0 2 | // swift-compiler-version: Apple Swift version 6.0 effective-5.10 (swiftlang-6.0.0.9.10 clang-1600.0.26.2) 3 | // swift-module-flags: -target x86_64-apple-ios15.0-simulator -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -enable-bare-slash-regex -module-name PreviewsSupport 4 | // swift-module-flags-ignorable: -no-verify-emitted-module-interface 5 | @_exported import PreviewsSupport 6 | import Swift 7 | import SwiftUI 8 | import UIKit 9 | import _Concurrency 10 | import _StringProcessing 11 | import _SwiftConcurrencyShims 12 | public protocol MakeViewProvider { 13 | var makeView: @_Concurrency.MainActor () -> any SwiftUI.View { get } 14 | } 15 | public protocol MakeUIViewProvider { 16 | var makeView: @_Concurrency.MainActor () -> UIKit.UIView { get } 17 | } 18 | public protocol MakeViewControllerProvider { 19 | var makeViewController: @_Concurrency.MainActor () -> UIKit.UIViewController { get } 20 | } 21 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/tvos-arm64_x86_64-simulator/PreviewsSupport.framework/Modules/PreviewsSupport.swiftmodule/arm64-apple-tvos-simulator.swiftinterface: -------------------------------------------------------------------------------- 1 | // swift-interface-format-version: 1.0 2 | // swift-compiler-version: Apple Swift version 6.0 effective-5.10 (swiftlang-6.0.0.9.10 clang-1600.0.26.2) 3 | // swift-module-flags: -target arm64-apple-tvos15.0-simulator -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -enable-bare-slash-regex -module-name PreviewsSupport 4 | // swift-module-flags-ignorable: -no-verify-emitted-module-interface 5 | @_exported import PreviewsSupport 6 | import Swift 7 | import SwiftUI 8 | import UIKit 9 | import _Concurrency 10 | import _StringProcessing 11 | import _SwiftConcurrencyShims 12 | public protocol MakeViewProvider { 13 | var makeView: @_Concurrency.MainActor () -> any SwiftUI.View { get } 14 | } 15 | public protocol MakeUIViewProvider { 16 | var makeView: @_Concurrency.MainActor () -> UIKit.UIView { get } 17 | } 18 | public protocol MakeViewControllerProvider { 19 | var makeViewController: @_Concurrency.MainActor () -> UIKit.UIViewController { get } 20 | } 21 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/tvos-arm64_x86_64-simulator/PreviewsSupport.framework/Modules/PreviewsSupport.swiftmodule/x86_64-apple-tvos-simulator.swiftinterface: -------------------------------------------------------------------------------- 1 | // swift-interface-format-version: 1.0 2 | // swift-compiler-version: Apple Swift version 6.0 effective-5.10 (swiftlang-6.0.0.9.10 clang-1600.0.26.2) 3 | // swift-module-flags: -target x86_64-apple-tvos15.0-simulator -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -enable-bare-slash-regex -module-name PreviewsSupport 4 | // swift-module-flags-ignorable: -no-verify-emitted-module-interface 5 | @_exported import PreviewsSupport 6 | import Swift 7 | import SwiftUI 8 | import UIKit 9 | import _Concurrency 10 | import _StringProcessing 11 | import _SwiftConcurrencyShims 12 | public protocol MakeViewProvider { 13 | var makeView: @_Concurrency.MainActor () -> any SwiftUI.View { get } 14 | } 15 | public protocol MakeUIViewProvider { 16 | var makeView: @_Concurrency.MainActor () -> UIKit.UIView { get } 17 | } 18 | public protocol MakeViewControllerProvider { 19 | var makeViewController: @_Concurrency.MainActor () -> UIKit.UIViewController { get } 20 | } 21 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/xros-arm64_x86_64-simulator/PreviewsSupport.framework/Modules/PreviewsSupport.swiftmodule/arm64-apple-xros-simulator.swiftinterface: -------------------------------------------------------------------------------- 1 | // swift-interface-format-version: 1.0 2 | // swift-compiler-version: Apple Swift version 6.0 effective-5.10 (swiftlang-6.0.0.9.10 clang-1600.0.26.2) 3 | // swift-module-flags: -target arm64-apple-xros2.0-simulator -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -enable-bare-slash-regex -module-name PreviewsSupport 4 | // swift-module-flags-ignorable: -no-verify-emitted-module-interface 5 | @_exported import PreviewsSupport 6 | import Swift 7 | import SwiftUI 8 | import UIKit 9 | import _Concurrency 10 | import _StringProcessing 11 | import _SwiftConcurrencyShims 12 | public protocol MakeViewProvider { 13 | var makeView: @_Concurrency.MainActor () -> any SwiftUI.View { get } 14 | } 15 | public protocol MakeUIViewProvider { 16 | var makeView: @_Concurrency.MainActor () -> UIKit.UIView { get } 17 | } 18 | public protocol MakeViewControllerProvider { 19 | var makeViewController: @_Concurrency.MainActor () -> UIKit.UIViewController { get } 20 | } 21 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/xros-arm64_x86_64-simulator/PreviewsSupport.framework/Modules/PreviewsSupport.swiftmodule/x86_64-apple-xros-simulator.swiftinterface: -------------------------------------------------------------------------------- 1 | // swift-interface-format-version: 1.0 2 | // swift-compiler-version: Apple Swift version 6.0 effective-5.10 (swiftlang-6.0.0.9.10 clang-1600.0.26.2) 3 | // swift-module-flags: -target x86_64-apple-xros2.0-simulator -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -enable-bare-slash-regex -module-name PreviewsSupport 4 | // swift-module-flags-ignorable: -no-verify-emitted-module-interface 5 | @_exported import PreviewsSupport 6 | import Swift 7 | import SwiftUI 8 | import UIKit 9 | import _Concurrency 10 | import _StringProcessing 11 | import _SwiftConcurrencyShims 12 | public protocol MakeViewProvider { 13 | var makeView: @_Concurrency.MainActor () -> any SwiftUI.View { get } 14 | } 15 | public protocol MakeUIViewProvider { 16 | var makeView: @_Concurrency.MainActor () -> UIKit.UIView { get } 17 | } 18 | public protocol MakeViewControllerProvider { 19 | var makeViewController: @_Concurrency.MainActor () -> UIKit.UIViewController { get } 20 | } 21 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/ios-arm64_x86_64-maccatalyst/PreviewsSupport.framework/Versions/A/Modules/PreviewsSupport.swiftmodule/arm64-apple-ios-macabi.swiftinterface: -------------------------------------------------------------------------------- 1 | // swift-interface-format-version: 1.0 2 | // swift-compiler-version: Apple Swift version 6.0 effective-5.10 (swiftlang-6.0.0.9.10 clang-1600.0.26.2) 3 | // swift-module-flags: -target arm64-apple-ios15.0-macabi -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -enable-bare-slash-regex -module-name PreviewsSupport 4 | // swift-module-flags-ignorable: -no-verify-emitted-module-interface 5 | @_exported import PreviewsSupport 6 | import Swift 7 | import SwiftUI 8 | import UIKit 9 | import _Concurrency 10 | import _StringProcessing 11 | import _SwiftConcurrencyShims 12 | public protocol MakeViewProvider { 13 | var makeView: @_Concurrency.MainActor () -> any SwiftUI.View { get } 14 | } 15 | public protocol MakeUIViewProvider { 16 | var makeView: @_Concurrency.MainActor () -> UIKit.UIView { get } 17 | } 18 | public protocol MakeViewControllerProvider { 19 | var makeViewController: @_Concurrency.MainActor () -> UIKit.UIViewController { get } 20 | } 21 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/ios-arm64_x86_64-maccatalyst/PreviewsSupport.framework/Versions/A/Modules/PreviewsSupport.swiftmodule/x86_64-apple-ios-macabi.swiftinterface: -------------------------------------------------------------------------------- 1 | // swift-interface-format-version: 1.0 2 | // swift-compiler-version: Apple Swift version 6.0 effective-5.10 (swiftlang-6.0.0.9.10 clang-1600.0.26.2) 3 | // swift-module-flags: -target x86_64-apple-ios15.0-macabi -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -enable-bare-slash-regex -module-name PreviewsSupport 4 | // swift-module-flags-ignorable: -no-verify-emitted-module-interface 5 | @_exported import PreviewsSupport 6 | import Swift 7 | import SwiftUI 8 | import UIKit 9 | import _Concurrency 10 | import _StringProcessing 11 | import _SwiftConcurrencyShims 12 | public protocol MakeViewProvider { 13 | var makeView: @_Concurrency.MainActor () -> any SwiftUI.View { get } 14 | } 15 | public protocol MakeUIViewProvider { 16 | var makeView: @_Concurrency.MainActor () -> UIKit.UIView { get } 17 | } 18 | public protocol MakeViewControllerProvider { 19 | var makeViewController: @_Concurrency.MainActor () -> UIKit.UIViewController { get } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/SnapshottingTests/PreviewFilters.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PreviewFilters.swift 3 | // 4 | // 5 | // Created by Noah Martin on 8/9/24. 6 | // 7 | 8 | import Foundation 9 | 10 | public protocol PreviewFilters { 11 | /// Override to return a list of previews that should be snapshotted. The default is null, which snapshots all previews. 12 | /// Elements should be the type name of the preview, like "MyModule.MyView_Previews". This also supports Regex format. 13 | /// 14 | /// Override this method to specify which previews should be included in the snapshot test. 15 | /// - Returns: An optional array of String containing the names of previews to be included. 16 | static func snapshotPreviews() -> [String]? 17 | 18 | /// Override to return a list of previews that should NOT be snapshotted. The default is null, which excludes none. 19 | /// Elements should be the type name of the preview, like "MyModule.MyView_Previews". This also supports Regex format. 20 | /// 21 | /// Override this method to specify which previews should be excluded from the snapshot test. 22 | /// - Returns: An optional array of String containing the names of previews to be excluded. 23 | static func excludedSnapshotPreviews() -> [String]? 24 | } 25 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release workflow 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | release: 10 | runs-on: macos-14 11 | 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v2 15 | - name: Xcode select 16 | run: sudo xcode-select -s '/Applications/Xcode_15.4.app/Contents/Developer' 17 | - name: Build xcframework 18 | run: sh build.sh 19 | - name: Zip SnapshottingTests xcframework 20 | run: zip -r SnapshottingTests.xcframework.zip SnapshottingTests.xcframework 21 | - name: Zip PreviewGallery xcframework 22 | run: zip -r PreviewGallery.xcframework.zip PreviewGallery.xcframework 23 | - name: Zip preivews support 24 | run: (cd PreviewsSupport && zip -r PreviewsSupport.xcframework.zip PreviewsSupport.xcframework) 25 | - name: Upload Artifact 26 | uses: softprops/action-gh-release@v1 27 | if: startsWith(github.ref, 'refs/tags/') 28 | with: 29 | files: | 30 | PreviewGallery.xcframework.zip 31 | SnapshottingTests.xcframework.zip 32 | PreviewsSupport/PreviewsSupport.xcframework.zip 33 | body: 34 | Release ${{ github.ref }} 35 | Automated release created by GitHub Actions. -------------------------------------------------------------------------------- /Examples/DemoApp/DemoModule/ButtonView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ButtonView.swift 3 | // DemoModule 4 | // 5 | // Created by Noah Martin on 7/3/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ButtonView: View { 11 | var title: String 12 | var background: T 13 | var action: () -> Void 14 | 15 | var body: some View { 16 | Button(action: action) { 17 | Text(title) 18 | .font(.headline) 19 | .foregroundColor(.white) 20 | .padding() 21 | .background(background) 22 | .cornerRadius(8) 23 | }.padding() 24 | } 25 | } 26 | 27 | struct ButtonView_Previews: PreviewProvider { 28 | static var previews: some View { 29 | Group { 30 | ButtonView(title: "Click Me", background: Color.blue) { 31 | print("Button clicked") 32 | } 33 | .previewDisplayName("ButtonView - Solid") 34 | .previewLayout(.sizeThatFits) 35 | 36 | ButtonView(title: "Click Me", background: LinearGradient(gradient: Gradient(colors: [Color.blue, Color.green]), startPoint: .leading, endPoint: .trailing)) { 37 | print("Button clicked") 38 | } 39 | .previewDisplayName("ButtonView - Gradient") 40 | .previewLayout(.sizeThatFits) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/PreviewGallery/Colors.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Colors.swift 3 | // 4 | // 5 | // Created by Noah Martin on 12/11/23. 6 | // 7 | 8 | import Foundation 9 | 10 | #if canImport(UIKit) 11 | import UIKit 12 | typealias PlatformColor = UIColor 13 | 14 | extension PlatformColor { 15 | #if os(watchOS) 16 | static var label: UIColor { 17 | UIColor.white 18 | } 19 | static var secondaryLabel: UIColor { 20 | UIColor.gray 21 | } 22 | #endif 23 | #if os(tvOS) || os(watchOS) 24 | static var gallerySecondaryBackground: UIColor { 25 | UIColor.lightGray 26 | } 27 | 28 | static var galleryBackground: UIColor { 29 | UIColor.lightGray 30 | } 31 | #else 32 | static var gallerySecondaryBackground: UIColor { 33 | UIColor.secondarySystemGroupedBackground 34 | } 35 | 36 | static var galleryBackground: UIColor { 37 | UIColor.systemGroupedBackground 38 | } 39 | #endif 40 | } 41 | 42 | #else 43 | import AppKit 44 | 45 | typealias PlatformColor = NSColor 46 | 47 | extension NSColor { 48 | static var label: NSColor { 49 | NSColor.labelColor 50 | } 51 | 52 | static var secondaryLabel: NSColor { 53 | NSColor.secondaryLabelColor 54 | } 55 | 56 | static var gallerySecondaryBackground: NSColor { 57 | NSColor.controlBackgroundColor 58 | } 59 | 60 | static var galleryBackground: NSColor { 61 | NSColor.windowBackgroundColor 62 | } 63 | } 64 | #endif 65 | -------------------------------------------------------------------------------- /Sources/SnapshotPreferences/ExpansionPreference.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExpansionPreference.swift 3 | // 4 | // 5 | // Created by Noah Martin on 9/7/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | struct ExpansionPreferenceKey: PreferenceKey { 12 | static func reduce(value: inout Bool?, nextValue: () -> Bool?) { 13 | if value == nil { 14 | value = nextValue() 15 | } 16 | } 17 | 18 | static var defaultValue: Bool? = nil 19 | } 20 | 21 | extension View { 22 | /// Applies an expansion effect to the view's snapshot. 23 | /// 24 | /// Use this method to control the emerge expansion effect on a view. When enabled, 25 | /// the view's first scrollview will be expanded to show all content in the snapshot. 26 | /// 27 | /// - Parameter enabled: A Boolean value that determines whether the emerge expansion 28 | /// effect is applied. If `nil`, the effect will default to `true`. 29 | /// 30 | /// - Returns: A view with the expansion preference applied. 31 | /// 32 | /// # Example 33 | /// ```swift 34 | /// struct ContentView: View { 35 | /// var body: some View { 36 | /// Text("Hello, World!") 37 | /// .emergeExpansion(false) 38 | /// } 39 | /// } 40 | /// ``` 41 | public func emergeExpansion(_ enabled: Bool?) -> some View { 42 | preference(key: ExpansionPreferenceKey.self, value: enabled) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/PreviewGallery/PreviewsDetail.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PreviewsDetail.swift 3 | // 4 | // 5 | // Created by Noah Martin on 8/18/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | import SnapshotPreviewsCore 11 | 12 | struct PreviewsDetail: View { 13 | 14 | let previewGrouping: PreviewGrouping 15 | @State private var searchText = "" 16 | 17 | var previews: [SnapshotPreviewsCore.Preview] { 18 | previewGrouping.previews.flatMap { $0.previews(requiringFullscreen: false) }.filterWithText(searchText, { $0.displayName ?? "" }) 19 | } 20 | 21 | var body: some View { 22 | ScrollView { 23 | VStack(alignment: .leading, spacing: 12) { 24 | ForEach(previews) { preview in 25 | VStack(alignment: .center) { 26 | Text(preview.displayName ?? "Preview") 27 | .font(.headline) 28 | .foregroundStyle(Color(PlatformColor.label)) 29 | PreviewCell(preview: preview) 30 | } 31 | .padding(.vertical, 16) 32 | #if canImport(UIKit) && !os(visionOS) && !os(watchOS) 33 | .frame(width: UIScreen.main.bounds.width) 34 | #endif 35 | #if !os(watchOS) 36 | .background(Color(PlatformColor.gallerySecondaryBackground)) 37 | #endif 38 | } 39 | } 40 | } 41 | #if !os(watchOS) 42 | .background(Color(PlatformColor.galleryBackground)) 43 | #endif 44 | .navigationTitle(previewGrouping.displayName) 45 | .searchable(text: $searchText) 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /Examples/DemoApp/Demo Watch App/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // Demo Watch App 4 | // 5 | // Created by Noah Martin on 7/5/24. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ContentView: View { 11 | var body: some View { 12 | VStack { 13 | Image(systemName: "globe") 14 | .imageScale(.large) 15 | .foregroundStyle(.tint) 16 | Text("Hello, world!") 17 | } 18 | .padding() 19 | } 20 | } 21 | 22 | #Preview { 23 | ContentView() 24 | } 25 | 26 | struct ExerciseButtonView: View { 27 | var imageName: String 28 | 29 | var body: some View { 30 | HStack { 31 | Image(systemName: imageName) 32 | .font(.title) 33 | Text("Start Workout") 34 | .font(.headline) 35 | } 36 | .foregroundColor(.white) 37 | .padding() 38 | .background(Color.green) 39 | .cornerRadius(10) 40 | } 41 | } 42 | 43 | struct ExerciseButtonView_Previews: PreviewProvider { 44 | static var previews: some View { 45 | Group { 46 | ExerciseButtonView(imageName: "figure.walk.circle.fill") 47 | .previewDisplayName("Walk") 48 | .previewDevice("Apple Watch Series 6 - 40mm") 49 | ExerciseButtonView(imageName: "figure.run.circle.fill") 50 | .previewDisplayName("Run") 51 | .previewDevice("Apple Watch Series 6 - 40mm") 52 | } 53 | .previewLayout(.sizeThatFits) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Examples/DemoApp/DemoModule/FeatureCardView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FeatureCardView.swift 3 | // DemoModule 4 | // 5 | // Created by Noah Martin on 7/3/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct FeatureCardView: View { 11 | var imageName: String 12 | var title: String 13 | var description: String 14 | 15 | var body: some View { 16 | VStack(spacing: 8) { 17 | Image(imageName) 18 | .resizable() 19 | .aspectRatio(contentMode: .fill) 20 | .frame(height: 200) 21 | .cornerRadius(12) 22 | 23 | Text(title) 24 | .font(.title) 25 | .fontWeight(.bold) 26 | .foregroundColor(.primary) 27 | .multilineTextAlignment(.center) 28 | 29 | Text(description) 30 | .font(.body) 31 | .foregroundColor(.secondary) 32 | .multilineTextAlignment(.center) 33 | } 34 | .padding() 35 | .background(Color.white) 36 | .cornerRadius(12) 37 | .shadow(color: .gray, radius: 3, x: 0, y: 2) 38 | } 39 | } 40 | 41 | struct FeatureCardView_Previews: PreviewProvider { 42 | static var previews: some View { 43 | FeatureCardView(imageName: "product-image", 44 | title: "Feature Title", 45 | description: "This is a description of the feature and its benefits.") 46 | .previewLayout(.sizeThatFits) 47 | .padding() 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Examples/DemoApp/DemoApp/TestViews/ExpandingView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExpandingView.swift 3 | // DemoApp 4 | // 5 | // Created by Noah Martin on 8/2/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | import SnapshotPreferences 11 | 12 | struct ExpandingView: View { 13 | var body: some View { 14 | List { 15 | ForEach(1..<20) { i in 16 | Section("Section \(i)") { 17 | VStack { 18 | Text("Subtitle").font(.title3) 19 | Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce interdum, tortor id dapibus elementum, nibh libero pretium ligula, eu bibendum nulla sapien sit amet eros. Etiam lobortis ornare nibh, ut sagittis massa egestas sed. Donec ullamcorper consequat neque, vestibulum fringilla ipsum bibendum eget. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nullam ligula nibh, scelerisque sit amet laoreet at, eleifend vel elit. Suspendisse et tellus justo. Fusce lobortis semper ipsum. Etiam sit amet ultrices velit. Aliquam iaculis faucibus maximus. Proin vel magna orci. Vestibulum auctor vel velit eu iaculis. Quisque est purus, facilisis id ligula vel, semper dictum nibh. Curabitur gravida, est et placerat iaculis, sapien sapien accumsan lacus, a congue libero quam quis sem.") 20 | } 21 | } 22 | } 23 | } 24 | } 25 | } 26 | 27 | struct ExpandingView_Previews: PreviewProvider { 28 | static var previews: some View { 29 | ExpandingView() 30 | ExpandingView().emergeExpansion(false) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Examples/DemoApp/DemoModule/ProductCard.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProductCard.swift 3 | // DemoModule 4 | // 5 | // Created by Noah Martin on 7/3/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ProductCardView: View { 11 | var imageName: String 12 | var productName: String 13 | var price: Double 14 | 15 | var body: some View { 16 | VStack(spacing: 8) { 17 | Image(imageName) 18 | .resizable() 19 | .aspectRatio(contentMode: .fit) 20 | .cornerRadius(8) 21 | 22 | Text(productName) 23 | .font(.headline) 24 | .lineLimit(2) 25 | 26 | Text("$\(price, specifier: "%.2f")") 27 | .font(.subheadline) 28 | .foregroundColor(.secondary) 29 | } 30 | .padding() 31 | .background(Color(PlatformColor.systemBackground)) 32 | .cornerRadius(10) 33 | .shadow(color: .secondary, radius: 3, x: 0, y: 2) 34 | } 35 | } 36 | 37 | struct ProductCardView_Previews: PreviewProvider { 38 | static var previews: some View { 39 | Group { 40 | ProductCardView(imageName: "product-image", productName: "Sample Product 1", price: 29.99) 41 | .previewLayout(.sizeThatFits) 42 | .previewDevice(.init(rawValue: "iPad Pro (11-inch)")) 43 | .preferredColorScheme(.dark) 44 | 45 | ProductCardView(imageName: "product-image", productName: "Sample Product 2", price: 49.99) 46 | .previewLayout(.sizeThatFits) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/macos-arm64_x86_64/PreviewsSupport.framework/Versions/A/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildMachineOSBuild 6 | 23G93 7 | CFBundleDevelopmentRegion 8 | en 9 | CFBundleExecutable 10 | PreviewsSupport 11 | CFBundleIdentifier 12 | com.emerge.PreviewsSupport 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | PreviewsSupport 17 | CFBundlePackageType 18 | FMWK 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSupportedPlatforms 22 | 23 | MacOSX 24 | 25 | CFBundleVersion 26 | 1 27 | DTCompiler 28 | com.apple.compilers.llvm.clang.1_0 29 | DTPlatformBuild 30 | 24A5324a 31 | DTPlatformName 32 | macosx 33 | DTPlatformVersion 34 | 15.0 35 | DTSDKBuild 36 | 24A5324a 37 | DTSDKName 38 | macosx15.0 39 | DTXcode 40 | 1600 41 | DTXcodeBuild 42 | 16A5230g 43 | LSMinimumSystemVersion 44 | 12.0 45 | 46 | 47 | -------------------------------------------------------------------------------- /Sources/SnapshotPreviewsCore/RenderingStrategy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RenderingStrategy.swift 3 | // 4 | // 5 | // Created by Noah Martin on 7/6/24. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | #if canImport(UIKit) 12 | import UIKit 13 | public typealias ImageType = UIImage 14 | #else 15 | import AppKit 16 | public typealias ImageType = NSImage 17 | #endif 18 | 19 | public enum MarkerShape { 20 | case frame(CGRect) 21 | #if canImport(UIKit) 22 | case path(UIBezierPath) 23 | #endif 24 | } 25 | 26 | public struct SnapshotResult { 27 | public init( 28 | image: Result, 29 | precision: Float?, 30 | accessibilityEnabled: Bool?, 31 | colorScheme: ColorScheme?, 32 | appStoreSnapshot: Bool?) 33 | { 34 | self.image = image 35 | self.precision = precision 36 | self.accessibilityEnabled = accessibilityEnabled 37 | self.colorScheme = colorScheme 38 | self.appStoreSnapshot = appStoreSnapshot 39 | } 40 | 41 | public let image: Result 42 | public let precision: Float? 43 | public let accessibilityEnabled: Bool? 44 | public let colorScheme: ColorScheme? 45 | public let appStoreSnapshot: Bool? 46 | } 47 | 48 | public protocol RenderingStrategy { 49 | @MainActor func render( 50 | preview: SnapshotPreviewsCore.Preview, 51 | completion: @escaping (SnapshotResult) -> Void) 52 | } 53 | 54 | private let testHandler: NSObject.Type? = NSClassFromString("EMGTestHandler") as? NSObject.Type 55 | 56 | extension RenderingStrategy { 57 | static func setup() { 58 | testHandler?.perform(NSSelectorFromString("setup")) 59 | } 60 | } 61 | 62 | -------------------------------------------------------------------------------- /Sources/SnapshotPreviewsCore/SwiftUIRenderingStrategy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftUIRenderingStrategy.swift 3 | // 4 | // 5 | // Created by Noah Martin on 7/5/24. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | @available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, visionOS 1.0, *) 12 | public class SwiftUIRenderingStrategy: RenderingStrategy { 13 | 14 | public init() { } 15 | 16 | private var colorScheme: ColorScheme? = nil 17 | 18 | @MainActor public func render( 19 | preview: SnapshotPreviewsCore.Preview, 20 | completion: @escaping (SnapshotResult) -> Void) 21 | { 22 | Self.setup() 23 | var view = preview.view() 24 | colorScheme = nil 25 | view = PreferredColorSchemeWrapper { 26 | AnyView(view) 27 | } colorSchemeUpdater: { [weak self] scheme in 28 | self?.colorScheme = scheme 29 | } 30 | let wrappedView = EmergeModifierView(wrapped: view) 31 | let renderer = ImageRenderer(content: wrappedView) 32 | #if canImport(UIKit) 33 | let image = renderer.uiImage 34 | #else 35 | let image = renderer.nsImage 36 | #endif 37 | if let image { 38 | completion(SnapshotResult(image: .success(image), precision: wrappedView.precision, accessibilityEnabled: wrappedView.accessibilityEnabled, colorScheme: colorScheme, appStoreSnapshot: wrappedView.appStoreSnapshot)) 39 | } else { 40 | completion(SnapshotResult(image: .failure(RenderingError.failedRendering(.zero)), precision: wrappedView.precision, accessibilityEnabled: wrappedView.accessibilityEnabled, colorScheme: colorScheme, appStoreSnapshot: wrappedView.appStoreSnapshot)) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/PreviewGallery/PreviewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PreviewCell.swift 3 | // 4 | // 5 | // Created by Noah Martin on 8/18/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | import SnapshotPreviewsCore 11 | 12 | struct PreviewCell: View { 13 | 14 | let preview: SnapshotPreviewsCore.Preview 15 | 16 | var body: some View { 17 | PreferredColorSchemeWrapper { 18 | VStack { 19 | AnyView(preview.view()) 20 | .padding(.vertical, 8) 21 | .border(Color.dynamicBackground) 22 | .background { 23 | Checkerboard() 24 | .foregroundStyle(Color.lightChecker) 25 | .background(Color.dynamicBackground) 26 | } 27 | } 28 | } 29 | } 30 | 31 | } 32 | 33 | private extension Color { 34 | static let lightChecker = Color(#colorLiteral(red: 0.7333333333, green: 0.7333333333, blue: 0.7333333333, alpha: 0.18)) 35 | static let slate100 = Color(#colorLiteral(red: 0.9450980392, green: 0.9607843137, blue: 0.9764705882, alpha: 1)) 36 | 37 | static let dynamicBackground = { 38 | #if os(watchOS) 39 | return lightChecker 40 | #elseif canImport(UIKit) 41 | Color(UIColor { traitCollection in 42 | if traitCollection.userInterfaceStyle == .dark { 43 | return UIColor(Color.black) 44 | } 45 | return UIColor(Color.slate100) 46 | }) 47 | #else 48 | Color(NSColor(name: nil, dynamicProvider: { appearance in 49 | if appearance.bestMatch(from: [.aqua, .darkAqua]) == .darkAqua { 50 | return NSColor(Color.black) 51 | } 52 | return NSColor(Color.slate100) 53 | })) 54 | #endif 55 | }() 56 | } 57 | -------------------------------------------------------------------------------- /Examples/DemoApp/DemoModule/TripCardView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TripCardView.swift 3 | // DemoModule 4 | // 5 | // Created by Noah Martin on 7/3/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct TripCardView: View { 11 | var destination: String 12 | var checkInDate: String 13 | var checkOutDate: String 14 | var imageName: String 15 | 16 | var body: some View { 17 | VStack(alignment: .leading, spacing: 8) { 18 | Rectangle() 19 | .frame(height: 200) 20 | .foregroundColor(.clear) 21 | .background( 22 | Image(imageName) 23 | .resizable() 24 | .cornerRadius(12)) 25 | 26 | Text(destination) 27 | .font(.title) 28 | .fontWeight(.bold) 29 | 30 | HStack { 31 | Image(systemName: "calendar") 32 | .foregroundColor(.gray) 33 | 34 | Text("\(checkInDate) - \(checkOutDate)") 35 | .foregroundColor(.gray) 36 | .font(.subheadline) 37 | } 38 | } 39 | .padding() 40 | .background(Color.white) 41 | .cornerRadius(12) 42 | .shadow(color: .gray, radius: 3, x: 0, y: 2) 43 | } 44 | } 45 | 46 | struct TripCardView_Previews: PreviewProvider { 47 | static var previews: some View { 48 | TripCardView(destination: "Paris, France", 49 | checkInDate: "Aug 22", 50 | checkOutDate: "Aug 28", 51 | imageName: "product-image") 52 | .previewLayout(.device) 53 | .padding() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Examples/DemoApp/DemoModule/MessageRow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MessageView.swift 3 | // DemoModule 4 | // 5 | // Created by Noah Martin on 7/3/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct Message: Identifiable { 11 | let id = UUID() 12 | let sender: String 13 | let content: String 14 | let isCurrentUser: Bool 15 | } 16 | 17 | struct MessageRow: View { 18 | let message: Message 19 | 20 | var body: some View { 21 | Group { 22 | if message.isCurrentUser { 23 | HStack { 24 | Spacer() 25 | Text(message.content) 26 | .padding() 27 | .background(Color.blue) 28 | .foregroundColor(.white) 29 | .cornerRadius(12) 30 | } 31 | } else { 32 | HStack { 33 | Text(message.content) 34 | .padding() 35 | .background(Color.gray) 36 | .foregroundColor(.white) 37 | .cornerRadius(12) 38 | Spacer() 39 | } 40 | } 41 | } 42 | } 43 | } 44 | 45 | struct MessageRow_Previews: PreviewProvider { 46 | static var previews: some View { 47 | MessageRow(message: Message(sender: "John", content: "Hey, how's it going?", isCurrentUser: false)) 48 | .previewLayout(.sizeThatFits) 49 | 50 | MessageRow(message: Message(sender: "Jane", content: "Hi John! I'm doing great, thanks. How about you?", isCurrentUser: true)) 51 | .previewLayout(.sizeThatFits) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Sources/SnapshotPreviewsCore/String+Split.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+Split.swift 3 | // SnapshotPreviews 4 | // 5 | // Created by Noah Martin on 1/17/25. 6 | // 7 | 8 | extension String { 9 | var splittingHumanReadableName: [String] { 10 | return split(separator: ".").flatMap { $0.split(separator: "_") } 11 | .joined(separator: " ") 12 | .splitBefore(separator: { $0.isUpperCase && !($1?.isUpperCase ?? true) }) 13 | .map { String($0).trimmingCharacters(in: .whitespaces) } 14 | .filter { $0.count > 0 } 15 | } 16 | } 17 | 18 | extension Sequence { 19 | func splitBefore( 20 | separator isSeparator: (Iterator.Element, Iterator.Element?) throws -> Bool 21 | ) rethrows -> [AnySequence] { 22 | var result: [AnySequence] = [] 23 | var subSequence: [Iterator.Element] = [] 24 | 25 | var iterator = self.makeIterator() 26 | var currentElement = iterator.next() 27 | while let element = currentElement { 28 | let nextElement = iterator.next() 29 | if try isSeparator(element, nextElement) { 30 | if !subSequence.isEmpty { 31 | result.append(AnySequence(subSequence)) 32 | } 33 | subSequence = [element] 34 | } 35 | else { 36 | subSequence.append(element) 37 | } 38 | currentElement = nextElement 39 | } 40 | result.append(AnySequence(subSequence)) 41 | return result 42 | } 43 | } 44 | 45 | extension Character { 46 | var isUpperCase: Bool { return String(self) == String(self).uppercased() } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/SnapshotPreferences/PrecisionPreference.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PrecisionPreference.swift 3 | // 4 | // 5 | // Created by Noah Martin on 9/5/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | struct PrecisionPreferenceKey: PreferenceKey { 12 | static func reduce(value: inout Float?, nextValue: () -> Float?) { 13 | value = nextValue() 14 | } 15 | 16 | static var defaultValue: Float? = nil 17 | } 18 | 19 | extension View { 20 | /// Sets the precision level for the snapshot on the view. 21 | /// 22 | /// Use this method to control the precision of the snapshot, which will be used for 23 | /// the comparison logic. With precision level 1.0, the images fully match. With precision 24 | /// level 0, the snapshot will never be flagged for having differences. 25 | /// 26 | /// - Parameter precision: A Float value representing the desired precision level for 27 | /// emerge snapshot operations. If `nil`, the value will default to 1.0. 28 | /// 29 | /// - Returns: A view with the snapshot precision preference applied. 30 | /// 31 | /// # Example 32 | /// ```swift 33 | /// struct ContentView: View { 34 | /// var body: some View { 35 | /// Image("sample") 36 | /// .emergeSnapshotPrecision(0.8) 37 | /// } 38 | /// } 39 | /// ``` 40 | /// 41 | /// - Note: The actual impact of the precision value may vary depending on the 42 | /// specific implementation of the emerge snapshot feature. 43 | public func emergeSnapshotPrecision(_ precision: Float?) -> some View { 44 | preference(key: PrecisionPreferenceKey.self, value: precision) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Examples/DemoApp/DemoModule/InfoCardView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InfoCardView.swift 3 | // DemoModule 4 | // 5 | // Created by Noah Martin on 7/3/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct InfoCardView: View { 11 | var title: String 12 | var description: String 13 | var icon: String 14 | var backgroundColor: Color 15 | 16 | var body: some View { 17 | VStack(spacing: 8) { 18 | Image(systemName: icon) 19 | .font(.system(size: 50)) 20 | .foregroundColor(.white) 21 | .padding() 22 | .background(backgroundColor) 23 | .clipShape(Circle()) 24 | 25 | Text(title) 26 | .font(.title) 27 | .bold() 28 | 29 | Text(description) 30 | .font(.body) 31 | .multilineTextAlignment(.center) 32 | .foregroundColor(.gray) 33 | } 34 | .padding() 35 | .background(Color.white) 36 | .cornerRadius(12) 37 | .shadow(color: .gray, radius: 3, x: 0, y: 2) 38 | } 39 | } 40 | 41 | struct InfoCardView_Previews: PreviewProvider { 42 | static var previews: some View { 43 | Group { 44 | InfoCardView(title: "Discover", description: "Explore a world of possibilities", icon: "globe", backgroundColor: Color.blue) 45 | .previewLayout(.sizeThatFits) 46 | .padding() 47 | 48 | InfoCardView(title: "Connect", description: "Stay connected with loved ones", icon: "network", backgroundColor: Color.orange) 49 | .previewLayout(.sizeThatFits) 50 | .padding() 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport.xcframework/ios-arm64_x86_64-maccatalyst/PreviewsSupport.framework/Versions/A/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildMachineOSBuild 6 | 23G93 7 | CFBundleDevelopmentRegion 8 | en 9 | CFBundleExecutable 10 | PreviewsSupport 11 | CFBundleIdentifier 12 | com.emerge.PreviewsSupport 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | PreviewsSupport 17 | CFBundlePackageType 18 | FMWK 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSupportedPlatforms 22 | 23 | MacOSX 24 | 25 | CFBundleVersion 26 | 1 27 | DTCompiler 28 | com.apple.compilers.llvm.clang.1_0 29 | DTPlatformBuild 30 | 24A5324a 31 | DTPlatformName 32 | macosx 33 | DTPlatformVersion 34 | 15.0 35 | DTSDKBuild 36 | 24A5324a 37 | DTSDKName 38 | macosx15.0 39 | DTXcode 40 | 1600 41 | DTXcodeBuild 42 | 16A5230g 43 | LSMinimumSystemVersion 44 | 12.0 45 | UIDeviceFamily 46 | 47 | 2 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /Examples/DemoApp/DemoModule/HomeMapView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HomeMapView.swift 3 | // DemoModule 4 | // 5 | // Created by Noah Martin on 7/3/23. 6 | // 7 | 8 | import SwiftUI 9 | import MapKit 10 | import SnapshotPreferences 11 | 12 | struct Location: Identifiable { 13 | let id = UUID() 14 | let coordinate: CLLocationCoordinate2D 15 | } 16 | 17 | 18 | struct HomeMapView: View { 19 | @State private var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 37.332_331_41, longitude: -122.031_218_6), 20 | span: MKCoordinateSpan(latitudeDelta: 0.2, longitudeDelta: 0.2)) 21 | let address: String 22 | 23 | var body: some View { 24 | VStack { 25 | Map(coordinateRegion: $region, annotationItems: [Location(coordinate: CLLocationCoordinate2D(latitude: 37.332_331_41, longitude: -122.031_218_6))]) { item in 26 | MapMarker(coordinate: item.coordinate) 27 | } 28 | .frame(height: 300) 29 | .cornerRadius(12) 30 | 31 | VStack(alignment: .leading, spacing: 4) { 32 | Text(address) 33 | .font(.subheadline) 34 | .foregroundColor(.gray) 35 | .lineLimit(2) 36 | .padding(.horizontal) 37 | } 38 | } 39 | .padding() 40 | .onAppear { 41 | //annotation.coordinate = region.center 42 | } 43 | } 44 | } 45 | 46 | struct HomeMapView_Previews: PreviewProvider { 47 | static var previews: some View { 48 | HomeMapView(address: "123 Main St, San Francisco, CA") 49 | .emergeSnapshotPrecision(0.9) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Sources/SnapshotPreferences/AppStoreSnapshotPreference.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppStoreSnapshotPreference.swift 3 | // 4 | // 5 | // Created by Trevor Elkins on 09/30/24. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | struct AppStoreSnapshotPreferenceKey: PreferenceKey { 12 | static func reduce(value: inout Bool?, nextValue: () -> Bool?) { 13 | if value == nil { 14 | value = nextValue() 15 | } 16 | } 17 | 18 | static var defaultValue: Bool? = nil 19 | } 20 | 21 | extension View { 22 | /// Marks a snapshot for use with our App Store screenshot editing tool. This should ideally be used with a 23 | /// full-size preview matching one of our supported devices. 24 | /// 25 | /// - Note: This method is only available on iOS. It is unavailable on macOS, watchOS, visionOS, and tvOS. 26 | /// 27 | /// - Parameter enabled: A Boolean value that determines whether the snapshot is for an App Store screenshot. 28 | /// If `nil`, the effect will default to `false`. 29 | /// 30 | /// - Returns: A view with the app store snapshot preference applied. 31 | /// 32 | /// # Example 33 | /// ```swift 34 | /// struct ContentView: View { 35 | /// var body: some View { 36 | /// Text("My App Store listing!") 37 | /// .emergeAppStoreSnapshot(true) 38 | /// } 39 | /// } 40 | /// ``` 41 | @available(macOS, unavailable) 42 | @available(watchOS, unavailable) 43 | @available(visionOS, unavailable) 44 | @available(tvOS, unavailable) 45 | public func emergeAppStoreSnapshot(_ enabled: Bool?) -> some View { 46 | preference(key: AppStoreSnapshotPreferenceKey.self, value: enabled) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/SnapshotPreferences/AccessibiltyPreference.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AccessibiltyPreference.swift 3 | // 4 | // 5 | // Created by Noah Martin on 2/2/24. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | struct AccessibilityPreferenceKey: PreferenceKey { 12 | static func reduce(value: inout Bool?, nextValue: () -> Bool?) { 13 | if value == nil { 14 | value = nextValue() 15 | } 16 | } 17 | 18 | static var defaultValue: Bool? = nil 19 | } 20 | 21 | extension View { 22 | /// Applies accessibility support to the view's snapshot. 23 | /// 24 | /// Use this method to control whether the snapshot should render with accessibility elements 25 | /// highlighted as well as a corresponding legend for them. 26 | /// 27 | /// - Note: This method is only available on iOS. It is unavailable on macOS, watchOS, visionOS, and tvOS. 28 | /// 29 | /// - Parameter enabled: A Boolean value that determines whether the emerge accessibility 30 | /// features are applied. If `nil`, the effect will default to `false`. 31 | /// 32 | /// - Returns: A view with the accessibility preference applied. 33 | /// 34 | /// # Example 35 | /// ```swift 36 | /// struct ContentView: View { 37 | /// var body: some View { 38 | /// Text("Accessible Content") 39 | /// .emergeAccessibility(true) 40 | /// } 41 | /// } 42 | /// ``` 43 | @available(macOS, unavailable) 44 | @available(watchOS, unavailable) 45 | @available(visionOS, unavailable) 46 | @available(tvOS, unavailable) 47 | public func emergeAccessibility(_ enabled: Bool?) -> some View { 48 | preference(key: AccessibilityPreferenceKey.self, value: enabled) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Examples/UnitTestMigration/README.md: -------------------------------------------------------------------------------- 1 | # Unit Test Migration 2 | 3 | This example shows how to migrate from swift-snapshot-testing to preview providers. The example app contains a unit test target `UnitTestMigrationTests` with one [snapshot test](https://github.com/EmergeTools/SnapshotPreviews-iOS/blob/main/Examples/UnitTestMigration/UnitTestMigrationTests/ExampleSnapshotTest.swift). It has been migrated by copying/pasting the file into the main app target as [ExampleSnapshotTest_Migrated.swift](https://github.com/EmergeTools/SnapshotPreviews-iOS/blob/main/Examples/UnitTestMigration/UnitTestMigration/ExampleSnapshotTest_Migrated.swift). The test is modified by removing the XCTest/SnapshotTesting importants, changing the base class to `SnapshotTest` and adding the conformance to `PreviewProvider`. 4 | 5 | ## Migrating your own tests 6 | 7 | 1. Add the file [SnapshotTest.swift](https://github.com/EmergeTools/SnapshotPreviews-iOS/blob/main/Examples/UnitTestMigration/UnitTestMigration/SnapshotTest.swift) to your app, this does the heavy lifting of converting snapshot test function calls into previews. 8 | 2. Copy your test file from the unit test target to your application. 9 | 3. Remove imports of `XCTest` and `SnapshotTesting`. 10 | 4. Change the base class from `XCTestCase` to `SnapshotTest` and add a conformce to `PreviewProvider`. 11 | 12 | Here’s a complete example of a migrated test: 13 | 14 | ```swift 15 | import SwiftUI 16 | 17 | // Add the conformance to `PreviewProvider` when extending `SnapshotTest` 18 | // The `SnapshotTest` base class automatically handles turning calls to 19 | // assertSnapshot into previews. 20 | class ExampleSnapshotTest: SnapshotTest, PreviewProvider { 21 | 22 | func testContentViewSnapshot() { 23 | assertSnapshot(of: ContentView(), as: .image) 24 | } 25 | } 26 | ``` 27 | -------------------------------------------------------------------------------- /Sources/SnapshotPreviewsCore/ModifierFinder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ModifierFinder.swift 3 | // 4 | // 5 | // Created by Noah Martin on 7/8/24. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | import SnapshotSharedModels 11 | 12 | private let modifierFinderClass = (NSClassFromString("EmergeModifierFinder") as? NSObject.Type)?.init() 13 | private let finder = modifierFinderClass != nil ? Mirror(reflecting: modifierFinderClass!).descendant("finder") as? (any View) -> any View : nil 14 | private let modifierState = NSClassFromString("EmergeModifierState") as? NSObject.Type 15 | private let stateMirror = modifierState != nil ? Mirror( 16 | reflecting: modifierState! 17 | .perform(NSSelectorFromString("shared")) 18 | .takeUnretainedValue()) : nil 19 | 20 | public struct EmergeModifierView: View { 21 | 22 | private let internalView: AnyView 23 | 24 | init(wrapped: some View) { 25 | let rootView = finder?(wrapped) 26 | internalView = rootView != nil ? AnyView(rootView!) : AnyView(wrapped) 27 | } 28 | 29 | public var body: some View { 30 | internalView 31 | } 32 | 33 | var emergeRenderingMode: EmergeRenderingMode? { 34 | let renderingMode = stateMirror?.descendant("renderingMode") as? EmergeRenderingMode.RawValue 35 | return renderingMode != nil ? EmergeRenderingMode(rawValue: renderingMode!) : nil 36 | } 37 | 38 | var accessibilityEnabled: Bool? { 39 | stateMirror?.descendant("accessibilityEnabled") as? Bool 40 | } 41 | 42 | var appStoreSnapshot: Bool? { 43 | stateMirror?.descendant("appStoreSnapshot") as? Bool 44 | } 45 | 46 | var precision: Float? { 47 | stateMirror?.descendant("precision") as? Float 48 | } 49 | 50 | var supportsExpansion: Bool { 51 | stateMirror?.descendant("expansionPreference") as? Bool ?? true 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Sources/SnapshottingTestsObjc/EMGInvocationCreator.mm: -------------------------------------------------------------------------------- 1 | // 2 | // EMGInvocationCreator.m 3 | // 4 | // 5 | // Created by Noah Martin on 8/9/24. 6 | // 7 | 8 | #import 9 | 10 | @interface EMGInvocationCreator: NSObject 11 | 12 | + (NSInvocation *)create:(NSString *)selectorName; 13 | 14 | @end 15 | 16 | @implementation EMGInvocationCreator 17 | 18 | + (NSInvocation *)create:(NSString *)selectorName { 19 | NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:"v@:"]]; 20 | invocation.selector = NSSelectorFromString(selectorName); 21 | return invocation; 22 | } 23 | 24 | #if TARGET_OS_TV || TARGET_OS_WATCH || TARGET_OS_VISION || !(defined(__arm64__) || defined(__aarch64__)) 25 | #define EMG_ENABLE_FIX_TIME 0 26 | #else 27 | #define EMG_ENABLE_FIX_TIME 1 28 | #endif 29 | 30 | #if EMG_ENABLE_FIX_TIME 31 | 32 | #import 33 | #import 34 | #import 35 | #import 36 | 37 | SimpleDebugger *handler; 38 | 39 | int gettimeofday_new(struct timeval *t, void *a) { 40 | t->tv_sec = 1723532400; 41 | t->tv_usec = 0; 42 | return 0; 43 | } 44 | 45 | #endif 46 | 47 | + (void)hookTime { 48 | #if EMG_ENABLE_FIX_TIME 49 | handler = new SimpleDebugger(); 50 | handler->hookFunction((void *) &gettimeofday, (void *) &gettimeofday_new); 51 | #endif 52 | } 53 | 54 | + (void)load { 55 | NSDictionary *env = [[NSProcessInfo processInfo] environment]; 56 | if (![[env objectForKey:@"EMERGE_DISABLE_FIX_TIME"] isEqualToString:@"1"]) { 57 | [self hookTime]; 58 | } 59 | id previewBaseTest = NSClassFromString(@"EMGPreviewBaseTest"); 60 | [previewBaseTest performSelector:@selector(swizzle:) withObject:[self class]]; 61 | } 62 | 63 | @end 64 | -------------------------------------------------------------------------------- /Examples/DemoApp/DemoAppTests/DemoAppSnapshotTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoAppSnapshotTest.swift 3 | // DemoAppTests 4 | // 5 | // Created by Noah Martin on 8/10/24. 6 | // 7 | 8 | import Foundation 9 | #if canImport(UIKit) 10 | import UIKit 11 | #endif 12 | import SnapshottingTests 13 | import AccessibilitySnapshotCore 14 | 15 | class DemoAppSnapshotTest: SnapshotTest { 16 | override class func snapshotPreviews() -> [String]? { 17 | return nil 18 | } 19 | 20 | override class func excludedSnapshotPreviews() -> [String]? { 21 | return nil 22 | } 23 | 24 | #if canImport(UIKit) && !os(watchOS) && !os(visionOS) && !os(tvOS) 25 | override open class func setupA11y() -> ((UIViewController, UIWindow, PreviewLayout) -> UIView)? { 26 | return { (controller: UIViewController, window: UIWindow, layout: PreviewLayout) in 27 | let containerVC = controller.parent 28 | let containedView: UIView 29 | switch layout { 30 | case .device: 31 | containedView = containerVC?.view ?? controller.view 32 | default: 33 | containedView = controller.view 34 | } 35 | let a11yView = AccessibilitySnapshotView( 36 | containedView: containedView, 37 | viewRenderingMode: controller.view.bounds.size.requiresCoreAnimationSnapshot ? .renderLayerInContext : .drawHierarchyInRect, 38 | activationPointDisplayMode: .never, 39 | showUserInputLabels: true) 40 | 41 | a11yView.center = window.center 42 | window.addSubview(a11yView) 43 | 44 | _ = try? a11yView.parseAccessibility(useMonochromeSnapshot: false) 45 | a11yView.sizeToFit() 46 | return a11yView 47 | } 48 | } 49 | #endif 50 | } 51 | 52 | extension CGSize { 53 | var requiresCoreAnimationSnapshot: Bool { 54 | height >= UIScreen.main.bounds.size.height * 2 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /PreviewsSupport/README.md: -------------------------------------------------------------------------------- 1 | # PreviewsSupport 2 | 3 | This exposes functions in UIKit and SwiftUI for extracting previews that are not in the swiftinterface for these libraries. The symbols are in the .tbd files, and can be found at link-time. 4 | 5 | Modifications to the libraries are needed to make the Swift compiler see these symbols, which is why this is built outside of SPM and linked to the main snapshots package as a binaryTarget. 6 | 7 | To build locally, modify `Xcode.app/Contents/Developer/Platforms/[iPhoneSimulator/iPhoneOS/MacOSX].platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/Frameworks/SwiftUI.framework/Modules/SwiftUI.swiftmodule/*.swiftinterface` with the following additions at the end of the files: 8 | 9 | ``` 10 | @available(iOS 17.0, macOS 14.0, *) 11 | public struct ViewPreviewSource { 12 | public var makeView: @_Concurrency.MainActor () -> any SwiftUI.View 13 | } 14 | ``` 15 | 16 | and modify `Xcode.app/Contents/Developer/Platforms/[iPhoneSimulator/iPhoneOS].platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/Frameworks/UIKit.framework/Modules/UIKit.swiftmodule/*.swiftinterface` with the following additions at the end of the files: 17 | 18 | ``` 19 | @available(iOS 17.0, macOS 14.0, *) 20 | public struct UIViewPreviewSource { 21 | public var makeView: @_Concurrency.MainActor () -> UIKit.UIView 22 | } 23 | 24 | @available(iOS 17.0, macOS 14.0, *) 25 | public struct UIViewControllerPreviewSource { 26 | public var makeViewController: @_Concurrency.MainActor () -> UIKit.UIViewController 27 | } 28 | ``` 29 | 30 | The files in `Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/iOSSupport/System/Library/Frameworks/` need to be modified as well for Mac Catalyst support. 31 | 32 | In order to link the xcframework without having made the system library modifications, the extensions need to be deleted from the xcframework's .swiftinterface files. -------------------------------------------------------------------------------- /Examples/DemoApp/DemoApp/TestViews/RideOptionsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RideOptionsView.swift 3 | // DemoApp 4 | // 5 | // Created by Noah Martin on 7/3/23. 6 | // 7 | 8 | import SwiftUI 9 | import DemoModule 10 | 11 | struct RideOptionView: View { 12 | var title: String 13 | var description: String 14 | var price: Double 15 | 16 | var body: some View { 17 | HStack { 18 | VStack(alignment: .leading, spacing: 8) { 19 | Text(title) 20 | .font(.headline) 21 | 22 | Text(description) 23 | .foregroundColor(Color(PlatformColor.systemGray)) 24 | .font(.subheadline) 25 | .fixedSize(horizontal: false, vertical: true) 26 | .lineLimit(2) 27 | 28 | Text("$\(price, specifier: "%.2f")") 29 | .font(.headline) 30 | .foregroundColor(Color(PlatformColor.systemBlue)) 31 | } 32 | 33 | Spacer() 34 | 35 | Image(systemName: "chevron.right") 36 | .foregroundColor(Color(PlatformColor.systemGray)) 37 | } 38 | .padding() 39 | .background(Color(PlatformColor.systemBackground)) 40 | .cornerRadius(12) 41 | .shadow(color: .gray, radius: 3, x: 0, y: 2) 42 | } 43 | } 44 | 45 | struct RideOptionView_Previews: PreviewProvider { 46 | static var previews: some View { 47 | PreviewVariants(layout: .sizeThatFits) { 48 | RideOptionView(title: "Economy", description: "Affordable and efficient", price: 19.99) 49 | .padding() 50 | .previewVariant(named: "Ride Option View - Economy") 51 | 52 | RideOptionView(title: "Luxury", description: "Premium experience", price: 39.99) 53 | .padding() 54 | .previewVariant(named: "Ride Option View - Luxury") 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Examples/DemoApp/DemoApp/TestViews/RideShareButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RideShareButton.swift 3 | // DemoApp 4 | // 5 | // Created by Noah Martin on 7/3/23. 6 | // 7 | 8 | import SwiftUI 9 | import DemoModule 10 | 11 | struct RideShareButtonView: View { 12 | var title: String 13 | var action: () -> Void 14 | 15 | var body: some View { 16 | Button(action: action) { 17 | Text(title) 18 | .font(.headline) 19 | .foregroundColor(Color(PlatformColor.systemBackground)) 20 | .padding() 21 | .frame(maxWidth: .infinity) 22 | .background(Color(PlatformColor.label)) 23 | .cornerRadius(12) 24 | } 25 | .buttonStyle(PlainButtonStyle()) 26 | } 27 | } 28 | 29 | struct RideShareButtonView_Previews: PreviewProvider { 30 | static var previews: some View { 31 | RideShareButtonView(title: "Request Ride") { 32 | print("Button tapped") 33 | } 34 | .previewLayout(.sizeThatFits) 35 | .padding() 36 | .previewDisplayName("Ride Share Button View - Light") 37 | // This should never show as a diff 38 | .emergeSnapshotPrecision(0.0) 39 | #if os(iOS) 40 | .emergeRenderingMode(.coreAnimation) 41 | #endif 42 | 43 | RideShareButtonView(title: "Request Ride") { 44 | print("Button tapped") 45 | } 46 | .previewDisplayName("Ride Share Button View - Dark") 47 | .preferredColorScheme(.dark) 48 | .previewLayout(.sizeThatFits) 49 | .padding() 50 | 51 | RideShareButtonView(title: "Request Ride") { 52 | print("Button tapped") 53 | } 54 | .previewLayout(.sizeThatFits) 55 | .padding() 56 | .previewDisplayName("Ride Share Button View - Light") 57 | #if os(iOS) 58 | .emergeRenderingMode(.coreAnimation) 59 | .emergeAccessibility(true) 60 | #endif 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Sources/PreviewGallery/ModuleScreens.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ModuleScreens.swift 3 | // 4 | // 5 | // Created by Noah Martin on 8/31/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | import SnapshotPreviewsCore 11 | 12 | struct ModuleSelectionView: View { 13 | let provider: PreviewGrouping 14 | var body: some View { 15 | let featurePreviews = provider.previews.flatMap(\.previews).filter { $0.requiresFullScreen } 16 | NavigationLink { 17 | if featurePreviews.count == 1 { 18 | AnyView(featurePreviews[0].view()) 19 | } else { 20 | List { 21 | ForEach(featurePreviews) { preview in 22 | NavigationLink(preview.displayName ?? provider.displayName) { 23 | AnyView(preview.view()) 24 | } 25 | } 26 | }.navigationTitle(provider.displayName) 27 | } 28 | } label: { 29 | VStack(alignment: .leading) { 30 | Text(provider.displayName) 31 | .font(.headline) 32 | .foregroundStyle(Color(PlatformColor.label)) 33 | .padding(.leading, 8) 34 | 35 | Text("\(featurePreviews.count) Preview\(featurePreviews.count != 1 ? "s" : "")") 36 | .font(.subheadline) 37 | .foregroundStyle(Color(PlatformColor.secondaryLabel)) 38 | .padding(.leading, 8) 39 | } 40 | } 41 | } 42 | } 43 | 44 | struct ModuleScreens: View { 45 | 46 | let module: String 47 | let data: PreviewData 48 | @State private var searchText = "" 49 | 50 | var body: some View { 51 | let featureProviders = data.previews(in: module).filter { provider in 52 | !provider.previewTypes(requiringFullscreen: true).isEmpty 53 | }.filterWithText(searchText, { $0.displayName }) 54 | return List { 55 | ForEach(featureProviders) { provider in 56 | ModuleSelectionView(provider: provider) 57 | } 58 | }.navigationTitle("Screens") 59 | .searchable(text: $searchText) 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /Examples/DemoApp/DemoApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Emerge-Avatar Optimized.heic", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | }, 9 | { 10 | "filename" : "Emerge-Avatar Optimized 10.heic", 11 | "idiom" : "mac", 12 | "scale" : "1x", 13 | "size" : "16x16" 14 | }, 15 | { 16 | "filename" : "Emerge-Avatar Optimized 9.heic", 17 | "idiom" : "mac", 18 | "scale" : "2x", 19 | "size" : "16x16" 20 | }, 21 | { 22 | "filename" : "Emerge-Avatar Optimized 8.heic", 23 | "idiom" : "mac", 24 | "scale" : "1x", 25 | "size" : "32x32" 26 | }, 27 | { 28 | "filename" : "Emerge-Avatar Optimized 7.heic", 29 | "idiom" : "mac", 30 | "scale" : "2x", 31 | "size" : "32x32" 32 | }, 33 | { 34 | "filename" : "Emerge-Avatar Optimized 6.heic", 35 | "idiom" : "mac", 36 | "scale" : "1x", 37 | "size" : "128x128" 38 | }, 39 | { 40 | "filename" : "Emerge-Avatar Optimized 4.heic", 41 | "idiom" : "mac", 42 | "scale" : "2x", 43 | "size" : "128x128" 44 | }, 45 | { 46 | "filename" : "Emerge-Avatar Optimized 5.heic", 47 | "idiom" : "mac", 48 | "scale" : "1x", 49 | "size" : "256x256" 50 | }, 51 | { 52 | "filename" : "Emerge-Avatar Optimized 3.heic", 53 | "idiom" : "mac", 54 | "scale" : "2x", 55 | "size" : "256x256" 56 | }, 57 | { 58 | "filename" : "Emerge-Avatar Optimized 2.heic", 59 | "idiom" : "mac", 60 | "scale" : "1x", 61 | "size" : "512x512" 62 | }, 63 | { 64 | "filename" : "Emerge-Avatar Optimized 1.heic", 65 | "idiom" : "mac", 66 | "scale" : "2x", 67 | "size" : "512x512" 68 | } 69 | ], 70 | "info" : { 71 | "author" : "xcode", 72 | "version" : 1 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Examples/DemoApp/DemoModule/RowView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RowView.swift 3 | // DemoModule 4 | // 5 | // Created by Noah Martin on 7/3/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | public struct RowView: View { 12 | var imageName: String 13 | var productName: String 14 | var ratings: Double 15 | 16 | public init(imageName: String, productName: String, ratings: Double) { 17 | self.imageName = imageName 18 | self.productName = productName 19 | self.ratings = ratings 20 | } 21 | 22 | public var body: some View { 23 | HStack { 24 | Image(imageName) 25 | .resizable() 26 | .aspectRatio(contentMode: .fit) 27 | .frame(width: 80, height: 80) 28 | .cornerRadius(8) 29 | 30 | VStack(alignment: .leading, spacing: 8) { 31 | Text(productName) 32 | .font(.headline) 33 | .lineLimit(2) 34 | 35 | HStack(spacing: 4) { 36 | ForEach(0..<5) { index in 37 | Image(systemName: ratings >= Double(index + 1) ? "star.fill" : "star") 38 | .foregroundColor(Color(PlatformColor.systemYellow)) 39 | .font(.caption) 40 | } 41 | } 42 | } 43 | } 44 | .padding(8) 45 | } 46 | } 47 | 48 | #if compiler(>=5.9) 49 | #Preview("New test") { 50 | RowView( 51 | imageName: "product-image", 52 | productName: "New Preview test", 53 | ratings: 4.2) 54 | .preferredColorScheme(.dark) 55 | } 56 | #endif 57 | 58 | struct RowView_Previews: PreviewProvider { 59 | static var previews: some View { 60 | RowView( 61 | imageName: "product-image", 62 | productName: "My Awesome Item", 63 | ratings: 4.2) 64 | .previewDisplayName("RowView") 65 | .previewLayout(.sizeThatFits) 66 | .preferredColorScheme(.dark) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Sources/SnapshotPreferences/RenderingModePreference.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RenderingModePreference.swift 3 | // 4 | // 5 | // Created by Noah Martin on 9/7/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | import SnapshotSharedModels 11 | 12 | struct RenderingModePreferenceKey: PreferenceKey { 13 | static func reduce(value: inout Int?, nextValue: () -> Int?) { 14 | value = nextValue() 15 | } 16 | 17 | static var defaultValue: EmergeRenderingMode.RawValue? = nil 18 | } 19 | 20 | extension View { 21 | /// Sets the emerge rendering mode for the view. 22 | /// 23 | /// Use this method to control how the view is rendered for snapshots. You can indicate whether 24 | /// to use `.coreAnimation` which will use the CALayer from Quartz or `.uiView` which will use 25 | /// UIKit's `drawViewHierarchyInRect` under the hood. 26 | /// 27 | /// - Note: This method is only available on iOS. It is unavailable on macOS, watchOS, visionOS, and tvOS. 28 | /// 29 | /// - Parameter renderingMode: An `EmergeRenderingMode` value that specifies the 30 | /// desired rendering mode for snapshots. If `nil`, the default rendering 31 | /// mode will be selected based off of the view's height. 32 | /// 33 | /// - Returns: A view with the specified rendering mode preference applied. 34 | /// 35 | /// # Example 36 | /// ```swift 37 | /// struct ContentView: View { 38 | /// var body: some View { 39 | /// Text("Emerge Effect") 40 | /// .emergeRenderingMode(.coreAnimation) 41 | /// } 42 | /// } 43 | /// ``` 44 | /// 45 | /// - SeeAlso: `EmergeRenderingMode` 46 | @available(watchOS, unavailable) 47 | @available(visionOS, unavailable) 48 | @available(tvOS, unavailable) 49 | public func emergeRenderingMode(_ renderingMode: EmergeRenderingMode?) -> some View { 50 | preference(key: RenderingModePreferenceKey.self, value: renderingMode?.rawValue) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Sources/SnapshotPreferences/EmergeModifierFinder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmergeModifierFinder.swift 3 | // 4 | // 5 | // Created by Noah Martin on 10/6/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | import SnapshotSharedModels 11 | 12 | // Classes in this file get compiled to an app that use any of the custom preview preferences. 13 | // The inserted test runner code finds these classes through ObjC runtime functions (NSClassFromString) 14 | // and Swift reflection (Mirror). 15 | 16 | @objc(EmergeModifierState) 17 | class EmergeModifierState: NSObject { 18 | 19 | @objc 20 | static let shared = EmergeModifierState() 21 | 22 | func reset() { 23 | expansionPreference = nil 24 | renderingMode = nil 25 | precision = nil 26 | accessibilityEnabled = nil 27 | } 28 | 29 | var expansionPreference: Bool? 30 | var renderingMode: EmergeRenderingMode.RawValue? 31 | var precision: Float? 32 | var accessibilityEnabled: Bool? 33 | var appStoreSnapshot: Bool? 34 | } 35 | 36 | @objc(EmergeModifierFinder) 37 | class EmergeModifierFinder: NSObject { 38 | let finder: (any View) -> (any View) = { view in 39 | EmergeModifierState.shared.reset() 40 | return view 41 | .onPreferenceChange(ExpansionPreferenceKey.self, perform: { value in 42 | EmergeModifierState.shared.expansionPreference = value 43 | }) 44 | .onPreferenceChange(RenderingModePreferenceKey.self, perform: { value in 45 | EmergeModifierState.shared.renderingMode = value 46 | }) 47 | .onPreferenceChange(PrecisionPreferenceKey.self, perform: { value in 48 | EmergeModifierState.shared.precision = value 49 | }) 50 | .onPreferenceChange(AccessibilityPreferenceKey.self, perform: { value in 51 | EmergeModifierState.shared.accessibilityEnabled = value 52 | }) 53 | .onPreferenceChange(AppStoreSnapshotPreferenceKey.self, perform: { value in 54 | EmergeModifierState.shared.appStoreSnapshot = value 55 | }) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Sources/PreviewGallery/Checkerboard.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Checkerboard.swift 3 | // 4 | // 5 | // Created by Noah Martin on 7/4/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct Checkerboard: Shape { 11 | func path(in rect: CGRect) -> Path { 12 | var path = Path() 13 | 14 | let rowSize: Double = 10 15 | let columnSize: Double = 10 16 | 17 | let rows = Int(rect.height / CGFloat(rowSize)) 18 | let columns = Int(rect.width / CGFloat(columnSize)) 19 | let columnRemainder = rect.width - Double(columns) * columnSize 20 | let rowRemainder = rect.height - Double(rows) * rowSize 21 | 22 | for row in 0 ..< rows { 23 | for column in 0 ..< columns { 24 | if (row + column).isMultiple(of: 2) { 25 | let startX = Double(columnSize) * Double(column) 26 | let startY = Double(rowSize) * Double(row) 27 | 28 | let rect = CGRect(x: startX, y: startY, width: columnSize, height: rowSize) 29 | path.addRect(rect) 30 | } 31 | } 32 | if (row + columns).isMultiple(of: 2) { 33 | if columnRemainder > 0 { 34 | let startX = Double(columnSize) * Double(columns) 35 | let startY = Double(rowSize) * Double(row) 36 | 37 | let rect = CGRect(x: startX, y: startY, width: columnRemainder, height: rowSize) 38 | path.addRect(rect) 39 | } 40 | } 41 | } 42 | if rowRemainder > 0 { 43 | for column in 0.. { 10 | public var structure: DefaultPreviewSource.Structure 11 | 12 | public enum Structure { 13 | case singlePreview(makeBody: @_Concurrency.MainActor () -> A) 14 | } 15 | }") 16 | end 17 | end 18 | end 19 | 20 | for file_path in swiftui_interface 21 | if !File.read(file_path).include?("ViewPreviewSource") 22 | File.open(file_path, 'a') do |file| 23 | file.puts("@available(iOS 17.0, macOS 14.0, watchOS 10.0, tvOS 17.0, *) 24 | public struct ViewPreviewSource { 25 | public var makeView: @_Concurrency.MainActor () -> any SwiftUI.View 26 | } 27 | @available(iOS 18.0, macOS 15.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) 28 | public struct ViewPreviewBody { 29 | public var body: SwiftUI.View { get } 30 | } 31 | ") 32 | end 33 | end 34 | end 35 | 36 | for file_path in uikit_interface 37 | if !file_path.include?("Watch") && !File.read(file_path).include?("UIViewPreviewSource") 38 | File.open(file_path, 'a') do |file| 39 | file.puts("@available(iOS 17.0, macOS 14.0, tvOS 17.0, *) 40 | public struct UIViewPreviewSource { 41 | public var makeView: @_Concurrency.MainActor () -> UIKit.UIView 42 | } 43 | 44 | @available(iOS 17.0, macOS 14.0, tvOS 17.0, *) 45 | public struct UIViewControllerPreviewSource { 46 | public var makeViewController: @_Concurrency.MainActor () -> UIKit.UIViewController 47 | }") 48 | end 49 | end 50 | end -------------------------------------------------------------------------------- /Sources/PreviewGallery/ModulePreviews.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ModulePreviews.swift 3 | // 4 | // 5 | // Created by Noah Martin on 7/3/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | struct ModulePreviews: View { 12 | let module: String 13 | let data: PreviewData 14 | 15 | @State private var searchText = "" 16 | 17 | var body: some View { 18 | let allPreviewGroups = data.previews(in: module) 19 | let componentProviders = allPreviewGroups.filter { provider in 20 | provider.previewTypes(requiringFullscreen: false).count > 0 21 | }.filterWithText(searchText, { $0.displayName }) 22 | let fullScreenCount = allPreviewGroups.flatMap { $0.previews.flatMap { $0.previews(requiringFullscreen: true)} }.count 23 | return NavigationLink(module) { 24 | ScrollView { 25 | LazyVStack(alignment: .leading, spacing: 12) { 26 | if fullScreenCount > 0 && (searchText.isEmpty || "screens".contains(searchText.lowercased())) { 27 | NavigationLink(destination: ModuleScreens(module: module, data: data)) { 28 | TitleSubtitleRow( 29 | title: "Screens", 30 | subtitle: "\(fullScreenCount) Preview\(fullScreenCount != 1 ? "s" : "")") 31 | .padding(16) 32 | #if !os(watchOS) 33 | .background(Color(PlatformColor.gallerySecondaryBackground)) 34 | #endif 35 | } 36 | } 37 | ForEach(componentProviders) { preview in 38 | NavigationLink(destination: PreviewsDetail(previewGrouping: preview)) { 39 | PreviewCellView(previewGrouping: preview) 40 | #if !os(watchOS) 41 | .background(Color(PlatformColor.gallerySecondaryBackground)) 42 | #endif 43 | .allowsHitTesting(false) 44 | } 45 | #if canImport(UIKit) && !os(visionOS) && !os(watchOS) 46 | .frame(width: UIScreen.main.bounds.width) 47 | #endif 48 | } 49 | } 50 | } 51 | #if !os(watchOS) 52 | .background(Color(PlatformColor.galleryBackground)) 53 | #endif 54 | .navigationTitle(module) 55 | .searchable(text: $searchText) 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Examples/DemoApp/DemoModule/ConversationMessageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConversationMessageView.swift 3 | // DemoModule 4 | // 5 | // Created by Noah Martin on 7/3/23. 6 | // 7 | 8 | 9 | import SwiftUI 10 | 11 | struct Conversation: Identifiable { 12 | let id = UUID() 13 | let contactName: String 14 | let messagePreview: String 15 | let timestamp: Date 16 | let unreadCount: Int 17 | } 18 | 19 | struct ConversationCellView: View { 20 | var conversation: Conversation 21 | 22 | var body: some View { 23 | HStack(spacing: 16) { 24 | Image(systemName: "person.circle.fill") 25 | .font(.system(size: 48)) 26 | .foregroundColor(.blue) 27 | 28 | VStack(alignment: .leading, spacing: 4) { 29 | Text(conversation.contactName) 30 | .font(.headline) 31 | 32 | Text(conversation.messagePreview) 33 | .font(.subheadline) 34 | .foregroundColor(.gray) 35 | .lineLimit(1) 36 | 37 | Text(conversation.timestamp, style: .time) 38 | .font(.caption) 39 | .foregroundColor(.gray) 40 | } 41 | 42 | Spacer() 43 | 44 | if conversation.unreadCount > 0 { 45 | Text("\(conversation.unreadCount)") 46 | .font(.caption) 47 | .foregroundColor(.white) 48 | .padding(8) 49 | .background(Color.red) 50 | .clipShape(Circle()) 51 | } 52 | } 53 | .padding(8) 54 | } 55 | } 56 | 57 | struct ConversationCellView_Previews: PreviewProvider { 58 | static var previews: some View { 59 | ConversationCellView(conversation: Conversation( 60 | contactName: "John Doe", 61 | messagePreview: "Hey, how are you?", 62 | timestamp: Date(), 63 | unreadCount: 3 64 | )) 65 | .previewLayout(.sizeThatFits) 66 | } 67 | } 68 | 69 | extension ProcessInfo { 70 | var isPreviews: Bool { 71 | self.environment["EMERGE_IS_RUNNING_FOR_SNAPSHOTS"] == "1" || self.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Examples/DemoApp/DemoApp.xcodeproj/xcshareddata/xcschemes/DemoAppTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 9 | 10 | 15 | 16 | 19 | 20 | 21 | 22 | 24 | 30 | 31 | 32 | 33 | 34 | 44 | 45 | 51 | 52 | 54 | 55 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /Examples/DemoApp/DemoModule/RatingView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RatingView.swift 3 | // DemoModule 4 | // 5 | // Created by Noah Martin on 7/3/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct RatingView: View { 11 | var rating: Double 12 | 13 | var body: some View { 14 | HStack(spacing: 4) { 15 | ForEach(0..<5) { index in 16 | Image(systemName: rating >= Double(index + 1) ? "star.fill" : "star") 17 | .foregroundColor(Color(PlatformColor.systemYellow)) 18 | .font(.body) 19 | } 20 | } 21 | } 22 | } 23 | 24 | struct RatingView_Previews: PreviewProvider { 25 | static var previews: some View { 26 | Group { 27 | RatingView(rating: 5.0) 28 | .previewLayout(.sizeThatFits) 29 | .padding() 30 | 31 | RatingView(rating: 4.9) 32 | .previewLayout(.sizeThatFits) 33 | .padding() 34 | .preferredColorScheme(.dark) 35 | 36 | RatingView(rating: 4.8) 37 | .previewLayout(.sizeThatFits) 38 | .padding() 39 | 40 | RatingView(rating: 4.7) 41 | .previewLayout(.sizeThatFits) 42 | .padding() 43 | 44 | RatingView(rating: 4.6) 45 | .previewLayout(.sizeThatFits) 46 | .padding() 47 | 48 | RatingView(rating: 4.5) 49 | .previewLayout(.sizeThatFits) 50 | .padding() 51 | } 52 | 53 | RatingView(rating: 4.4) 54 | .previewLayout(.sizeThatFits) 55 | .padding() 56 | 57 | RatingView(rating: 4.3) 58 | .previewLayout(.sizeThatFits) 59 | .padding() 60 | 61 | RatingView(rating: 4.2) 62 | .previewLayout(.sizeThatFits) 63 | .padding() 64 | 65 | RatingView(rating: 4.1) 66 | .previewLayout(.sizeThatFits) 67 | .padding() 68 | 69 | RatingView(rating: 4.0) 70 | .previewLayout(.sizeThatFits) 71 | .padding() 72 | 73 | RatingView(rating: 3.9) 74 | .previewLayout(.sizeThatFits) 75 | .padding() 76 | 77 | RatingView(rating: 3.8) 78 | .previewLayout(.sizeThatFits) 79 | .padding() 80 | 81 | RatingView(rating: 3.7) 82 | .previewLayout(.sizeThatFits) 83 | .padding() 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Sources/PreviewGallery/PreviewGallery.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PreviewGallery.swift 3 | // 4 | // 5 | // Created by Noah Martin on 7/3/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | import SnapshotPreviewsCore 11 | 12 | struct PreviewCellView: View { 13 | let previewGrouping: PreviewGrouping 14 | 15 | var previews: [SnapshotPreviewsCore.Preview] { 16 | previewGrouping.previews.flatMap { $0.previews(requiringFullscreen: false)} 17 | } 18 | 19 | var body: some View { 20 | VStack(alignment: .center) { 21 | TitleSubtitleRow( 22 | title: previewGrouping.displayName, 23 | subtitle: "\(previews.count) Preview\(previews.count != 1 ? "s" : "")" 24 | ) 25 | .padding(EdgeInsets(top: 12, leading: 16, bottom: 6, trailing: 16)) 26 | 27 | PreviewCell(preview: previews[0]) 28 | } 29 | .padding(.bottom, 8) 30 | } 31 | } 32 | 33 | /// A SwiftUI View that displays a gallery of previews organized by modules. 34 | /// 35 | /// `PreviewGallery` presents a list of modules, each containing its respective previews. 36 | /// If no previews are found, it displays a message indicating so. 37 | /// 38 | /// It should be created within a `NavigationStack`. 39 | /// 40 | /// # Example 41 | /// ```swift 42 | /// struct GalleryApp: App { 43 | /// var body: some Scene { 44 | /// WindowGroup { 45 | /// NavigationStack { 46 | /// PreviewGallery() 47 | /// } 48 | /// } 49 | /// } 50 | /// } 51 | /// ``` 52 | public struct PreviewGallery: View { 53 | /// The data source containing preview information. 54 | let data: PreviewData 55 | 56 | @State private var searchText = "" 57 | 58 | /// Initializes a new `PreviewGallery` with the given preview data. 59 | /// 60 | /// - Parameter data: The `PreviewData` to use for populating the gallery. 61 | /// If `nil`, the default `PreviewData` will be used. 62 | @MainActor 63 | public init(data: PreviewData? = nil) { 64 | self.data = data ?? .default 65 | } 66 | 67 | public var body: some View { 68 | if data.modules.count > 0 { 69 | List { 70 | ForEach(Array(data.modules).sorted().filterWithText(searchText, { $0 }), id: \.self) { module in 71 | ModulePreviews(module: module, data: data) 72 | } 73 | } 74 | .navigationTitle("Modules") 75 | .searchable(text: $searchText) 76 | } else { 77 | Text("No previews found") 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Sources/PreviewGallery/PreviewData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PreviewData.swift 3 | // 4 | // 5 | // Created by Noah Martin on 7/3/23. 6 | // 7 | 8 | import Foundation 9 | import SnapshotPreviewsCore 10 | 11 | struct PreviewGrouping: Identifiable { 12 | let id: String 13 | 14 | var displayName: String { 15 | previews[0].displayName 16 | } 17 | 18 | func previewTypes(requiringFullscreen: Bool) -> [PreviewType] { 19 | return previews.filter { !$0.previews(requiringFullscreen: requiringFullscreen).isEmpty } 20 | } 21 | 22 | let previews: [PreviewType] 23 | } 24 | 25 | 26 | /// A structure that manages a collection of preview types. 27 | /// 28 | /// `PreviewData` is the backing data for PreviewGallery. 29 | public struct PreviewData { 30 | /// The collection of preview types managed by this instance. 31 | let previews: [PreviewType] 32 | 33 | /// Initializes a new `PreviewData` instance with the given previews. 34 | /// 35 | /// - Parameter previews: An array of `PreviewType` instances to be managed by this `PreviewData`. 36 | public init(previews: [PreviewType]) { 37 | self.previews = previews 38 | } 39 | 40 | /// Retrieves and sorts the previews for a specific module. 41 | /// 42 | /// This method filters the previews to include only those from the specified module, 43 | /// then sorts them alphabetically by their type names. 44 | /// 45 | /// - Parameter module: The name of the module to filter previews for. 46 | /// - Returns: An array of `PreviewType` instances belonging to the specified module, sorted by type name. 47 | func previews(in module: String) -> [PreviewGrouping] { 48 | let modulePreviews = previews.filter { $0.module == module } 49 | return Dictionary(grouping: modulePreviews) { p in 50 | p.fileID ?? p.typeName 51 | }.values.map { previews in 52 | PreviewGrouping(id: previews[0].fileID ?? previews[0].typeName, previews: previews) 53 | }.sorted { $0.displayName < $1.displayName } 54 | } 55 | 56 | /// A set of all unique module names represented in the previews. 57 | var modules: Set { 58 | Set(previews.map { $0.module }) 59 | } 60 | 61 | /// A default instance of `PreviewData` that automatically finds all available previews. 62 | /// 63 | /// This property uses `FindPreviews.findPreviews()` to populate the previews. 64 | @MainActor public static var `default`: PreviewData { 65 | self.init(previews: FindPreviews.findPreviews()) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Sources/SnapshotPreviewsCore/MetadataTypes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MetadataTypes.swift 3 | // 4 | // 5 | // Created by Noah Martin on 7/3/23. 6 | // 7 | 8 | import Foundation 9 | 10 | // https://github.com/apple/swift/blob/f13167d9d162e69d1aac6ce022b19f6a80c62aba/include/swift/ABI/Metadata.h#L2472-L2643 11 | struct ProtocolConformanceDescriptor { 12 | let protocolDescriptor: Int32 13 | var nominalTypeDescriptor: Int32 14 | let protocolWitnessTable: Int32 15 | let conformanceFlags: ConformanceFlags 16 | } 17 | 18 | // https://github.com/apple/swift/blob/f13167d9d162e69d1aac6ce022b19f6a80c62aba/include/swift/ABI/Metadata.h#L3139-L3222 19 | struct ProtocolDescriptor { 20 | let flags: UInt32 21 | let parent: Int32 22 | let name: Int32 23 | let numRequirementsInSignature: UInt32 24 | let numRequirements: UInt32 25 | let associatedTypeNames: Int32 26 | } 27 | 28 | // https://github.com/apple/swift/blob/f13167d9d162e69d1aac6ce022b19f6a80c62aba/include/swift/ABI/MetadataValues.h#L1203-L1234 29 | public enum ContextDescriptorKind: UInt8 { 30 | case Module = 0 31 | case Extension = 1 32 | case Anonymous = 2 33 | case `Protocol` = 3 34 | case OpaqueType = 4 35 | case Class = 16 36 | case Struct = 17 37 | case Enum = 18 38 | } 39 | 40 | // https://github.com/apple/swift/blob/f13167d9d162e69d1aac6ce022b19f6a80c62aba/include/swift/ABI/MetadataValues.h#L1237-L1312 41 | struct ContextDescriptorFlags { 42 | 43 | private let rawFlags: UInt32 44 | 45 | var kind: ContextDescriptorKind? { 46 | let value = UInt8(rawFlags & 0x1F) 47 | return ContextDescriptorKind(rawValue: value) 48 | } 49 | } 50 | 51 | struct TargetModuleContextDescriptor { 52 | let flags: ContextDescriptorFlags 53 | let parent: Int32 54 | let name: Int32 55 | let accessFunction: Int32 56 | } 57 | 58 | // https://github.com/apple/swift/blob/f13167d9d162e69d1aac6ce022b19f6a80c62aba/include/swift/ABI/MetadataValues.h#L372-L398 59 | enum TypeReferenceKind: UInt32 { 60 | case DirectTypeDescriptor = 0 61 | case IndirectTypeDescriptor = 1 62 | case DirectObjCClassName = 2 63 | case IndirectObjCClass = 3 64 | } 65 | 66 | // https://github.com/apple/swift/blob/f13167d9d162e69d1aac6ce022b19f6a80c62aba/include/swift/ABI/MetadataValues.h#L582-L687 67 | struct ConformanceFlags { 68 | 69 | private let rawFlags: UInt32 70 | 71 | var kind: TypeReferenceKind? { 72 | let rawKind = (rawFlags & Self.TypeMetadataKindMask) >> Self.TypeMetadataKindShift 73 | return TypeReferenceKind(rawValue: rawKind) 74 | } 75 | 76 | private static let TypeMetadataKindMask: UInt32 = 0x7 << Self.TypeMetadataKindShift 77 | private static let TypeMetadataKindShift = 3 78 | } 79 | -------------------------------------------------------------------------------- /Examples/DemoApp/DemoApp/TestViews/PreviewVariants.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct PreviewView: Identifiable { 4 | let id = UUID() 5 | let value: AnyView 6 | let name: String 7 | } 8 | 9 | extension View { 10 | func previewVariant(named name: String) -> PreviewView { 11 | PreviewView(value: AnyView(self), name: name) 12 | } 13 | } 14 | 15 | struct PreviewVariants: View { 16 | 17 | init( 18 | modifiers: [NamedViewModifier] = .previewDefault, 19 | layout: PreviewLayout = .device, 20 | @ArrayBuilder views: () -> [PreviewView]) 21 | { 22 | self.modifiers = modifiers 23 | self.layout = layout 24 | self.views = views() 25 | } 26 | 27 | var body: some View { 28 | ForEach(modifiers) { modifier in 29 | ForEach(views) { view in 30 | let displayName = [view.name, modifier.name] 31 | .filter { !$0.isEmpty } 32 | .joined(separator: ", ") 33 | 34 | AnyView(modifier.value(view.value)) 35 | .previewDisplayName(displayName) 36 | .previewLayout(layout) 37 | } 38 | } 39 | } 40 | 41 | private let modifiers: [NamedViewModifier] 42 | private let layout: PreviewLayout 43 | private let views: [PreviewView] 44 | } 45 | 46 | struct NamedViewModifier { 47 | var name: String 48 | var value: (any View) -> any View 49 | } 50 | 51 | extension NamedViewModifier: Identifiable { 52 | var id: String { name } 53 | } 54 | 55 | extension NamedViewModifier { 56 | static var unmodified: NamedViewModifier { 57 | .init(name: "", value: { $0 }) 58 | } 59 | 60 | static var darkMode: NamedViewModifier { 61 | .init(name: "Dark mode", value: { $0.preferredColorScheme(.dark).environment(\.colorScheme, .dark) }) 62 | } 63 | 64 | static var landscape: NamedViewModifier { 65 | .init(name: "Landscape", value: { $0.previewInterfaceOrientation(.landscapeLeft) }) 66 | } 67 | 68 | static var xxlTextSize: NamedViewModifier { 69 | .init(name: "XXL Text Size", value: { $0.dynamicTypeSize(.xxxLarge) }) 70 | } 71 | 72 | @available(macOS, unavailable) 73 | @available(watchOS, unavailable) 74 | @available(visionOS, unavailable) 75 | @available(tvOS, unavailable) 76 | static var accessibility: NamedViewModifier { 77 | .init(name: "Accessibility", value: { $0.emergeAccessibility(true) }) 78 | } 79 | 80 | static var rtl: NamedViewModifier { 81 | .init(name: "RTL", value: { $0.environment(\.layoutDirection, .rightToLeft) }) 82 | } 83 | } 84 | 85 | extension [NamedViewModifier] { 86 | /// The default named view modifiers in a ``PreviewVariants``. 87 | static var previewDefault: [NamedViewModifier] { 88 | #if os(iOS) 89 | if UserDefaults.standard.bool(forKey: "NSDoubleLocalizedStrings") { 90 | return [.unmodified, .darkMode, .xxlTextSize, .rtl, .landscape] 91 | } 92 | return [.unmodified, .darkMode, .xxlTextSize, .rtl, .accessibility, .landscape] 93 | #else 94 | [.unmodified, .darkMode, .rtl] 95 | #endif 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Examples/DemoApp/DemoApp.xcodeproj/xcshareddata/xcschemes/DemoApp.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 34 | 35 | 36 | 37 | 47 | 49 | 55 | 56 | 57 | 58 | 64 | 66 | 72 | 73 | 74 | 75 | 77 | 78 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.7 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "SnapshotPreviews", 8 | platforms: [.iOS(.v15), .macOS(.v12), .watchOS(.v9)], 9 | products: [ 10 | // Products define the executables and libraries a package produces, making them visible to other packages. 11 | .library( 12 | name: "PreviewGallery", 13 | type: .static, // Replace this to build dynamic 14 | targets: ["PreviewGallery"]), 15 | // Test library to import in your XCTest target. 16 | // This is the only library that depends on XCTest.framework 17 | .library( 18 | name: "SnapshottingTests", 19 | type: .static, // Replace this to build dynamic 20 | targets: ["SnapshottingTests"]), 21 | // Link the main app to this target to use custom snapshot settings 22 | // This lib does not get inserted when running tests to avoid 23 | // duplicate symbols. 24 | .library( 25 | name: "SnapshotPreferences", 26 | targets: ["SnapshotPreferences"]), 27 | // Core functionality for snapshotting exported from the internal package 28 | .library( 29 | name: "SnapshotPreviewsCore", 30 | targets: ["SnapshotPreviewsCore"]), 31 | // Dynamic library that your main app will have inserted to generate previews 32 | .library( 33 | name: "Snapshotting", 34 | type: .dynamic, 35 | targets: ["Snapshotting"]), 36 | ], 37 | dependencies: [ 38 | .package(url: "https://github.com/swhitty/FlyingFox.git", exact: "0.16.0"), 39 | .package(url: "https://github.com/EmergeTools/SimpleDebugger.git", exact: "1.0.0"), 40 | ], 41 | targets: [ 42 | // Targets are the basic building blocks of a package, defining a module or a test suite. 43 | // Targets can depend on other targets in this package and products from dependencies. 44 | // Target that provides the XCTest 45 | .target(name: "SnapshottingTestsObjc", dependencies: [.product(name: "SimpleDebugger", package: "SimpleDebugger", condition: .when(platforms: [.iOS, .macOS, .macCatalyst]))]), 46 | .target(name: "SnapshottingTests", dependencies: ["SnapshotPreviewsCore", "SnapshottingTestsObjc"]), 47 | .target(name: "SnapshotSharedModels"), 48 | // Core functionality 49 | .target(name: "SnapshotPreviewsCore", dependencies: ["PreviewsSupport", "SnapshotSharedModels"]), 50 | .target(name: "SnapshotPreferences", dependencies: ["SnapshotSharedModels"]), 51 | // Inserted dylib 52 | .target(name: "Snapshotting", dependencies: ["SnapshottingSwift"]), 53 | // Swift code in the inserted dylib 54 | .target(name: "SnapshottingSwift", dependencies: ["SnapshotPreviewsCore", .product(name: "FlyingFox", package: "FlyingFox")]), 55 | .target(name: "PreviewGallery", dependencies: ["SnapshotPreviewsCore", "SnapshotPreferences"]), 56 | .binaryTarget( 57 | name: "PreviewsSupport", 58 | path: "PreviewsSupport/PreviewsSupport.xcframework"), 59 | .testTarget( 60 | name: "SnapshotPreviewsTests", 61 | dependencies: ["SnapshotPreviewsCore"]), 62 | ], 63 | cxxLanguageStandard: .cxx11 64 | ) 65 | -------------------------------------------------------------------------------- /PreviewsSupport/PreviewsSupport/PreviewsSupport.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PreviewsSupport.swift 3 | // PreviewsSupport 4 | // 5 | // Created by Noah Martin on 10/18/23. 6 | // 7 | 8 | import SwiftUI 9 | private import DeveloperToolsSupport 10 | #if canImport(UIKit) 11 | import UIKit 12 | #endif 13 | 14 | public protocol MakeViewProvider { 15 | var makeView: @MainActor () -> any View { get } 16 | } 17 | 18 | #if canImport(UIKit) && !os(watchOS) 19 | public protocol MakeUIViewProvider { 20 | var makeView: @MainActor () -> UIView { get } 21 | } 22 | 23 | public protocol MakeViewControllerProvider { 24 | var makeViewController: @MainActor () -> UIViewController { get } 25 | } 26 | 27 | @available(iOS 17.0, macOS 14.0, tvOS 17.0, *) 28 | @_spi(Private) 29 | extension UIViewPreviewSource: MakeUIViewProvider { } 30 | 31 | @available(iOS 17.0, macOS 14.0, tvOS 17.0, *) 32 | @_spi(Private) 33 | extension UIViewControllerPreviewSource: MakeViewControllerProvider { } 34 | #endif 35 | 36 | @available(iOS 17.0, macOS 14.0, watchOS 10.0, tvOS 17.0, *) 37 | @_spi(Private) 38 | extension ViewPreviewSource: MakeViewProvider { } 39 | 40 | @available(iOS 18.0, macOS 15.0, watchOS 11.0, tvOS 18.0, *) 41 | @_spi(Private) 42 | extension DefaultPreviewSource: MakeViewProvider where A == SwiftUI.ViewPreviewBody { 43 | public var makeView: @MainActor () -> any View { 44 | switch structure { 45 | case .singlePreview(let makeBody): 46 | return { 47 | makeBody().body 48 | } 49 | // These cases return a placeholder view 50 | @unknown default: 51 | return { 52 | Text("Unhandled SwiftUI case in DefaultPreviewSource") 53 | } 54 | } 55 | } 56 | } 57 | 58 | #if canImport(UIKit) && !os(watchOS) 59 | @available(iOS 18.0, macOS 15.0, watchOS 11.0, tvOS 18.0, *) 60 | @_spi(Private) 61 | extension DefaultPreviewSource: MakeUIViewProvider where A == UIView { 62 | public var makeView: @MainActor () -> UIView { 63 | switch structure { 64 | case .singlePreview(let makeBody): 65 | return makeBody 66 | // These cases return a placeholder view 67 | @unknown default: 68 | return { 69 | let label = UILabel() 70 | label.text = "Unhandled UIView case in DefaultPreviewSource" 71 | return label 72 | } 73 | } 74 | } 75 | } 76 | 77 | class UnhandledViewController: UIViewController { 78 | let label = UILabel() 79 | 80 | override func viewDidLoad() { 81 | super.viewDidLoad() 82 | label.text = "Unhandled UIViewController case in DefaultPreviewSource" 83 | label.translatesAutoresizingMaskIntoConstraints = false 84 | view.addSubview(label) 85 | label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true 86 | label.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true 87 | } 88 | } 89 | 90 | 91 | @available(iOS 18.0, macOS 15.0, watchOS 11.0, tvOS 18.0, *) 92 | @_spi(Private) 93 | extension DefaultPreviewSource: MakeViewControllerProvider where A == UIViewController { 94 | public var makeViewController: @MainActor () -> UIViewController { 95 | switch structure { 96 | case .singlePreview(let makeBody): 97 | return makeBody 98 | // These cases return a placeholder view 99 | @unknown default: 100 | return { 101 | UnhandledViewController() 102 | } 103 | } 104 | } 105 | } 106 | 107 | #endif 108 | -------------------------------------------------------------------------------- /Sources/SnapshottingTests/PreviewLayoutTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PreviewLayoutTest.swift 3 | // 4 | // 5 | // Created by Noah Martin on 8/9/24. 6 | // 7 | 8 | import Foundation 9 | @_implementationOnly import SnapshotPreviewsCore 10 | import SwiftUI 11 | import XCTest 12 | 13 | /// A test class for verifying Xcode previews by forcing a layout pass on each one. 14 | /// 15 | /// This class is designed to discover and test SwiftUI previews to ensure they can be laid out without crashing. 16 | /// It provides mechanisms for filtering previews and performs layout tests on different platforms. 17 | open class PreviewLayoutTest: PreviewBaseTest, PreviewFilters { 18 | 19 | /// Returns an optional array of preview names to be included in the snapshot testing. This also supports Regex format. 20 | /// 21 | /// Override this method to specify which previews should be included in the test. 22 | /// - Returns: An optional array of String containing the names of previews to be included. 23 | open class func snapshotPreviews() -> [String]? { 24 | nil 25 | } 26 | 27 | /// Returns an optional array of preview names to be excluded from the snapshot testing. This also supports Regex format 28 | /// 29 | /// Override this method to specify which previews should be excluded from the test. 30 | /// - Returns: An optional array of String containing the names of previews to be excluded. 31 | open class func excludedSnapshotPreviews() -> [String]? { 32 | nil 33 | } 34 | 35 | static private var previews: [PreviewType] = [] 36 | 37 | /// Discovers all relevant previews based on inclusion and exclusion filters. Subclasses should NOT override this method. 38 | /// 39 | /// This method uses `FindPreviews` to locate all previews, applying any specified filters. 40 | /// - Returns: An array of `DiscoveredPreview` objects representing the found previews. 41 | @MainActor 42 | override class func discoverPreviews() -> [DiscoveredPreview] { 43 | previews = FindPreviews.findPreviews(included: Self.snapshotPreviews(), excluded: Self.excludedSnapshotPreviews()) 44 | return previews.map { DiscoveredPreview.from(previewType: $0) } 45 | } 46 | 47 | /// Tests a specific preview by performing a layout pass. Subclasses should NOT override this method. 48 | /// 49 | /// This method creates a hosting controller for the preview and forces a layout pass to verify 50 | /// that no crashes occur during the process. 51 | /// 52 | /// - Parameter preview: A `DiscoveredPreviewAndIndex` object representing the preview to be tested. 53 | @MainActor 54 | override func testPreview(_ preview: DiscoveredPreviewAndIndex) { 55 | let previewType = Self.previews.first { $0.typeName == preview.preview.typeName } 56 | guard let preview = previewType?.previews[preview.index] else { 57 | XCTFail("Preview not found") 58 | return 59 | } 60 | 61 | #if canImport(UIKit) && !os(watchOS) 62 | let hostingVC = UIHostingController(rootView: AnyView(preview.view())) 63 | #if os(visionOS) || os(watchOS) 64 | hostingVC.view.sizeThatFits(CGSize(width: 100, height: CGFloat.greatestFiniteMagnitude)) 65 | #else 66 | hostingVC.view.sizeThatFits(UIScreen.main.bounds.size) 67 | #endif 68 | #elseif canImport(AppKit) 69 | let hostingVC = NSHostingController(rootView: AnyView(preview.view())) 70 | _ = hostingVC.sizeThatFits(in: NSScreen.main!.frame.size) 71 | #else 72 | _ = ImageRenderer(content: AnyView(preview.view())).uiImage 73 | #endif 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Examples/UnitTestMigration/UnitTestMigration/SnapshotTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SnapshotTest.swift 3 | // TestAppInstall 4 | // 5 | // Created by Noah Martin on 9/11/24. 6 | // 7 | 8 | import SwiftUI 9 | 10 | // Change your unit test from extending from `XCTestCase` to `SnapshotTest` 11 | // to convert snapshot tests running as XCTests into previews. 12 | // Any call to `assertSnapshot` will automatically be added to the previews. 13 | // Note the class containing your tests should also conform to `PreviewProvider`. 14 | @objcMembers 15 | class SnapshotTest: NSObject { 16 | 17 | required override init() { } 18 | 19 | static var previews: some View { 20 | generatePreviews() 21 | } 22 | 23 | func assertSnapshot( 24 | of value: @autoclosure () throws -> Value, 25 | as snapshotting: SnapshotType, 26 | named name: String? = nil 27 | ) where Value: SwiftUI.View { 28 | try! SnapshotTest.collectedViews.append(AnyView(value().previewLayout(snapshotting.layout))) 29 | } 30 | 31 | func assertSnapshot( 32 | of value: @autoclosure () throws -> UIView, 33 | as snapshotting: SnapshotType, 34 | named name: String? = nil) { 35 | try! SnapshotTest.collectedViews.append( 36 | AnyView( 37 | UIViewWrapper(view: value()).previewLayout(snapshotting.layout))) 38 | } 39 | 40 | func assertSnapshot( 41 | of value: @autoclosure () throws -> UIViewController, 42 | as snapshotting: SnapshotType, 43 | named name: String? = nil) { 44 | try! SnapshotTest.collectedViews.append( 45 | AnyView(UIViewControllerWrapper(viewController: value()) 46 | .previewLayout(snapshotting.layout))) 47 | } 48 | 49 | private static var collectedViews: [AnyView] = [] 50 | 51 | private static func generatePreviews() -> some View { 52 | let testCase = self.init() 53 | collectedViews.removeAll() 54 | testCase.runAllTestMethods() 55 | return ForEach(0.. UIView { 81 | return view 82 | } 83 | 84 | func updateUIView(_ uiView: UIView, context: Context) { } 85 | } 86 | 87 | private struct UIViewControllerWrapper: UIViewControllerRepresentable { 88 | 89 | let viewController: UIViewController 90 | 91 | func makeUIViewController(context: Context) -> UIViewController { 92 | return viewController 93 | } 94 | 95 | func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { } 96 | } 97 | 98 | struct SnapshotType { 99 | let precision: Float 100 | let layout: PreviewLayout 101 | } 102 | 103 | extension SnapshotType where Value: UIView, Format == UIImage { 104 | static var image: SnapshotType { 105 | return SnapshotType(precision: 1, layout: .sizeThatFits) 106 | } 107 | } 108 | 109 | extension SnapshotType where Value: SwiftUI.View, Format == UIImage { 110 | static var image: SnapshotType { 111 | return .image() 112 | } 113 | 114 | static func image( 115 | drawHierarchyInKeyWindow: Bool = false, 116 | precision: Float = 1, 117 | perceptualPrecision: Float = 1, 118 | layout: PreviewLayout = .sizeThatFits, 119 | traits: UITraitCollection = .init() 120 | ) -> SnapshotType { 121 | return SnapshotType(precision: precision, layout: layout) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /Sources/SnapshotPreviewsCore/ScrollExpansion.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScrollExpansion.swift 3 | // 4 | // 5 | // Created by Noah Martin on 8/22/24. 6 | // 7 | 8 | import Foundation 9 | #if canImport(UIKit) 10 | import UIKit 11 | #endif 12 | 13 | #if canImport(AppKit) 14 | import AppKit 15 | #endif 16 | 17 | protocol ContentHeightProviding { 18 | var contentHeight: CGFloat { get } 19 | 20 | var visibleContentHeight: CGFloat { get } 21 | } 22 | 23 | protocol FirstScrollViewProviding { 24 | var firstScrollView: ContentHeightProviding? { get } 25 | } 26 | 27 | #if !os(watchOS) 28 | protocol ScrollExpansionProviding: AnyObject, FirstScrollViewProviding { 29 | var previousHeight: CGFloat? { get set } 30 | var heightAnchor: NSLayoutConstraint? { get } 31 | var supportsExpansion: Bool { get } 32 | } 33 | 34 | extension ScrollExpansionProviding { 35 | func updateHeight(_ complete: (() -> Void)) { 36 | // If heightAnchor isn't set, this was a fixed size and we don't expand the scroll view 37 | guard let heightAnchor else { 38 | complete() 39 | return 40 | } 41 | 42 | let supportsExpansion = supportsExpansion 43 | let scrollView = firstScrollView 44 | if let scrollView, supportsExpansion { 45 | let diff = Int(scrollView.contentHeight - scrollView.visibleContentHeight) 46 | if abs(diff) > 0 { 47 | if previousHeight != nil || diff > 0 { 48 | if let previousHeight { 49 | // Check if expansion isn't working and we should give up. 50 | // Could happen if the view is constrained to not grow, such as a half sheet 51 | guard abs(previousHeight - scrollView.visibleContentHeight) >= 1 else { 52 | complete() 53 | return 54 | } 55 | } 56 | previousHeight = scrollView.visibleContentHeight 57 | heightAnchor.constant += CGFloat(diff) 58 | } else { 59 | complete() 60 | } 61 | } else { 62 | complete() 63 | } 64 | } else { 65 | complete() 66 | } 67 | } 68 | } 69 | #endif 70 | 71 | #if canImport(UIKit) && !os(visionOS) && !os(watchOS) && !os(tvOS) 72 | extension UIScrollView: ContentHeightProviding { 73 | 74 | var contentHeight: CGFloat { 75 | contentSize.height 76 | } 77 | 78 | var visibleContentHeight: CGFloat { 79 | frame.height - (adjustedContentInset.top + adjustedContentInset.bottom) 80 | } 81 | } 82 | 83 | extension UIView: FirstScrollViewProviding { 84 | var firstScrollView: ContentHeightProviding? { 85 | var subviews = subviews 86 | while !subviews.isEmpty { 87 | let subview = subviews.removeFirst() 88 | // Don’t expand UITextView, it can cause flakes 89 | guard !(subview is UITextView) else { 90 | continue 91 | } 92 | 93 | subviews.append(contentsOf: subview.subviews) 94 | if let scrollView = subview as? UIScrollView { 95 | return scrollView 96 | } 97 | } 98 | return nil 99 | } 100 | } 101 | 102 | extension UIViewController: FirstScrollViewProviding { 103 | var firstScrollView: ContentHeightProviding? { 104 | view?.firstScrollView 105 | } 106 | } 107 | #endif 108 | 109 | #if canImport(AppKit) && !targetEnvironment(macCatalyst) 110 | extension NSScrollView: ContentHeightProviding { 111 | 112 | var contentHeight: CGFloat { 113 | documentView?.frame.size.height ?? 0 114 | } 115 | 116 | var visibleContentHeight: CGFloat { 117 | frame.height - (contentInsets.top + contentInsets.bottom) 118 | } 119 | } 120 | 121 | extension NSView: FirstScrollViewProviding { 122 | var firstScrollView: ContentHeightProviding? { 123 | var subviews = subviews 124 | while !subviews.isEmpty { 125 | let subview = subviews.removeFirst() 126 | if let scrollView = subview as? NSScrollView { 127 | // Don’t expand NSTextView, it can cause flakes 128 | guard !(scrollView.documentView is NSTextView) else { 129 | continue 130 | } 131 | 132 | return scrollView 133 | } 134 | subviews.append(contentsOf: subview.subviews) 135 | } 136 | return nil 137 | } 138 | } 139 | 140 | extension NSViewController: FirstScrollViewProviding { 141 | var firstScrollView: ContentHeightProviding? { 142 | view.firstScrollView 143 | } 144 | } 145 | #endif 146 | -------------------------------------------------------------------------------- /PreviewsSupport/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | xcodebuild archive \ 6 | -scheme PreviewsSupport \ 7 | -archivePath ./PreviewsSupport-iphonesimulator.xcarchive \ 8 | -sdk iphonesimulator \ 9 | -destination 'generic/platform=iOS Simulator' \ 10 | BUILD_LIBRARY_FOR_DISTRIBUTION=YES \ 11 | INSTALL_PATH='Library/Frameworks' \ 12 | SKIP_INSTALL=NO \ 13 | CLANG_CXX_LANGUAGE_STANDARD=c++17 14 | 15 | xcodebuild archive \ 16 | -scheme PreviewsSupport \ 17 | -archivePath ./PreviewsSupport-iphoneos.xcarchive \ 18 | -sdk iphoneos \ 19 | -destination 'generic/platform=iOS' \ 20 | BUILD_LIBRARY_FOR_DISTRIBUTION=YES \ 21 | INSTALL_PATH='Library/Frameworks' \ 22 | SKIP_INSTALL=NO \ 23 | CLANG_CXX_LANGUAGE_STANDARD=c++17 24 | 25 | xcodebuild archive \ 26 | -scheme PreviewsSupport \ 27 | -archivePath ./PreviewsSupport-watchossimulator.xcarchive \ 28 | -sdk watchsimulator \ 29 | -destination 'generic/platform=watchOS Simulator' \ 30 | BUILD_LIBRARY_FOR_DISTRIBUTION=YES \ 31 | INSTALL_PATH='Library/Frameworks' \ 32 | SKIP_INSTALL=NO \ 33 | CLANG_CXX_LANGUAGE_STANDARD=c++17 34 | 35 | xcodebuild archive \ 36 | -scheme PreviewsSupport \ 37 | -archivePath ./PreviewsSupport-tvos.xcarchive \ 38 | -sdk appletvos \ 39 | -destination 'generic/platform=tvOS' \ 40 | BUILD_LIBRARY_FOR_DISTRIBUTION=YES \ 41 | INSTALL_PATH='Library/Frameworks' \ 42 | SKIP_INSTALL=NO \ 43 | CLANG_CXX_LANGUAGE_STANDARD=c++17 44 | 45 | xcodebuild archive \ 46 | -scheme PreviewsSupport \ 47 | -archivePath ./PreviewsSupport-tvossimulator.xcarchive \ 48 | -sdk appletvsimulator \ 49 | -destination 'generic/platform=tvOS Simulator' \ 50 | BUILD_LIBRARY_FOR_DISTRIBUTION=YES \ 51 | INSTALL_PATH='Library/Frameworks' \ 52 | SKIP_INSTALL=NO \ 53 | CLANG_CXX_LANGUAGE_STANDARD=c++17 54 | 55 | xcodebuild archive \ 56 | -scheme PreviewsSupport \ 57 | -archivePath ./PreviewsSupport-watchos.xcarchive \ 58 | -sdk watchos \ 59 | -destination 'generic/platform=watchOS' \ 60 | BUILD_LIBRARY_FOR_DISTRIBUTION=YES \ 61 | INSTALL_PATH='Library/Frameworks' \ 62 | SKIP_INSTALL=NO \ 63 | CLANG_CXX_LANGUAGE_STANDARD=c++17 64 | 65 | xcodebuild archive \ 66 | -scheme PreviewsSupport \ 67 | -archivePath ./PreviewsSupport-visionossimulator.xcarchive \ 68 | -sdk xrsimulator \ 69 | -destination 'generic/platform=visionOS Simulator' \ 70 | BUILD_LIBRARY_FOR_DISTRIBUTION=YES \ 71 | INSTALL_PATH='Library/Frameworks' \ 72 | SKIP_INSTALL=NO \ 73 | CLANG_CXX_LANGUAGE_STANDARD=c++17 74 | 75 | xcodebuild archive \ 76 | -scheme PreviewsSupport \ 77 | -archivePath ./PreviewsSupport-visionos.xcarchive \ 78 | -sdk xros \ 79 | BUILD_LIBRARY_FOR_DISTRIBUTION=YES \ 80 | INSTALL_PATH='Library/Frameworks' \ 81 | SKIP_INSTALL=NO \ 82 | CLANG_CXX_LANGUAGE_STANDARD=c++17 83 | 84 | xcodebuild archive \ 85 | -scheme PreviewsSupport \ 86 | -archivePath ./PreviewsSupport-catalyst.xcarchive \ 87 | -sdk macosx \ 88 | -destination 'generic/platform=macOS,variant=Mac Catalyst' \ 89 | BUILD_LIBRARY_FOR_DISTRIBUTION=YES \ 90 | INSTALL_PATH='Library/Frameworks' \ 91 | SKIP_INSTALL=NO \ 92 | CLANG_CXX_LANGUAGE_STANDARD=c++17 93 | 94 | xcodebuild archive \ 95 | -scheme PreviewsSupport \ 96 | -archivePath ./PreviewsSupport-macosx.xcarchive \ 97 | -sdk macosx \ 98 | BUILD_LIBRARY_FOR_DISTRIBUTION=YES \ 99 | INSTALL_PATH='Library/Frameworks' \ 100 | SKIP_INSTALL=NO \ 101 | CLANG_CXX_LANGUAGE_STANDARD=c++17 102 | 103 | xcodebuild -create-xcframework \ 104 | -framework ./PreviewsSupport-iphonesimulator.xcarchive/Products/Library/Frameworks/PreviewsSupport.framework \ 105 | -framework ./PreviewsSupport-iphoneos.xcarchive/Products/Library/Frameworks/PreviewsSupport.framework \ 106 | -framework ./PreviewsSupport-watchossimulator.xcarchive/Products/Library/Frameworks/PreviewsSupport.framework \ 107 | -framework ./PreviewsSupport-watchos.xcarchive/Products/Library/Frameworks/PreviewsSupport.framework \ 108 | -framework ./PreviewsSupport-tvossimulator.xcarchive/Products/Library/Frameworks/PreviewsSupport.framework \ 109 | -framework ./PreviewsSupport-tvos.xcarchive/Products/Library/Frameworks/PreviewsSupport.framework \ 110 | -framework ./PreviewsSupport-visionos.xcarchive/Products/Library/Frameworks/PreviewsSupport.framework \ 111 | -framework ./PreviewsSupport-visionossimulator.xcarchive/Products/Library/Frameworks/PreviewsSupport.framework \ 112 | -framework ./PreviewsSupport-macosx.xcarchive/Products/Library/Frameworks/PreviewsSupport.framework \ 113 | -framework ./PreviewsSupport-catalyst.xcarchive/Products/Library/Frameworks/PreviewsSupport.framework \ 114 | -output ./PreviewsSupport.xcframework 115 | -------------------------------------------------------------------------------- /Sources/SnapshotPreviewsCore/ExpandingViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExpandingViewController.swift 3 | // TestAppSwiftUI 4 | // 5 | // Created by Noah Martin on 6/30/23. 6 | // 7 | 8 | import Foundation 9 | #if canImport(UIKit) 10 | import UIKit 11 | #endif 12 | import SwiftUI 13 | import SnapshotSharedModels 14 | 15 | #if canImport(UIKit) && !os(visionOS) && !os(watchOS) && !os(tvOS) 16 | 17 | public final class ExpandingViewController: UIHostingController, ScrollExpansionProviding { 18 | 19 | var supportsExpansion: Bool { 20 | rootView.supportsExpansion 21 | } 22 | 23 | private let HeightExpansionTimeLimitInSeconds: UInt64 = 30 24 | 25 | private var didCall = false 26 | var previousHeight: CGFloat? 27 | 28 | var heightAnchor: NSLayoutConstraint? 29 | private var widthAnchor: NSLayoutConstraint? 30 | 31 | private var startTime: UInt64? 32 | private var timer: Timer? 33 | 34 | public var expansionSettled: ((EmergeRenderingMode?, Float?, Bool?, Bool?, Error?) -> Void)? { 35 | didSet { didCall = false } 36 | } 37 | 38 | init(rootView: Content) { 39 | super.init(rootView: EmergeModifierView(wrapped: rootView)) 40 | 41 | if #available(iOS 16, *) { 42 | sizingOptions = .intrinsicContentSize 43 | } 44 | view.translatesAutoresizingMaskIntoConstraints = false 45 | view.backgroundColor = .clear 46 | } 47 | 48 | @MainActor required dynamic init?(coder aDecoder: NSCoder) { 49 | fatalError("init(coder:) has not been implemented") 50 | } 51 | 52 | public func removeConstraints() { 53 | heightAnchor?.isActive = false 54 | widthAnchor?.isActive = false 55 | heightAnchor = nil 56 | widthAnchor = nil 57 | previousHeight = nil 58 | } 59 | 60 | public func setupView(layout: PreviewLayout) { 61 | removeConstraints() 62 | switch layout { 63 | case let .fixed(width: width, height: height): 64 | widthAnchor = view.widthAnchor.constraint(equalToConstant: width) 65 | widthAnchor?.isActive = true 66 | heightAnchor = view.heightAnchor.constraint(equalToConstant: height) 67 | heightAnchor?.isActive = true 68 | default: 69 | let fittingSize = sizeThatFits(in: UIScreen.main.bounds.size) 70 | widthAnchor = view.widthAnchor.constraint(greaterThanOrEqualToConstant: fittingSize.width) 71 | widthAnchor?.isActive = true 72 | heightAnchor = view.heightAnchor.constraint(greaterThanOrEqualToConstant: fittingSize.height) 73 | heightAnchor?.isActive = true 74 | } 75 | } 76 | 77 | private func runCallback(_ error: Error? = nil) { 78 | guard !didCall else { return } 79 | 80 | didCall = true 81 | expansionSettled?(rootView.emergeRenderingMode, rootView.precision, rootView.accessibilityEnabled, rootView.appStoreSnapshot, error) 82 | stopAndResetTimer() 83 | } 84 | 85 | public override func viewDidLayoutSubviews() { 86 | super.viewDidLayoutSubviews() 87 | updateScrollViewHeight() 88 | } 89 | 90 | public func updateScrollViewHeight() { 91 | // Timeout limit 92 | if timer == nil && heightAnchor != nil && supportsExpansion && firstScrollView != nil { 93 | startTimer() 94 | } 95 | 96 | guard expansionSettled != nil else { 97 | runCallback() 98 | return 99 | } 100 | 101 | updateHeight { 102 | runCallback() 103 | } 104 | } 105 | 106 | // MARK: - Timer 107 | 108 | func startTimer() { 109 | guard timer == nil else { 110 | print("Timer already exists") 111 | return 112 | } 113 | startTime = clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW) 114 | timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in 115 | guard let self, 116 | let start = startTime, 117 | clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW) - start >= (HeightExpansionTimeLimitInSeconds * 1_000_000_000) else { 118 | return 119 | } 120 | let timeoutError = RenderingError.expandingViewTimeout(CGSize(width: UIScreen.main.bounds.size.width, 121 | height: firstScrollView?.visibleContentHeight ?? -1)) 122 | NSLog("ExpandingViewController: Expanding Scroll View timed out. Current height is \(firstScrollView?.visibleContentHeight ?? -1)") 123 | runCallback(timeoutError) 124 | } 125 | } 126 | 127 | func stopAndResetTimer() { 128 | timer?.invalidate() 129 | timer = nil 130 | startTime = nil 131 | } 132 | 133 | } 134 | #endif 135 | -------------------------------------------------------------------------------- /Sources/SnapshotPreviewsCore/UIKitRenderingStrategy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIKitRenderingStrategy.swift 3 | // 4 | // 5 | // Created by Noah Martin on 7/5/24. 6 | // 7 | 8 | #if canImport(UIKit) && !os(watchOS) && !os(visionOS) && !os(tvOS) 9 | import Foundation 10 | import UIKit 11 | import SwiftUI 12 | 13 | public class UIKitRenderingStrategy: RenderingStrategy { 14 | 15 | public init(a11yWrapper: ((UIViewController, UIWindow, PreviewLayout) -> UIView)? = nil) { 16 | let windowScene = UIApplication.shared 17 | .connectedScenes 18 | .filter { $0.activationState == .foregroundActive } 19 | .first ?? UIApplication.shared.connectedScenes.first 20 | 21 | let window = windowScene != nil ? UIWindow(windowScene: windowScene as! UIWindowScene) : UIWindow() 22 | window.windowLevel = .statusBar + 1 23 | window.backgroundColor = UIColor.systemBackground 24 | window.makeKeyAndVisible() 25 | self.window = window 26 | self.a11yWrapper = a11yWrapper 27 | } 28 | 29 | private var windowScene: UIWindowScene? { 30 | window.windowScene 31 | } 32 | 33 | private let window: UIWindow 34 | private let a11yWrapper: ((UIViewController, UIWindow, PreviewLayout) -> UIView)? 35 | private var geometryUpdateError: Error? 36 | 37 | @MainActor 38 | public func render( 39 | preview: SnapshotPreviewsCore.Preview, 40 | completion: @escaping (SnapshotResult) -> Void 41 | ) { 42 | Self.setup() 43 | geometryUpdateError = nil 44 | let targetOrientation = preview.orientation.toInterfaceOrientation() 45 | guard #available(iOS 16.0, *), windowScene!.interfaceOrientation != targetOrientation else { 46 | performRender(preview: preview, completion: completion) 47 | return 48 | } 49 | 50 | windowScene!.requestGeometryUpdate(.iOS(interfaceOrientations: targetOrientation.toInterfaceOrientationMask())) { error in 51 | NSLog("Rotation error handler: \(error) \(self.windowScene!.interfaceOrientation)") 52 | DispatchQueue.main.async { 53 | self.geometryUpdateError = error 54 | } 55 | } 56 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in 57 | self?.waitForOrientationChange(targetOrientation: targetOrientation, preview: preview, attempts: 50, completion: completion) 58 | } 59 | } 60 | 61 | @MainActor private func waitForOrientationChange( 62 | targetOrientation: UIInterfaceOrientation, 63 | preview: SnapshotPreviewsCore.Preview, 64 | attempts: Int, 65 | completion: @escaping (SnapshotResult) -> Void 66 | ) { 67 | if let geometryUpdateError { 68 | if (geometryUpdateError as NSError).userInfo["BSErrorCodeDescription"] as? String == "timeout" { 69 | completion(SnapshotResult(image: .failure(RenderingError.orientationChangeTimeout), precision: nil, accessibilityEnabled: nil, colorScheme: nil, appStoreSnapshot: nil)) 70 | return 71 | } 72 | completion(SnapshotResult(image: .failure(geometryUpdateError), precision: nil, accessibilityEnabled: nil, colorScheme: nil, appStoreSnapshot: nil)) 73 | return 74 | } 75 | guard attempts > 0 else { 76 | let timeoutError = NSError(domain: "OrientationChangeTimeout", code: 0, userInfo: [NSLocalizedDescriptionKey: "Orientation change timed out"]) 77 | completion(SnapshotResult(image: .failure(timeoutError), precision: nil, accessibilityEnabled: nil, colorScheme: nil, appStoreSnapshot: nil)) 78 | return 79 | } 80 | 81 | if windowScene!.interfaceOrientation == targetOrientation { 82 | performRender(preview: preview, completion: completion) 83 | } else { 84 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in 85 | self?.waitForOrientationChange(targetOrientation: targetOrientation, preview: preview, attempts: attempts - 1, completion: completion) 86 | } 87 | } 88 | } 89 | 90 | @MainActor private func performRender( 91 | preview: SnapshotPreviewsCore.Preview, 92 | completion: @escaping (SnapshotResult) -> Void 93 | ) { 94 | UIView.setAnimationsEnabled(false) 95 | let view = preview.view() 96 | let controller = view.makeExpandingView(layout: preview.layout, window: window) 97 | view.snapshot( 98 | layout: preview.layout, 99 | controller: controller, 100 | window: window, 101 | async: false, 102 | a11yWrapper: a11yWrapper) { result in 103 | completion(result) 104 | } 105 | } 106 | } 107 | #endif 108 | --------------------------------------------------------------------------------