Capturing WKWebview is tricky with your view's layer, as WebKit lives in a standalone process. Luckily, there is a dedicated API that captures screenshot from WKWebview
takeSnapshotWithConfiguration:completionHandler:
In OC, you should use it like this:
- (void)captureWebViewSnapshot:(WKWebView *)webView {
if (!webView) { return; }
// Optional: guard against capturing while loading
if (webView.isLoading) {
// Show an alert/toast as needed and return
return;
}
WKSnapshotConfiguration *config = [[WKSnapshotConfiguration alloc] init];
config.rect = webView.bounds; // viewport coordinates
[webView takeSnapshotWithConfiguration:config
completionHandler:^(UIImage * _Nullable image, NSError * _Nullable error) {
if (error) {
// Handle error (e.g., show alert with error.localizedDescription)
return;
}
if (!image) {
// Handle empty image
return;
}
// TODO: SAVE IMAGE HERE.
}];
}
Here is the Full working code in Swift
import SwiftUI
import WebKit
import Photos
struct ContentView: View {
@State private var showingAlert = false
@State private var alertMessage = ""
var body: some View {
ZStack {
// WebView
WebView()
.ignoresSafeArea()
// Capture Button
VStack {
HStack {
Spacer()
Button(action: captureWebView) {
Image(systemName: "camera.fill")
.font(.title2)
.foregroundColor(.white)
.frame(width: 50, height: 50)
.background(Color.blue)
.clipShape(Circle())
.shadow(radius: 5)
}
.padding(.trailing, 20)
}
Spacer()
}
.padding(.top, 50)
}
.alert("Capture Result", isPresented: $showingAlert) {
Button("OK") { }
} message: {
Text(alertMessage)
}
}
private func captureWebView() {
guard let webView = webView else {
alertMessage = "WebView not ready"
showingAlert = true
return
}
// Wait for the page to load if it's still loading
if webView.isLoading {
alertMessage = "Please wait for the page to finish loading"
showingAlert = true
return
}
let configuration = WKSnapshotConfiguration()
configuration.rect = webView.bounds
webView.takeSnapshot(with: configuration) { image, error in
if let error = error {
alertMessage = "Failed to capture: \(error.localizedDescription)"
showingAlert = true
return
}
guard let image = image else {
alertMessage = "Failed to generate image"
showingAlert = true
return
}
saveImageToPhotoLibrary(image)
}
}
/// Saves photos to photo library
/// - Parameter image: image
/// - NOTE: You need to add `NSPhotoLibraryUsageDescription` and `NSPhotoLibraryAddUsageDescription` in info.plist
private func saveImageToPhotoLibrary(_ image: UIImage) {
PHPhotoLibrary.requestAuthorization { status in
DispatchQueue.main.async {
switch status {
case .authorized, .limited:
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAsset(from: image)
}) { success, error in
DispatchQueue.main.async {
if success {
alertMessage = "Image saved to photo library successfully!"
} else if let error = error {
alertMessage = "Failed to save image: \(error.localizedDescription)"
} else {
alertMessage = "Failed to save image"
}
showingAlert = true
}
}
case .denied, .restricted:
alertMessage = "Photo library access denied"
showingAlert = true
case .notDetermined:
alertMessage = "Photo library access not determined"
showingAlert = true
@unknown default:
alertMessage = "Unknown photo library status"
showingAlert = true
}
}
}
}
}
var webView: WKWebView?
struct WebView: UIViewRepresentable {
func makeUIView(context: Context) -> WKWebView {
let configuration = WKWebViewConfiguration()
configuration.allowsInlineMediaPlayback = true
webView = WKWebView(frame: .zero, configuration: configuration)
// Load a default webpage
if let url = URL(string: "https://www.apple.com") {
let request = URLRequest(url: url)
webView!.load(request)
}
return webView!
}
func updateUIView(_ uiView: WKWebView, context: Context) {
// Update UI if needed
}
}
#Preview {
ContentView()
}