swift-snapshot-testing icon indicating copy to clipboard operation
swift-snapshot-testing copied to clipboard

Content Size Category not being applied

Open zeusmedina opened this issue 7 years ago • 18 comments

Passing in a prefferedContentSizeCategory into traits doesn't change the image that's outputted. Is there any additional setup required to get this to work? Thanks

zeusmedina avatar Mar 08 '19 21:03 zeusmedina

Hi @zeusmedina! Thanks for the report! We have tests in the code base that exercise this:

https://github.com/pointfreeco/swift-snapshot-testing/blob/4d426f65da1eb4b8f8c7fe2b108ad380ceded10f/Tests/SnapshotTestingTests/SnapshotTestingTests.swift#L378-L397

And a couple example outputs:

  • Extra small: https://github.com/pointfreeco/swift-snapshot-testing/blob/4d426f65da1eb4b8f8c7fe2b108ad380ceded10f/Tests/SnapshotTestingTests/Snapshots/SnapshotTestingTests/testTraits.iphone-se-extra-small.png
  • Accessibility extra extra extra large: https://github.com/pointfreeco/swift-snapshot-testing/blob/4d426f65da1eb4b8f8c7fe2b108ad380ceded10f/Tests/SnapshotTestingTests/Snapshots/SnapshotTestingTests/testTraits.iphone-se-accessibility-extra-extra-extra-large.png

Can you attach some code that reproduces the issue or submit a PR with a failing test case?

stephencelis avatar Mar 08 '19 21:03 stephencelis

Thank you for the speedy response! Below is the code I'm using. The outputted image shows the labels without the content size category being applied. I'm testing a table view controller, if that changes anything. Maybe I should be testing the cell directly?

assertSnapshot(matching: vc, as: .image(on: iPhoneX, traits: .init(preferredContentSizeCategory: .accessibilityExtraExtraExtraLarge)))

zeusmedina avatar Mar 08 '19 22:03 zeusmedina

Does the view controller handle dynamic content size changes via traitCollectionDidChange?

stephencelis avatar Mar 08 '19 22:03 stephencelis

We currently leverage adjustsFontForContentSizeCategory. Our labels are automatically scaled when larger text is enabled.

zeusmedina avatar Mar 11 '19 16:03 zeusmedina

A bit more context... I'm testing a table view. My cells have two labels, both with adjustsFontForContentSizeCategory set to true (changing the text size in the emulator works as expected). I also tried overriding traitCollectionDidChange and calling setNeedsLayout and updateConstraintsIfNeeded.

zeusmedina avatar Mar 11 '19 17:03 zeusmedina

@zeusmedina Hm, I wonder if adjustsFontForContentSizeCategory is just going to be incompatible with trait collection overrides since it's going to depend on an uncontrollable global notification.

Did overriding traitCollectionDidChange work for you? As long as you re-style the fonts inside that method, you should be able to get fonts rendering differently per trait collection.

stephencelis avatar Mar 12 '19 19:03 stephencelis

@zeusmedina I just was able to use adjustsFontForContentSizeCategory and things rendered just fine. Here's a sample test:

  func testContentSizeCategory() {
    #if os(iOS)
    let label = UILabel()
    label.adjustsFontForContentSizeCategory = true
    label.font = .preferredFont(forTextStyle: .headline)
    label.frame.size = .init(width: 320, height: 160)
    label.text = "What's the point?"
    [
      "extra-small": UIContentSizeCategory.extraSmall,
      "small": .small,
      "medium": .medium,
      "large": .large,
      "extra-large": .extraLarge,
      "extra-extra-large": .extraExtraLarge,
      "extra-extra-extra-large": .extraExtraExtraLarge,
      "accessibility-medium": .accessibilityMedium,
      "accessibility-large": .accessibilityLarge,
      "accessibility-extra-large": .accessibilityExtraLarge,
      "accessibility-extra-extra-large": .accessibilityExtraExtraLarge,
      "accessibility-extra-extra-extra-large": .accessibilityExtraExtraExtraLarge,
      ].forEach { name, contentSize in
        assertSnapshot(
          matching: label,
          as: .image(traits: .init(preferredContentSizeCategory: contentSize)),
          named: "iphone-se-\(name)"
        )
    }
    #endif
  }

Can you attach some failing code to reproduce the issue you're having? Sounds like it should be working just fine.

stephencelis avatar Mar 12 '19 19:03 stephencelis

Hi @stephencelis, I'm having a similar problem. Your code works fine since you are setting the frame manually:

label.frame.size = .init(width: 320, height: 160)

but if you let the UILabel decide for itself using label.sizeToFit() it won't work because the images will have the same resolution instead of scaling accordingly

giulio92 avatar Mar 25 '19 11:03 giulio92

We use Autolayout to size our labels, maybe that's the problem?

zeusmedina avatar Mar 27 '19 13:03 zeusmedina

I'm not using Auto Layout but just the sizeToFit() func, if this can help

giulio92 avatar Mar 27 '19 14:03 giulio92

Any news on this @stephencelis?

giulio92 avatar May 23 '19 09:05 giulio92

@giulio92 Sorry, lost track of this! Were you ever able to resolve?

stephencelis avatar May 06 '20 21:05 stephencelis

👋 hi @stephencelis. I am hitting the same issue and noticed that the order of operations seem to break snapshots.

