1

I'd like to be able to take an Screenshot of a SwiftUI View in a XCTest.

I've tried things like the hackingwithswifts extension: https://www.hackingwithswift.com/quick-start/swiftui/how-to-convert-a-swiftui-view-to-an-image

However, my use case it slightly different, I need it to run in a XCTest. I've also seen pointfreeco snapshot testing, however, I want to understand why what I've written just produces either a black or empty image.

I've tried using a displayLink to screenshot during a loop, but the image is still empty. I feel I'm missing something fundamental.

Can anyone offer any help? Thank you

import SwiftUI
import XCTest

final class MyTests: XCTestCase {

  func test_screenshot_view() throws {
    
    let swiftUIView = Button {
      
    } label: {
      Text("Hello, World!")
    }
    .frame(width: 140, height: 56)

    let controller = UIHostingController(rootView: swiftUIView)

    let window = UIWindow()
    window.rootViewController = controller
    
    let view = controller.view
    let targetSize = controller.view.intrinsicContentSize
    view!.bounds = CGRect(origin: .zero, size: targetSize)
    view!.backgroundColor = UIColor.yellow
    
    UIGraphicsBeginImageContextWithOptions(view!.bounds.size, view!.isOpaque, 0)
    view!.drawHierarchy(in: view!.bounds, afterScreenUpdates: true)
    let snapshotImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()

    //snapshotImage is either black or empty

  }
}
Chris
  • 2,739
  • 4
  • 29
  • 57
  • Does this answer your question https://stackoverflow.com/a/59333377/12299030? – Asperi Jan 21 '22 at 05:43
  • On first attempt, no, UIApplication.shared is nil. I may have to dig into pointfreeco and see how they do it – Chris Jan 21 '22 at 20:29

1 Answers1

0

Just an update, this seems to work, but has some sizing issues:

import SwiftUI
import CoreGraphics

extension View {
  
  func asImage() -> UIImage {
    
    let controller = UIHostingController(rootView: self)
    let view = controller.view
    let targetSize = controller.view.intrinsicContentSize
    let bounds = CGRect(origin: .zero, size: targetSize)
    
    let window = UIWindow(frame: bounds)
   
    window.rootViewController = controller
    window.makeKeyAndVisible()
    
    view?.bounds = bounds
    view?.backgroundColor = .clear
    
    let image = controller.view.asImage()
    
    return image
  }
}

extension UIView {
  func asImage() -> UIImage {
    let renderer = UIGraphicsImageRenderer(bounds: bounds)
    return renderer.image { rendererContext in
      layer.render(in: rendererContext.cgContext)
    }
  }
}

Chris
  • 2,739
  • 4
  • 29
  • 57