Nimble-Snapshots icon indicating copy to clipboard operation
Nimble-Snapshots copied to clipboard

Support for SwiftUI?

Open contreras2004 opened this issue 2 years ago • 14 comments

I have attempted to use Nimble_snapshots with swiftUI Views using a UIHostingController but it just gives me a white snapshot of the window. Ant ideas on how can I make this work or why it does not work?

contreras2004 avatar Aug 05 '22 19:08 contreras2004

Sorry, I haven’t worked much with SwiftUI and I’m not sure. We use the underlying snapshot test case library, maybe check their GitHub repo for similar issues?

ashfurrow avatar Aug 06 '22 17:08 ashfurrow

I have attempted to use Nimble_snapshots with swiftUI Views using a UIHostingController but it just gives me a white snapshot of the window. Ant ideas on how can I make this work or why it does not work?

Hi! Got it to work with this setup:

import SwiftUI

@available(iOS 14, *)
extension View {
    func view(sizeThatFitsWidth width: CGFloat? = nil, colorScheme: ColorScheme? = nil) -> UIView {
        let vc = UIHostingController(rootView: self.environment(\.colorScheme, colorScheme ?? .light))
        vc._disableSafeArea = true
        vc.view.backgroundColor = .clear
        
        let calculatedSize = width.map { vc.view.sizeThatFits(CGSize(width: $0, height: CGFloat.greatestFiniteMagnitude)) } ?? UIScreen.main.bounds.size
        
        let window = UIWindow(frame: CGRect(origin: .zero, size: calculatedSize))
        window.rootViewController = vc
        window.makeKeyAndVisible()

        return vc.view
    }
}

And then:

it("should render as expected for colorscheme light") {
                    expect(sut.view(sizeThatFitsWidth: 400)).to(haveValidSnapshot())
                }
                
                it("should render as expected for colorscheme dark") {
                    expect(sut.view(sizeThatFitsWidth: 400, colorScheme: .dark)).to(haveValidSnapshot())
                }

robinbonin avatar Aug 09 '22 12:08 robinbonin

It works! Thanks!

contreras2004 avatar Aug 10 '22 15:08 contreras2004

@robinbonin Thanks for letting us know!

ashfurrow avatar Aug 14 '22 20:08 ashfurrow

Hello!

I'm experiencing the same issue, and the steps above didn't seem to resolve it. This only seems to happen when I'm testing against iOS 16, not 15.5. When I'm trying to record a new snapshot it's just blank white. Here's the code I'm using:

expect(subject.view(sizeThatFitsWidth: 390)).to(recordDynamicSizeSnapshot( named: named, identifier: nil, sizes: size, isDeviceAgnostic: true, usesDrawRect: true, resizeMode: ResizeMode.frame ))

Any help would be greatly appreciated! I did try looking at the snapshot test case library GitHub page, and didn't find anything unfortunately.

If there's any additional info that would be helpful, please let me know!

Thank you very much!

Ripcord715 avatar Oct 19 '22 22:10 Ripcord715

Hey @Ripcord715

Based on @robinbonin's answer I created this extension. At least it is working for me using Xcode 14.0.1 on iOS 16.

public extension View {
    func view(width: CGFloat? = nil, height: CGFloat? = nil, colorScheme: ColorScheme? = nil) -> UIView {
        let viewController = UIHostingController(rootView: self.environment(\.colorScheme, colorScheme ?? .light))
        viewController._disableSafeArea = true

        let calculatedSize = width.map {
            viewController.view.sizeThatFits(
                CGSize(width: $0, height: height ?? CGFloat.greatestFiniteMagnitude))
        } ?? UIScreen.main.bounds.size

        let window = UIWindow(frame: CGRect(origin: .zero, size: calculatedSize))
        window.rootViewController = viewController
        window.makeKeyAndVisible()
        window.backgroundColor = colorScheme == .light ? .white : .black
        return viewController.view
    }
}

And you can use it with any SwiftUI view like this:

let yourView = YourSwiftUIView()
expect(yourView.view()) == recordSnapshot()

contreras2004 avatar Oct 20 '22 12:10 contreras2004

Thank you very much for the reply @contreras2004 ! I gave it a shot, and unfortunately I'm still getting a white snapshot.

Ripcord715 avatar Oct 20 '22 15:10 Ripcord715

hmmm, could you post a bit pf your view's code? Maybe I could try rendering it myself

contreras2004 avatar Oct 20 '22 16:10 contreras2004

@contreras2004 - Sorry I wasn't able to get back to you sooner. I asked our iOS developer to take a look at what was going on, and it sounds like our view is loading just after the snapshot is being taken when we use iOS 16. We were able to get a workaround that works for now by preloading the data, but we would rather figure out if it was possible to delay the snapshot being taken until after the view has finished loading naturally. Is there a way to do that in Nimble Snapshots?

Thanks again for your help!

Ripcord715 avatar Oct 31 '22 18:10 Ripcord715

@Ripcord715 Maybe you could try using the .toEventually(recordSnapshot()) and .toEventually(haveValidSnapshot()) matchers. Also when dealing with service calls you must mock the services, you shouldn't use the same provider as in a real app. Use a mock json with the required response from the server that returns the data you need to test your screen. Just to be clear, this has nothing to do with Nimble-Snapshots package.

Hope it helps 🙂

contreras2004 avatar Nov 02 '22 15:11 contreras2004

@contreras2004 - Thank you very much, I'll give that a go!

Ripcord715 avatar Nov 02 '22 17:11 Ripcord715

@contreras2004 - Sorry I wasn't able to get back to you sooner. I asked our iOS developer to take a look at what was going on, and it sounds like our view is loading just after the snapshot is being taken when we use iOS 16. We were able to get a workaround that works for now by preloading the data, but we would rather figure out if it was possible to delay the snapshot being taken until after the view has finished loading naturally. Is there a way to do that in Nimble Snapshots?

Thanks again for your help!

@Ripcord715 were you able to resolve this? Now i'm having the same issue. I notice that this is because the function that loads the data is in the .onAppear { } of the swiftUI view.

contreras2004 avatar Nov 10 '22 20:11 contreras2004

@contreras2004 - Apologies for not getting back sooner, I wanted my senior dev to take a look at my work before replying. Yes, the View extension along with the .toEventually did work. expect(subject.view(width: 390, height: 925)) .toEventually( recordDynamicSizeSnapshot(), timeout: .seconds(waitTime), pollInterval: .milliseconds(waitTime) )

expect(testSubject).toEventually(haveValidDynamicSizeSnapshot(), timeout: .seconds(waitTime), pollInterval: .milliseconds(waitTime))

Ripcord715 avatar Nov 28 '22 22:11 Ripcord715