Skip to content

Commit 26a5ed7

Browse files
committed
feat(ios): replace static delay with dynamic WebView rendering detection
Replace hardcoded 2.5s delay with state-based waiting that checks for document.readyState and image loading completion. Use drawHierarchy instead of layer.render for proper SVG rendering in headers/footers. Changes: - Add waitForWebViewReady() to poll DOM and image load state - Wait for all WebViews (content, header, footer) to fully load - Replace layer.render with drawHierarchy for SVG support - Add layoutIfNeeded() calls before PDF generation - Remove timing-based delays for production-ready rendering
1 parent 4809e72 commit 26a5ed7

2 files changed

Lines changed: 77 additions & 10 deletions

File tree

example/ios/Podfile.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ PODS:
88
- hermes-engine (0.14.0):
99
- hermes-engine/Pre-built (= 0.14.0)
1010
- hermes-engine/Pre-built (0.14.0)
11-
- NitroHtmlPdf (0.1.0):
11+
- NitroHtmlPdf (0.1.4):
1212
- boost
1313
- DoubleConversion
1414
- fast_float
@@ -2767,7 +2767,7 @@ SPEC CHECKSUMS:
27672767
fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd
27682768
glog: 5683914934d5b6e4240e497e0f4a3b42d1854183
27692769
hermes-engine: 3f68b3182a29c2ce52d7a0ae129cec793b544cff
2770-
NitroHtmlPdf: 29a8777a393d69a5fe6e620ca8cb21bb4762a5c5
2770+
NitroHtmlPdf: efd8b2ac432c0f20853ceab8e2d0c03ef717db7e
27712771
NitroModules: 1ef0796714251dbdaea49af187e291d8b6a7c5ea
27722772
RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669
27732773
RCTDeprecation: 2b70c6e3abe00396cefd8913efbf6a2db01a2b36

ios/NitroHtmlPdf.swift

Lines changed: 75 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -87,40 +87,81 @@ class NitroHtmlPdf: HybridNitroHtmlPdfSpec {
8787

8888
var headerWebView: WKWebView?
8989
var footerWebView: WKWebView?
90+
var contentWebView: WKWebView?
91+
var loadedCount = 0
92+
var totalToLoad = 1
9093

9194
if let header = options.header, headerHeight > 0 {
95+
totalToLoad += 1
9296
let hwv = WKWebView(frame: CGRect(x: 0, y: 0, width: pageSize.width, height: headerHeight))
9397
hwv.scrollView.contentInset = .zero
9498
hwv.scrollView.contentInsetAdjustmentBehavior = .never
95-
let wrappedHeader = "<!DOCTYPE html><html><head><meta name='viewport' content='width=device-width,initial-scale=1,maximum-scale=1'><style>*{margin:0!important;padding:0!important;box-sizing:border-box;}html,body{margin:0!important;padding:0!important;width:100%;height:100%;overflow:hidden;}</style></head><body>\(header)</body></html>"
99+
let wrappedHeader = "<!DOCTYPE html><html><head><meta name='viewport' content='width=device-width,initial-scale=1,maximum-scale=1'><style>*{margin:0!important;padding:0!important;box-sizing:border-box;}html,body{margin:0!important;padding:0!important;width:100%;height:100%;overflow:hidden;}svg{display:block;width:100%;height:100%;}</style></head><body>\(header)</body></html>"
100+
let delegate = WebViewDelegate {
101+
self.waitForWebViewReady(hwv) {
102+
loadedCount += 1
103+
if loadedCount == totalToLoad {
104+
self.renderPdf(webView: contentWebView!, headerWebView: headerWebView, footerWebView: footerWebView, options: options, continuation: continuation)
105+
containerView.removeFromSuperview()
106+
}
107+
}
108+
}
109+
self.activeDelegates.append(delegate)
110+
hwv.navigationDelegate = delegate
96111
hwv.loadHTMLString(wrappedHeader, baseURL: nil)
97112
containerView.addSubview(hwv)
98113
headerWebView = hwv
99114
}
100115

101116
if let footer = options.footer, footerHeight > 0 {
117+
totalToLoad += 1
102118
let fwv = WKWebView(frame: CGRect(x: 0, y: 0, width: pageSize.width, height: footerHeight))
103119
fwv.scrollView.contentInset = .zero
104120
fwv.scrollView.contentInsetAdjustmentBehavior = .never
105-
let wrappedFooter = "<!DOCTYPE html><html><head><meta name='viewport' content='width=device-width,initial-scale=1,maximum-scale=1'><style>*{margin:0!important;padding:0!important;box-sizing:border-box;}html,body{margin:0!important;padding:0!important;width:100%;height:100%;overflow:hidden;}</style></head><body>\(footer)</body></html>"
121+
let wrappedFooter = "<!DOCTYPE html><html><head><meta name='viewport' content='width=device-width,initial-scale=1,maximum-scale=1'><style>*{margin:0!important;padding:0!important;box-sizing:border-box;}html,body{margin:0!important;padding:0!important;width:100%;height:100%;overflow:hidden;}svg{display:block;width:100%;height:100%;}</style></head><body>\(footer)</body></html>"
122+
let delegate = WebViewDelegate {
123+
self.waitForWebViewReady(fwv) {
124+
loadedCount += 1
125+
if loadedCount == totalToLoad {
126+
self.renderPdf(webView: contentWebView!, headerWebView: headerWebView, footerWebView: footerWebView, options: options, continuation: continuation)
127+
containerView.removeFromSuperview()
128+
}
129+
}
130+
}
131+
self.activeDelegates.append(delegate)
132+
fwv.navigationDelegate = delegate
106133
fwv.loadHTMLString(wrappedFooter, baseURL: nil)
107134
containerView.addSubview(fwv)
108135
footerWebView = fwv
109136
}
110137

111138
let webView = WKWebView(frame: CGRect(x: 0, y: 0, width: pageSize.width, height: 842))
112-
webView.loadHTMLString(options.html, baseURL: nil)
113139
containerView.addSubview(webView)
140+
contentWebView = webView
114141

115-
DispatchQueue.main.asyncAfter(deadline: .now() + 2.5) {
116-
self.renderPdf(webView: webView, headerWebView: headerWebView, footerWebView: footerWebView, options: options, continuation: continuation)
117-
containerView.removeFromSuperview()
142+
let delegate = WebViewDelegate {
143+
self.waitForWebViewReady(contentWebView!) {
144+
loadedCount += 1
145+
if loadedCount == totalToLoad {
146+
self.renderPdf(webView: contentWebView!, headerWebView: headerWebView, footerWebView: footerWebView, options: options, continuation: continuation)
147+
containerView.removeFromSuperview()
148+
}
149+
}
118150
}
151+
self.activeDelegates.append(delegate)
152+
webView.navigationDelegate = delegate
153+
webView.loadHTMLString(options.html, baseURL: nil)
119154
}
120155
}
121156
}
122157

