You can do this... but not in viewDidLoad() -- you have to wait at least until viewDidLayoutSubviews().
And, the view must be added to the view hierarchy -- but it can be removed as soon as we generate the image so it's never seen "on-screen."
Note: all "result" images here use:
- a 240 x 200image view
- .contentMode = .center
- green background so we can see the frame
and we give the UIImage generate from the SwiftUI ContentView a yellow background, because we will need to address some layout quirks.
So, to generate the image and set it to a UIImageView, we can do this:
// we will generate the image in viewDidLayoutSubview()
//  but that can be (and usually is) called more than once
//  so we'll use this to make sure we only generate the image once
var firstTime: Bool = true
override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    
    // we only want this to run once
    if firstTime {
        firstTime = false
        if let img = imageFromContentView() {
            imgView.image = img
        }
    }
}
using this imageFromContentView() func:
func imageFromContentView() -> UIImage? {
    
    let swiftUIView = UIHostingController(rootView: ContentView())
    
    // add as chlld controller
    addChild(swiftUIView)
    
    // make sure we can get its view (safely unwrap its view)
    guard let v = swiftUIView.view else {
        swiftUIView.willMove(toParent: nil)
        swiftUIView.removeFromParent()
        return nil
    }
    
    view.addSubview(v)
    swiftUIView.didMove(toParent: self)
    
    // size the view to its content
    v.sizeToFit()
    
    // force it to layout its subviews
    v.setNeedsLayout()
    v.layoutIfNeeded()
    
    // if we want to see the background
    v.backgroundColor = .systemYellow
    
    // get it as a UIImage
    let img = v.asImage()
    
    // we're done with it, so get rid of it
    v.removeFromSuperview()
    swiftUIView.willMove(toParent: nil)
    swiftUIView.removeFromParent()
    
    return img
    
}
Result #1:

Notice the 20-pt yellow band at the top, and the content is not vertically centered... that's because the UIHostingController applies a safe area layout guide.
Couple options to get around that...
If we add this line:
    view.addSubview(v)
    swiftUIView.didMove(toParent: self)
    // add same bottom safe area inset as top
    swiftUIView.additionalSafeAreaInsets = UIEdgeInsets(top: 0, left: 0, bottom: v.safeAreaInsets.top, right: 0)
    
    // size the view to its content
    v.sizeToFit()
we get this result:

the rendered image now has 20-pts Top and Bottom "safe area" insets.
If we don't want any safe area insets, we can use this extension:
// extension to remove safe area from UIHostingController
//  source: https://stackoverflow.com/a/70339424/6257435
extension UIHostingController {
    convenience public init(rootView: Content, ignoreSafeArea: Bool) {
        self.init(rootView: rootView)
        
        if ignoreSafeArea {
            disableSafeArea()
        }
    }
    
    func disableSafeArea() {
        guard let viewClass = object_getClass(view) else { return }
        
        let viewSubclassName = String(cString: class_getName(viewClass)).appending("_IgnoreSafeArea")
        if let viewSubclass = NSClassFromString(viewSubclassName) {
            object_setClass(view, viewSubclass)
        }
        else {
            guard let viewClassNameUtf8 = (viewSubclassName as NSString).utf8String else { return }
            guard let viewSubclass = objc_allocateClassPair(viewClass, viewClassNameUtf8, 0) else { return }
            
            if let method = class_getInstanceMethod(UIView.self, #selector(getter: UIView.safeAreaInsets)) {
                let safeAreaInsets: @convention(block) (AnyObject) -> UIEdgeInsets = { _ in
                    return .zero
                }
                class_addMethod(viewSubclass, #selector(getter: UIView.safeAreaInsets), imp_implementationWithBlock(safeAreaInsets), method_getTypeEncoding(method))
            }
            
            objc_registerClassPair(viewSubclass)
            object_setClass(view, viewSubclass)
        }
    }
}
and change the first line in our func to:
let swiftUIView = UIHostingController(rootView: ContentView(), ignoreSafeArea: true)
and we get this result:

Because the SwiftUI ContentView layout is using a zStack where its content (the "ring") exceeds its vertical bounds, the top and bottom of the ring is "clipped."
We can fix that either by changing the framing in ContentView:

or by increasing the frame height of the loaded view, like this for example:
    // size the view to its content
    v.sizeToFit()
    
