Skip to content

Support UIViewController contentScrollView #723

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Sources/SnapshotTesting/Common/View.swift
Original file line number Diff line number Diff line change
Expand Up @@ -908,6 +908,7 @@ extension UIApplication {
func prepareView(
config: ViewImageConfig,
drawHierarchyInKeyWindow: Bool,
computeScrollSize: Bool,
traits: UITraitCollection,
view: UIView,
viewController: UIViewController
Expand All @@ -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) {
Comment on lines +938 to +939
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In some cases this is misleading, for example, computeScrollSize: true on the caller site, but without effect when the project's deployment target is less than iOS 15.

Having an alternative for backward compatibility that looks for the top-most or bottom-most(?) scrollView would be great.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes sense. Just to better understand, can you expand a little about what do you think should be the alternative for devices with target < iOS 15 ? Thanks!

Copy link

@chosa91 chosa91 Sep 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking about something that traverses through the subviews and returns the first (topmost) one which matches the predicate of view is UIScrollView.

https://stackoverflow.com/questions/32301336/swift-recursively-cycle-through-all-subviews-to-find-a-specific-class-and-appen
https://stackoverflow.com/questions/32151637/swift-get-all-subviews-of-a-specific-type-and-add-to-an-array/45297466

I'm not saying that these implementations are correct, so please use them with a grain of salt, but the idea is something like this.

Edit: The above is not going to be good, because we want to assess those at the current depth level, and only then go deeper.

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()
Expand All @@ -947,6 +957,7 @@ func prepareView(
func snapshotView(
config: ViewImageConfig,
drawHierarchyInKeyWindow: Bool,
computeScrollSize: Bool,
traits: UITraitCollection,
view: UIView,
viewController: UIViewController
Expand All @@ -956,6 +967,7 @@ func snapshotView(
let dispose = prepareView(
config: config,
drawHierarchyInKeyWindow: drawHierarchyInKeyWindow,
computeScrollSize: computeScrollSize,
traits: traits,
view: view,
viewController: viewController
Expand Down
2 changes: 2 additions & 0 deletions Sources/SnapshotTesting/Snapshotting/SwiftUIView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions Sources/SnapshotTesting/Snapshotting/UIView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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()
Expand All @@ -45,13 +47,15 @@ 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<UIView, String> {
return SimplySnapshotting.lines.pullback { view in
let dispose = prepareView(
config: .init(safeArea: .zero, size: size ?? view.frame.size, traits: traits),
drawHierarchyInKeyWindow: false,
computeScrollSize: computeScrollSize,
traits: .init(),
view: view,
viewController: .init()
Expand Down
7 changes: 7 additions & 0 deletions Sources/SnapshotTesting/Snapshotting/UIViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand All @@ -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 {
Expand All @@ -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
Expand All @@ -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
Expand All @@ -96,13 +101,15 @@ 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<UIViewController, String> {
return SimplySnapshotting.lines.pullback { viewController in
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
Expand Down
47 changes: 47 additions & 0 deletions Tests/SnapshotTestingTests/SnapshotTestingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.