123158
private func renderPdf(webView: WKWebView, headerWebView: WKWebView?, footerWebView: WKWebView?, options: PdfOptions, continuation: CheckedContinuation<PdfResult, Error>) {
159+
self.activeDelegates.removeAll()
160+
161+
headerWebView?.layoutIfNeeded()
162+
footerWebView?.layoutIfNeeded()
163+
webView.layoutIfNeeded()
164+
124165
let pageSizeString = options.pageSize?.stringValue ?? "A4"
125166
let pageSize = getPageSize(pageSizeString, width: options.width, height: options.height)
126167

@@ -181,6 +222,32 @@ class NitroHtmlPdf: HybridNitroHtmlPdfSpec {
181222
}
182223
}
183224

225+
private func waitForWebViewReady(_ webView: WKWebView, completion: @escaping () -> Void) {
226+
let js = """
227+
(function() {
228+
if (document.readyState !== 'complete') {
229+
return false;
230+
}
231+
const images = Array.from(document.images);
232+
return images.every(img => img.complete);
233+
})();
234+
"""
235+
236+
func check() {
237+
webView.evaluateJavaScript(js) { result, _ in
238+
if let isReady = result as? Bool, isReady {
239+
completion()
240+
} else {
241+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
242+
check()
243+
}
244+
}
245+
}
246+
}
247+
248+
check()
249+
}
250+
184251
private func getPageSize(_ size: String, width: Double?, height: Double?) -> CGSize {
185252
if let w = width, let h = height {
186253
return CGSize(width: w, height: h)
@@ -235,7 +302,7 @@ class CustomPrintPageRenderer: UIPrintPageRenderer {
235302
if let headerWebView = headerWebView, customHeaderHeight > 0 {
236303
context.saveGState()
237304
context.translateBy(x: 0, y: 0)
238-
headerWebView.layer.render(in: context)
305+
headerWebView.drawHierarchy(in: headerWebView.bounds, afterScreenUpdates: true)
239306
context.restoreGState()
240307
}
241308

@@ -262,7 +329,7 @@ class CustomPrintPageRenderer: UIPrintPageRenderer {
262329
if let footerWebView = footerWebView, customFooterHeight > 0 {
263330
context.saveGState()
264331
context.translateBy(x: 0, y: pageSize.height - customFooterHeight)
265-
footerWebView.layer.render(in: context)
332+
footerWebView.drawHierarchy(in: footerWebView.bounds, afterScreenUpdates: true)
266333
context.restoreGState()
267334
}
268335
}

0 commit comments

Comments
 (0)