In my code I use custom fonts and rely on adjustsFontForContentSizeCategory property to adjust font sizes for Dynamic Type, e.g.

   lazy var label: UILabel = {
     let label = UILabel()
     label.translatesAutoresizingMaskIntoConstraints = false
     label.text = "Hello"

     // [1] instruct UILabel to adjust font size based on dynamic type
     label.adjustsFontForContentSizeCategory = true

     // [2] set font
     label.font = .preferredFont(forTextStyle: .body)
     // or
     let customFont = UIFont(name: "MyAwesomeFont-Regular", size: 20)!
     label.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: customFont)

     return label
   }()

It works perfectly fine in Simulator and on real devices, however with swift-snapshot-testing when testing with custom preferredContentSizeCategory, it only works if I set the font first and then set the adjustsFontForContentSizeCategory to true but not the other way around (label.font = .preferredFont(forTextStyle: .body); label.adjustsFontForContentSizeCategory = true breaks it). And the issue seem to be limited to only cells in UITableView.

Here is how I run the test:

        let vc = MyViewControllerWithTableViewInside()

        let size = CGSize(width: 480, height: 3000)

        let traits: [String: UITraitCollection] = [
            "small": .init(preferredContentSizeCategory: .small),
            "extraExtraLarge": .init(preferredContentSizeCategory: .extraExtraLarge),
            "accessibilityExtraExtraExtraLarge": .init(preferredContentSizeCategory: .accessibilityExtraExtraExtraLarge),
        ]

        traits.forEach { (traitName, trait) in
            assertSnapshot(matching: vc, as: .image(size: size, traits: trait),
                           named: "\(traitName).\(Locale.current.languageCode!)")
        }

I've added a test case to reproduce this issue in my fork of swift-snapshot-testing: https://github.com/shadone/swift-snapshot-testing/commit/0e354c51440306348977fef95d3099ca37d310b6

I am out of ideas, how would you suggest to investigate this issue further - is it a limitation of UIKit or is it a bug swift-snapshot-testing?

shadone avatar May 07 '20 09:05 shadone

I'm running into this issue as well. My best guess is that adjustsFontForContentSizeCategory relies on the UIContentSizeCategoryDidChangeNotification to work. This notification is only sent when UIApplication.shared.preferredContentSizeCategory is changed but not when a view controller has it's trait collection overridden as snapshot testing does using setOverrideTraitCollection.

I was able to workaround by wrapping my test like:

//Create UITraitCollection you want to use
traits.performAsCurrent {
    let view = MyView(model: MyModel())
    assertSnapshot(matching: view, as: .image()))
}

stephengilroy-grx avatar Sep 03 '20 19:09 stephengilroy-grx

In my case the issue is similar, if I calculate size of the label manually, using AttributedString.boundingRect and dynamic font the size is calculated as if the trait collection was default, but then it is rendered with the correct font size. performAsCurrent does not seem to help either. I imagine that the library could provide a workaround by allowing to inject a closure that would calculate the size of the view instead of accepting already calculated size of the view, and use it to update size before actually making snapshot but after view was placed in the view hierarchy and trait collection was set.

IlyaPuchkaTW avatar Dec 04 '20 18:12 IlyaPuchkaTW

Hi, is there any update on this issue? It would be really great to have this support.

schinj avatar Jun 23 '21 10:06 schinj

Hi, I was experiencing same issues and after a lot of debugging I made it work!

I solved this issue by firstly creating a profile that doesn’t overrides the UIContentSizeCategory. Keep in mind that all the default ViewImageConfig profiles there are already using UIContentSizeCategoryM and that might conflict with your own request.


// This is existing API

  public static func iPhone8(_ orientation: ViewImageConfig.Orientation)
    -> UITraitCollection {
      let base: [UITraitCollection] = [
//        .init(displayGamut: .P3),
//        .init(displayScale: 2),
        .init(forceTouchCapability: .available),
        .init(layoutDirection: .leftToRight),
        .init(preferredContentSizeCategory: .medium), //   <===== We don’t want this here
        .init(userInterfaceIdiom: .phone)
      ]
      switch orientation {
      case .landscape:
        return .init(
          traitsFrom: base + [
            .init(horizontalSizeClass: .compact),
            .init(verticalSizeClass: .compact)
          ]
        )
      case .portrait:
        return .init(
          traitsFrom: base + [
            .init(horizontalSizeClass: .compact),
            .init(verticalSizeClass: .regular)
          ]
        )
      }
  }

So I created a profile that does not include any traits (because I would use my own array of traits iterating for each content size category and each user interface style), like iPhoneXNeutral

.init(safeArea: safeArea, size: size, traits: .init()) // no traits

After I’ve got my profile created I’ll simply iterate on an array of UITraitCollections composed with my own requirements, in my case I used a combination of all UIContentSizeCategory and UIUserInterfaceStyle (for dark/light modes). And create the snapshots by making on each iteration a Snapshotting with these traits and the neutral profile.

let snapshotting = Snapshotting<UIViewController, UIImage>.image(
      on: .iPhoneXNeutral,
      traits: traitCollection // make sure this is holding my desired traits (size, light/dark)
)

As last check, you need to make sure that your views and components are changing their sizes accordingly to detect the new trait change by using traitCollectionDidChange… and I used Combine to also refresh upon initialization to the right content size category.

traitCollection.publisher(for: \.preferredContentSizeCategory).sink…

Hope this also help you!

migueliOS avatar Aug 19 '21 22:08 migueliOS

Hi, I've tried every solution offered, but none of them worked without changing the order font > adjustsFontForContentSizeCategory. Is there any update on this issue?

Update: So I guess the order matters :) ( Also if you are using attributedText, it should be set before adjustsFontForContentSizeCategory as well ) Screenshot 2023-03-24 at 09 32 36

ogulcankeskin93 avatar Mar 24 '23 08:03 ogulcankeskin93