How to Render a WKWebView() to png-file (iOS16)?

I would like to render an html file (html string) to a png file (and save png to iCloud Drive).

I am using WKWebView() with WKNavigationDelegate to check whether web view didFinish loading.

WKWebView is working fine.

Once WKWebView loading didFinish I would like to render the view to an UIimage (and then save as png file) - and here I am currently struggling to generate UImage with

  • ImageRenderer(content: content)

Any Idea how to render the webView to png OR any another approach to render HTML to png?

Thanks a lot Eric

Replies

I think following extension could do the work:

extension UIView {

    func saveAsImage() -> UIImage {

        let renderer = UIGraphicsImageRenderer(bounds: bounds)

        let image = renderer.image { rendererContext in

            layer.render(in: rendererContext.cgContext)

        } 

        if let data = image.pngData() {

            let filename = *file path*

            try? data.write(to: filename)

        }

    }

}

you than just have to call the function(I never tried it…) don’t forget to replace file path with the file path where you want to save the image.

Thank you, NaimadDev, for the quick reply. Got 1 step forward.

I am using struct WebView : UIViewRepresentable { } to load my html.site with WKWebView(). Within the Webview I have a Coordinator to trigger once website is fully loaded.

struct WebView : UIViewRepresentable {
    ...
    @Binding var showLoading: Bool

    func makeCoordinator() -> Coordinator {
        Coordinator(didStart: {
            showLoading = true
        }, didFinish: {
            showLoading = false
            saveWebView(EmulatorView())
        })
    }
    ...
    

Once didFinish, I call the WebView extension function saveWebView with the EmulatorView, The EmulatorView contains my Website called with WebView(showLoading: $showLoading)

extension WebView {
    @MainActor func saveWebView(_ view: EmulatorView) {
        let renderer = ImageRenderer(content: view)
        renderer.scale = 3.0
        renderer.isOpaque = false
        if let image = renderer.uiImage {
            if let data = image.pngData() {
                if let filedirectory = FileManager.default.url(forUbiquityContainerIdentifier: nil)?.appendingPathComponent("Documents").appendingPathComponent("\(selectedPreviewTheme)") {
                    if (FileManager.default.fileExists(atPath: filedirectory.path, isDirectory: nil)) {
                        let filepath = filedirectory.appendingPathComponent("01_Screenshot_\(selectedPreviewTheme).png")
                        try? data.write(to: filepath)
                    }
                }
            }
        }
    }
}

The EmulatorView will be rendered, BUT unfortunately just a 216x216 png with my Emulator .background(Color("background2")). If I use another view from my app, e.g. the OnboardingView(), this one will be rendered fine.

Any ideas are highly appreciated

Eric

  • Maybe you can try to replace UIGraphicsImageRenderer(bounds: bounds) UIGraphicsImageRenderer(size: *CGSize*) I think this has an impacted on the image size… hopefully that works

  • Maybe you can try to replace UIGraphicsImageRenderer(bounds: bounds) with  UIGraphicsImageRenderer(size: *CGSize*) I think this has an impacted on the image size… hopefully that works

  • …./: you can’t edit comments… …or delete them… so the other views probably get rendered right, beacuse their boundaries are "right".Maybe the WebView just doesn’t really use the boundary(Or the boundary is different from the size of the hole page…).Maybe you can try to use the WebViews frame: UIGraphicsImageRenderer(bounds: self.frame) or UIGraphicsImageRenderer(bounds: self.frame).I’m not shure if that takes the size of the hole html etc. or only the size of the visible path.

    Naimad

So, I don’t think that comments work… idk why, I tried it 3rimes, but my comment is still missing… So what I said was: You could replace UIGraphicsImageRenderer(bounds: bounds) with UIGraphicsImageRenderer(bounds: *CGRect*) or UIGraphicsImageRenderer(frame: *CGSize*). I think in both cases "*CGSize*" should change the image Size.

I am still not giving up to use the ImageRenderer, but what was working for me, was the webview's async takeSnapshot:

e.g.

import WebKit
public class TakeScreenShotCommand {
	var screenShot: UIImage?
	func takeScreenShot(_ webview: WKWebView) {
		webview.takeSnapshot(with: nil, completionHandler: { image, error in
			if let image = image {
				self.screenShot = image
			}
		})
	}
}

From the documentation: https://developer.apple.com/documentation/swiftui/imagerenderer

"ImageRenderer output only includes views that SwiftUI renders, such as text, images, shapes, and composite views of these types. It does not render views provided by native platform frameworks (AppKit and UIKit) such as web views, media players, and some controls. For these views, ImageRenderer displays a placeholder image, similar to the behavior of drawingGroup(opaque:colorMode:)."