diff --git a/Sources/SnapshotTesting/Common/View.swift b/Sources/SnapshotTesting/Common/View.swift index fb6261c94..8ef75b27d 100644 --- a/Sources/SnapshotTesting/Common/View.swift +++ b/Sources/SnapshotTesting/Common/View.swift @@ -908,6 +908,7 @@ extension UIApplication { func prepareView( config: ViewImageConfig, drawHierarchyInKeyWindow: Bool, + computeScrollSize: Bool, traits: UITraitCollection, view: UIView, viewController: UIViewController @@ -934,6 +935,15 @@ func prepareView( } let dispose = add(traits: traits, viewController: viewController, to: window) + if #available(iOS 15.0, *) { + if computeScrollSize, let scrollView = viewController.contentScrollView(for: .top) { + var size = scrollView.contentSize + size.width += scrollView.contentInset.left + scrollView.contentInset.right + size.height += scrollView.contentInset.top + scrollView.contentInset.bottom + viewController.view.frame.size = size + } + } + if size.width == 0 || size.height == 0 { // Try to call sizeToFit() if the view still has invalid size view.sizeToFit() @@ -947,6 +957,7 @@ func prepareView( func snapshotView( config: ViewImageConfig, drawHierarchyInKeyWindow: Bool, + computeScrollSize: Bool, traits: UITraitCollection, view: UIView, viewController: UIViewController @@ -956,6 +967,7 @@ func snapshotView( let dispose = prepareView( config: config, drawHierarchyInKeyWindow: drawHierarchyInKeyWindow, + computeScrollSize: computeScrollSize, traits: traits, view: view, viewController: viewController diff --git a/Sources/SnapshotTesting/Snapshotting/SwiftUIView.swift b/Sources/SnapshotTesting/Snapshotting/SwiftUIView.swift index 6dfb40adf..892fb4073 100644 --- a/Sources/SnapshotTesting/Snapshotting/SwiftUIView.swift +++ b/Sources/SnapshotTesting/Snapshotting/SwiftUIView.swift @@ -33,6 +33,7 @@ extension Snapshotting where Value: SwiftUI.View, Format == UIImage { /// - traits: A trait collection override. public static func image( drawHierarchyInKeyWindow: Bool = false, + computeScrollSize: Bool = false, precision: Float = 1, perceptualPrecision: Float = 1, layout: SwiftUISnapshotLayout = .sizeThatFits, @@ -74,6 +75,7 @@ extension Snapshotting where Value: SwiftUI.View, Format == UIImage { return snapshotView( config: config, drawHierarchyInKeyWindow: drawHierarchyInKeyWindow, + computeScrollSize: computeScrollSize, traits: traits, view: controller.view, viewController: controller diff --git a/Sources/SnapshotTesting/Snapshotting/UIView.swift b/Sources/SnapshotTesting/Snapshotting/UIView.swift index d2ed6e0f5..3c2aa17c1 100644 --- a/Sources/SnapshotTesting/Snapshotting/UIView.swift +++ b/Sources/SnapshotTesting/Snapshotting/UIView.swift @@ -17,6 +17,7 @@ extension Snapshotting where Value == UIView, Format == UIImage { /// - traits: A trait collection override. public static func image( drawHierarchyInKeyWindow: Bool = false, + computeScrollSize: Bool = false, precision: Float = 1, perceptualPrecision: Float = 1, size: CGSize? = nil, @@ -28,6 +29,7 @@ extension Snapshotting where Value == UIView, Format == UIImage { snapshotView( config: .init(safeArea: .zero, size: size ?? view.frame.size, traits: .init()), drawHierarchyInKeyWindow: drawHierarchyInKeyWindow, + computeScrollSize: computeScrollSize, traits: traits, view: view, viewController: .init() @@ -45,6 +47,7 @@ extension Snapshotting where Value == UIView, Format == String { /// A snapshot strategy for comparing views based on a recursive description of their properties and hierarchies. public static func recursiveDescription( size: CGSize? = nil, + computeScrollSize: Bool = false, traits: UITraitCollection = .init() ) -> Snapshotting { @@ -52,6 +55,7 @@ extension Snapshotting where Value == UIView, Format == String { let dispose = prepareView( config: .init(safeArea: .zero, size: size ?? view.frame.size, traits: traits), drawHierarchyInKeyWindow: false, + computeScrollSize: computeScrollSize, traits: .init(), view: view, viewController: .init() diff --git a/Sources/SnapshotTesting/Snapshotting/UIViewController.swift b/Sources/SnapshotTesting/Snapshotting/UIViewController.swift index 833ec5e0a..a1211a9a9 100644 --- a/Sources/SnapshotTesting/Snapshotting/UIViewController.swift +++ b/Sources/SnapshotTesting/Snapshotting/UIViewController.swift @@ -20,6 +20,7 @@ extension Snapshotting where Value == UIViewController, Format == UIImage { precision: Float = 1, perceptualPrecision: Float = 1, size: CGSize? = nil, + computeScrollSize: Bool = false, traits: UITraitCollection = .init() ) -> Snapshotting { @@ -28,6 +29,7 @@ extension Snapshotting where Value == UIViewController, Format == UIImage { snapshotView( config: size.map { .init(safeArea: config.safeArea, size: $0, traits: config.traits) } ?? config, drawHierarchyInKeyWindow: false, + computeScrollSize: computeScrollSize, traits: traits, view: viewController.view, viewController: viewController @@ -48,6 +50,7 @@ extension Snapshotting where Value == UIViewController, Format == UIImage { precision: Float = 1, perceptualPrecision: Float = 1, size: CGSize? = nil, + computeScrollSize: Bool = false, traits: UITraitCollection = .init() ) -> Snapshotting { @@ -56,6 +59,7 @@ extension Snapshotting where Value == UIViewController, Format == UIImage { snapshotView( config: .init(safeArea: .zero, size: size, traits: traits), drawHierarchyInKeyWindow: drawHierarchyInKeyWindow, + computeScrollSize: computeScrollSize, traits: traits, view: viewController.view, viewController: viewController @@ -71,6 +75,7 @@ extension Snapshotting where Value == UIViewController, Format == String { let dispose = prepareView( config: .init(), drawHierarchyInKeyWindow: false, + computeScrollSize: false, traits: .init(), view: viewController.view, viewController: viewController @@ -96,6 +101,7 @@ extension Snapshotting where Value == UIViewController, Format == String { public static func recursiveDescription( on config: ViewImageConfig = .init(), size: CGSize? = nil, + computeScrollSize: Bool = false, traits: UITraitCollection = .init() ) -> Snapshotting { @@ -103,6 +109,7 @@ extension Snapshotting where Value == UIViewController, Format == String { let dispose = prepareView( config: .init(safeArea: config.safeArea, size: size ?? config.size, traits: config.traits), drawHierarchyInKeyWindow: false, + computeScrollSize: computeScrollSize, traits: traits, view: viewController.view, viewController: viewController diff --git a/Tests/SnapshotTestingTests/SnapshotTestingTests.swift b/Tests/SnapshotTestingTests/SnapshotTestingTests.swift index 570f24240..3262efc3f 100644 --- a/Tests/SnapshotTestingTests/SnapshotTestingTests.swift +++ b/Tests/SnapshotTestingTests/SnapshotTestingTests.swift @@ -953,6 +953,53 @@ final class SnapshotTestingTests: XCTestCase { #endif } + func testContentScrollSize() { + #if os(iOS) + let viewController = UIViewController() + viewController.view.backgroundColor = .green + + let scrollView = UIScrollView() + scrollView.translatesAutoresizingMaskIntoConstraints = false + scrollView.backgroundColor = .red + viewController.view.addSubview(scrollView) + if #available(iOS 15.0, *) { + viewController.setContentScrollView(scrollView) + } + + let labels = (0...100).map { + let label = UILabel() + label.text = "Label #\($0)" + label.backgroundColor = .blue + return label + } + + let stackView = UIStackView(arrangedSubviews: labels) + stackView.backgroundColor = .green + stackView.axis = .vertical + stackView.alignment = .center + stackView.spacing = 16 + stackView.layoutMargins = .init(top: 16, left: 0, bottom: 16, right: 0) + stackView.isLayoutMarginsRelativeArrangement = true + stackView.translatesAutoresizingMaskIntoConstraints = false + scrollView.addSubview(stackView) + + NSLayoutConstraint.activate([ + scrollView.leadingAnchor.constraint(equalTo: viewController.view.leadingAnchor), + scrollView.topAnchor.constraint(equalTo: viewController.view.topAnchor), + scrollView.trailingAnchor.constraint(equalTo: viewController.view.trailingAnchor), + scrollView.bottomAnchor.constraint(equalTo: viewController.view.bottomAnchor), + + stackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: 8), + stackView.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 8), + stackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: -8), + stackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: -8), + stackView.widthAnchor.constraint(equalTo: scrollView.widthAnchor, constant: -16) + ]) + + assertSnapshot(matching: viewController, as: .image(computeScrollSize: true)) + #endif + } + func testViewControllerHierarchy() { #if os(iOS) let page = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal) diff --git a/Tests/SnapshotTestingTests/__Snapshots__/SnapshotTestingTests/testContentScrollSize.1.png b/Tests/SnapshotTestingTests/__Snapshots__/SnapshotTestingTests/testContentScrollSize.1.png new file mode 100644 index 000000000..81cee64f9 Binary files /dev/null and b/Tests/SnapshotTestingTests/__Snapshots__/SnapshotTestingTests/testContentScrollSize.1.png differ