    // for this explicit example, the "ring" extends vertically
    //  outside the bounds of the zStack
    //  so we'll add 10-pts height
    v.frame.size.height += 10.0
    

Here's a complete implementation (using your unmodified ContentView):
class ViewController: UIViewController {
    let imgView = UIImageView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        imgView.contentMode = .center
        
        imgView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(imgView)
        let g = view.safeAreaLayoutGuide
        
        NSLayoutConstraint.activate([
            // let's put the imageView 40-pts from Top
            imgView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
            // centered horizontally
            imgView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            // width: 240
            imgView.widthAnchor.constraint(equalToConstant: 240.0),
            // height: 200
            imgView.heightAnchor.constraint(equalToConstant: 200.0),
        ])
        // show the image view background so we
        //  can see its frame
        imgView.backgroundColor = .systemGreen
    }
    // we will generate the image in viewDidLayoutSubview()
    //  but that can be (and usually is) called more than once
    //  so we'll use this to make sure we only generate the image once
    var firstTime: Bool = true
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        
        // we only want this to run once
        if firstTime {
            firstTime = false
            if let img = imageFromContentView() {
                imgView.image = img
            }
        }
    
    }
    func imageFromContentView() -> UIImage? {
        
        let swiftUIView = UIHostingController(rootView: ContentView(), ignoreSafeArea: true)
        
        // add as chlld controller
        addChild(swiftUIView)
        
        // make sure we can get its view (safely unwrap its view)
        guard let v = swiftUIView.view else {
            swiftUIView.willMove(toParent: nil)
            swiftUIView.removeFromParent()
            return nil
        }
        
        view.addSubview(v)
        swiftUIView.didMove(toParent: self)
        
        // size the view to its content
        v.sizeToFit()
        
        // for this explicit example, the "ring" extends vertically
        //  outside the bounds of the zStack
        //  so we'll add 10-pts height
        v.frame.size.height += 10.0
        
        // force it to layout its subviews
        v.setNeedsLayout()
        v.layoutIfNeeded()
        
        // if we want to see the background
        v.backgroundColor = .systemYellow
        
        // get it as a UIImage
        let img = v.asImage()
        
        // we're done with it, so get rid of it
        v.removeFromSuperview()
        swiftUIView.willMove(toParent: nil)
        swiftUIView.removeFromParent()
        
        return img
        
    }
}
// extension to remove safe area from UIHostingController
//  source: https://stackoverflow.com/a/70339424/6257435
extension UIHostingController {
    convenience public init(rootView: Content, ignoreSafeArea: Bool) {
        self.init(rootView: rootView)
        
        if ignoreSafeArea {
            disableSafeArea()
        }
    }
    
    func disableSafeArea() {
        guard let viewClass = object_getClass(view) else { return }
        
        let viewSubclassName = String(cString: class_getName(viewClass)).appending("_IgnoreSafeArea")
        if let viewSubclass = NSClassFromString(viewSubclassName) {
            object_setClass(view, viewSubclass)
        }
        else {
            guard let viewClassNameUtf8 = (viewSubclassName as NSString).utf8String else { return }
            guard let viewSubclass = objc_allocateClassPair(viewClass, viewClassNameUtf8, 0) else { return }
            
            if let method = class_getInstanceMethod(UIView.self, #selector(getter: UIView.safeAreaInsets)) {
                let safeAreaInsets: @convention(block) (AnyObject) -> UIEdgeInsets = { _ in
                    return .zero
                }
                class_addMethod(viewSubclass, #selector(getter: UIView.safeAreaInsets), imp_implementationWithBlock(safeAreaInsets), method_getTypeEncoding(method))
            }
            
            objc_registerClassPair(viewSubclass)
            object_setClass(view, viewSubclass)
        }
    }
}
extension UIView {
    func asImage() -> UIImage {
        let renderer = UIGraphicsImageRenderer(size: frame.size)
        return renderer.image { context in
            layer.render(in: context.cgContext)
        }
    }
}