├── 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 |
--------------------------------------------------------------------------------