You say you “waited for a very long time” for the picture to be downloaded, however when you did do this accurately, you wouldn’t have this drawback within the first place.
Should you simply have a UIHostingController
and simply name drawHierarchy
, you aren’t ready in any respect. drawHierarchy
will trigger SwiftUI will solely run its lifecycle for a really brief time, simply sufficient that one thing is drawn.
To really anticipate the picture to be downloaded, you have to add the UIHostingController
to a UIWindow
, and solely then can the SwiftUI lifecycle run for an prolonged time frame. Should you do a Process.sleep
throughout this time, you may anticipate the picture to be downloaded.
Right here is a few code that does this. That is modified from ViewHosting.swift in ViewInspector. You in all probability can additional simplify this relying in your wants.
@MainActor
public enum ViewHosting { }
public extension ViewHosting {
struct ViewId: Hashable, Sendable {
let operate: String
var key: String { operate }
}
@MainActor
static func host<V, R>(_ view: V,
operate: String = #operate,
whileHosted: @MainActor (UIViewController) async throws -> R
) async rethrows -> R the place V: View {
let viewId = ViewId(operate: operate)
let vc = host(view: view, viewId: viewId)
let outcome = strive await whileHosted(vc)
expel(viewId: viewId)
return outcome
}
@MainActor
non-public static func host<V>(view: V, viewId: ViewId) -> UIViewController the place V: View {
let parentVC = rootViewController
let childVC = hostVC(view)
retailer(Hosted(viewController: childVC), viewId: viewId)
childVC.view.translatesAutoresizingMaskIntoConstraints = false
childVC.view.body = parentVC.view.body
willMove(childVC, to: parentVC)
parentVC.addChild(childVC)
parentVC.view.addSubview(childVC.view)
NSLayoutConstraint.activate([
childVC.view.leadingAnchor.constraint(equalTo: parentVC.view.leadingAnchor),
childVC.view.topAnchor.constraint(equalTo: parentVC.view.topAnchor),
])
didMove(childVC, to: parentVC)
window.layoutIfNeeded()
return childVC
}
static func expel(operate: String = #operate) {
let viewId = ViewId(operate: operate)
MainActor.assumeIsolated {
expel(viewId: viewId)
}
}
@MainActor
non-public static func expel(viewId: ViewId) {
guard let hosted = expelHosted(viewId: viewId) else { return }
let childVC = hosted.viewController
willMove(childVC, to: nil)
childVC.view.removeFromSuperview()
childVC.removeFromParent()
didMove(childVC, to: nil)
}
}
@MainActor
non-public extension ViewHosting {
struct Hosted {
let viewController: UIViewController
}
non-public static var hosted: [ViewId: Hosted] = [:]
static let window: UIWindow = makeWindow()
static func makeWindow() -> UIWindow {
let body = UIScreen.most important.bounds
let window = UIWindow(body: body)
installRootViewController(window)
window.makeKeyAndVisible()
window.layoutIfNeeded()
return window
}
@discardableResult
static func installRootViewController(_ window: UIWindow) -> UIViewController {
let vc = UIViewController()
window.rootViewController = vc
vc.view.translatesAutoresizingMaskIntoConstraints = false
return vc
}
static var rootViewController: UIViewController {
window.rootViewController ?? installRootViewController(window)
}
static func hostVC<V>(_ view: V) -> UIHostingController<V> the place V: View {
UIHostingController(rootView: view)
}
// MARK: - WillMove & DidMove
static func willMove(_ youngster: UIViewController, to father or mother: UIViewController?) {
youngster.willMove(toParent: father or mother)
}
static func didMove(_ youngster: UIViewController, to father or mother: UIViewController?) {
youngster.didMove(toParent: father or mother)
}
// MARK: - ViewController identification
static func retailer(_ hosted: Hosted, viewId: ViewId) {
self.hosted[viewId] = hosted
}
static func expelHosted(viewId: ViewId) -> Hosted? {
return hosted.removeValue(forKey: viewId)
}
}
non-public extension NSLayoutConstraint {
func precedence(_ worth: UILayoutPriority) -> NSLayoutConstraint {
precedence = worth
return self
}
}
Right here is an instance utilization:
struct ContentView: View {
@State non-public var img: UIImage?
var physique: some View {
Group {
if let img {
Picture(uiImage: img)
} else {
Textual content("Ready...")
}
}.job {
strive? await Process.sleep(for: .seconds(1))
print("Start snapshot")
img = await snapshot(of: WebImage(url: URL(string: "https://picsum.photographs/200/300"), content material: .self) {
ProgressView()
})
}
}
func snapshot(of view: some View) async -> UIImage {
await ViewHosting.host(view) { vc in
strive? await Process.sleep(for: .seconds(2)) // anticipate the picture to obtain
vc.view.sizeToFit() // resize the view to be an applicable measurement
let renderer = UIGraphicsImageRenderer(measurement: vc.view.bounds.measurement)
return renderer.picture { _ in
vc.view.drawHierarchy(in: vc.view.bounds, afterScreenUpdates: true)
}
}
}
}