UIMarkupTextPrintFormatter never renders base64 images - html

Im creating a pdf file out of html content in swift 3.0:
/**
*
*/
func exportHtmlContentToPDF(HTMLContent: String, filePath: String) {
// let webView = UIWebView(frame: CGRect(x: 0, y: 0, width: 694, height: 603));
// webView.loadHTMLString(HTMLContent, baseURL: nil);
let pdfPrinter = PDFPrinter();
let printFormatter = UIMarkupTextPrintFormatter(markupText: HTMLContent);
// let printFormatter = webView.viewPrintFormatter();
pdfPrinter.addPrintFormatter(printFormatter, startingAtPageAt: 0);
let pdfData = self.drawPDFUsingPrintPageRenderer(printPageRenderer: pdfPrinter);
pdfData?.write(toFile: filePath, atomically: true);
}
/**
*
*/
func drawPDFUsingPrintPageRenderer(printPageRenderer: UIPrintPageRenderer) -> NSData! {
let data = NSMutableData();
UIGraphicsBeginPDFContextToData(data, CGRect.zero, nil);
printPageRenderer.prepare(forDrawingPages: NSMakeRange(0, printPageRenderer.numberOfPages));
let bounds = UIGraphicsGetPDFContextBounds();
for i in 0...(printPageRenderer.numberOfPages - 1) {
UIGraphicsBeginPDFPage();
printPageRenderer.drawPage(at: i, in: bounds);
}
UIGraphicsEndPDFContext();
return data;
}
Everything is rendered fine except my base64 encoded images. The HTML content itself in a webview or inside safari or chrome browser is presented correctly and is showing all images correctly. But the images are never rendered into the pdf.
Why are the images not rendered and how can I get them to be rendered?

This happens because WebKit first parses the HTML into a DOM, and renders content on multiple event loop cycles. You therefore need to wait for not just the page DOM to be ready but for the resource loading to be complete. As you also suggest, you need to refactor your code such that the webview gets loaded first, and you only then export its contents.
To determine the correct time to fire the export, you can observe for the state of the DOM document in the web view. There are multiple ways to do this, but the most readable option I find is a port of an answer to a related Objective-C question: in your UIWebViewDelegate implementation, implement webViewDidFinishLoad in the following way to monitor document.readyState:
func webViewDidFinishLoad(_ webView: UIWebView) {
guard let readyState = webView.stringByEvaluatingJavaScript(from: "document.readyState"),
readyState == "complete" else
{
// document not yet parsed, or resources not yet loaded.
return
}
// This is the last webViewDidFinishLoad call --> export.
//
// There is a problem with this method if you have JS code loading more content:
// in that case -webViewDidFinishLoad can get called again still after document.readyState has already been in state 'complete' once or more.
self.exportHtmlContentToPDF(…)
}

I found the solution!
The export to PDF happens before the rendering process is finished. If you put in a very small picture it is showing up in the PDF. If the picture is too big the rendering process takes too much time but the PDF export isnt waiting for the rendering to finish.
So what I did to make it work is the following:
Before I export to PDF I show the Result of the HTML in a WebView. The WebView is rendering everything correctly and now when I press on export to PDF the PDF is showing up correctly with all images inside.
So I guess this is a huge lag that there is no way to tell the PDF Exporter to wait for the rendering process to finish.

Related

iOS 13 Swift 5 wkwebview - display image from documents directory into wkwebview real device

I have an app with downloaded images to be displayed in local html in WKWEBVIEW. Everything is working fine but it is not showing image on iPhone device
the html is
"\n<img src="images/d35fb6a3-8a21-4196-9616-ad2c6db60669/fd21b894-38c0-42c5-aa69-a938abe40e4b2467857252325869136.png">\n<img src="images/d35fb6a3-8a21-4196-9616-ad2c6db60669/c927a2a6-4ef0-481d-b27d-525cec7ed3814195490571216387044.png">\n"
matching to this HTML it is to be displayed in a wkwebview which gets displayed perfectly on simulator with the following code
let baseURL = Functions.FileIO.contentFolderURL()
static func contentFolderURL() -> URL? {
guard let folderURL = DataManager.shared.applicationDocumentsURL?.appendingPathComponent("content") else {
return nil
}
do {
let properties = try (folderURL as NSURL).resourceValues(forKeys: [URLResourceKey.isDirectoryKey])
if let isDirectory = properties[URLResourceKey.isDirectoryKey] as? Bool , isDirectory == false {
return nil
}
} catch let error as NSError where error.code != NSFileReadNoSuchFileError {
return nil
} catch {
// No folder, so we create it.
do {
try FileManager.default.createDirectory(atPath: folderURL.path, withIntermediateDirectories: true, attributes: nil)
} catch {
return nil
}
}
return folderURL
}
and then finally displaying
baseURL comes to be
Optional ▿ some :
file:///Users/paza/Library/Developer/CoreSimulator/Devices/D2204E03-8A8F-4EF4-8924-683CF519DD19/data/Containers/Data/Application/E4ED56CF-B247-4471-9F9B-23384FD6D6B3/Documents/content/
- _url : file:///Users/paza/Library/Developer/CoreSimulator/Devices/D2204E03-8A8F-4EF4-8924-683CF519DD19/data/Containers/Data/Application/E4ED56CF-B247-4471-9F9B-23384FD6D6B3/Documents/content/
self.webView?.loadHTMLString(HTML, baseURL: baseURL)
working in simulator below does not work in Device
as https://stackoverflow.com/a/52838445/3455426[ ]2
suggested I tried
self.webView?.loadFileURL(baseURL, allowingReadAccessTo: baseURL)
but the result this comes blank on real device
any leads will be appreciated , Thanks in advance
In WKWebview if we have to show HTML with Cached resources ,firstly we have to save the html string in HTML file in documents directory.
make sure ur cached resources path and the path of HTML file are same also , the cached resources are identified by the lastPathComponent only .
So the fault was the HTML which should have been
let HTML = "<html><body>\n<p><img src=\"fd21b894-38c0-42c5-aa69-a938abe40e4b2467857252325869136.png\"></p>\n<p><img src=\"c927a2a6-4ef0-481d-b27d-525cec7ed3814195490571216387044.png\"><br></p>\n</body></html>"
Specifically the last path component should be there .
Also I followed
https://en.it1352.com/article/9f185d4d185243cc92128148443ca660.html
here the explanation is almost perfect , I just saved in documents directory for swift.

Swift - Switch multiple (x)html files in the same webview

I have one webview in my view controller that I want to use as a reader. How can I switch between multiple (x)html files in this webview? I retrieve them one by one with the following code:
apiClient.getData(forFile: file) { (data, error) in
if let error = error {
//show error
return
}
if let data = data {
self.data = data
self.webView.load(data, mimeType: mimeType, textEncodingName: "utf-8", baseURL: URL(string: "")!)
}
}
Right now When a user scrolls (horizontal) through the pages of an epub and transitions from one file to another, there is a short loading time (because it is retrieving the next xhtml from the server).
Right now, it is a jarring experience, preventing the user from scrolling forward. When the next file is loaded, the page jumps to the new one.

Why is dismissing uinavigationcontroller not releasing view controller memory?

Implementing UIDocumentBrowserViewController in an existing app. This vc, in iOS 11 is the root view controller, and tapping a file creates my doc, instantiates my view controller, and presents it inside a UINavigationController. It all works, in that the files display, the proper document opens, the vc displays and works as expected. I had to add a left button to the nav bar to provide a way to close the doc/vc.
When I tap the "Done" button, the view controller closes and returns to the document browser. All of that is good.
The problem is that the view controller's memory isn't releasing (and the domino effect of document memory, etc then not releasing). In the iOS 10 side of things, with a UICollectionViewController embedded in a UINavigationController as the initial vc, but the doc and the display vc identical code for iOS 10 & 11, all memory releases. I've studied How to correctly dismiss a UINavigationController that's presented as a modal? and related posts, tried dozens of alternatives, and am just not seeing what I'm missing. Instruments isn't showing any memory leaks, though I see document objects in memory after dismissing the view controller. The log shows that the vc's viewWillDisappear is being called at the proper time.
I appreciate any insights into why the vc memory isn't being released (deinit() not being called).
Thank you.
#available(iOS 11.0, *)
class DocumentBrowserViewController: UIDocumentBrowserViewController, UIDocumentBrowserViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
allowsDocumentCreation = true
allowsPickingMultipleItems = false
}
// MARK: Document Presentation
func returnToDocumentBrowser(sender: UIBarButtonItem) {
print("returnToDocumentBrowser")
if let controller = self.presentedViewController as? UINavigationController {
controller.dismiss(animated: true, completion: nil)
}
}
func presentDocument(at documentURL: URL) {
print("present document")
let doc = MyDocument(fileURL: documentURL)
doc.open(completionHandler: { (success) in
if (success) {
print("open succeeded")
DispatchQueue.main.async {
let myController = self.storyboard!.instantiateViewController(withIdentifier: "my controller") as! MyViewController
myController.navigationItem.setLeftBarButton(UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.done, target: self, action: #selector(self.returnToDocumentBrowser(sender:))), animated: false)
myController.doc = doc
let navigationController = UINavigationController(rootViewController: myController)
self.present(navigationController, animated: true, completion: nil)
}
} else {
print("open failed")
}
})
}
}
The navigation stack seems at the heart of this. In the iOS 10 version, a collection view controller is the root view controller and is embedded in a navigation controller via storyboard.
In the iOS 11 version, the document browser view controller is the root view controller and cannot be embedded in a navigation controller. When I presented in the original code, my document view controller became the navigation stack's root view controller, and that cannot be popped, for example.
So I figured that maybe the navigation controller needed a different root view controller to mimic the prior behavior as much as possible and require the fewest code changes.
So I changed
let navigationController = UINavigationController(rootViewController: myController)
self.present(navigationController, animated: true, completion: nil)
to
let dummyVC = UIViewController()
let navigationController = UINavigationController(rootViewController: dummyVC)
from.present(navigationController, animated: true, completion: nil)
dummyVC.navigationController?.pushViewController(myController, animated: true)
This nav controller setup added a back button, meaning I didn't have to add a back button, and that handled popping the view controller automatically. As a result, I only needed to dismiss the nav controller, since it wasn't the app's root vc.
Finally, for the comment that the delegate should be declared weak, that's Apple's doing, and I just use it as provided. I'm not sure what affect that's having on things.

Video thumbnail image moving for inset image using jwplayer video embedded Html content in web view from navigate in swift

I am using html content for uiwebview in swift but going to one view controller to another controller navigate suddenly moving inner video image.i tried to clear web view- refer link How To Clear A UIWebView and
also used autoresizing autoresizingMask = .flexibleWidth but didn't solved.Clear web view cache using in viewDiddisappear cann't get solution[
Swift 3.0 code:
let htmlString:String = descriptionHtml
webView.loadHTMLString(htmlString, baseURL: nil)
webView.scrollView.isScrollEnabled = false
webView.allowsInlineMediaPlayback = true
let aStr = String(format: "%#%x", "document.querySelector('meta[name=viewport]').setAttribute('content', 'width=%d;', false); ", webView.frame.size.width)
webView.stringByEvaluatingJavaScript(from: aStr)
webView.delegate = self
I would appreciate if someone could help.

iOS: Excluding certain URLs when running webview

I am using Swift and have web-based content (an external webpage) embedded in WebView for my native app. On that webpage, again, which is in-app, I need to make one link open in Safari and not in the App. HTML target _blank code on the webpage doesn’t work (I wish it was that easy), looking for the right code to do it in Swift.
I have used this code for uiwebview:
#IBOutlet var news: UIWebView!
var theURL = "http://"
override func viewDidLoad() {
super.viewDidLoad()
loadWebPage()
}
func loadWebPage(){
let requestURL = NSURL (string: theURL)
let URLrequest = NSURLRequest (URL: requestURL!)
news.loadRequest(URLrequest)
I have used this code for WKNaviagtionDelegate:
func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler: ((WKNavigationActionPolicy) -> Void)) {
if (navigationAction.navigationType == WKNavigationType.LinkActivated && !navigationAction.request.URL!.host!.lowercaseString.hasPrefix("http://")) {
UIApplication.sharedApplication().openURL(navigationAction.request.URL!)
decisionHandler(WKNavigationActionPolicy.Cancel)
} else {
decisionHandler(WKNavigationActionPolicy.Allow)
}
Best, Drew
Because you need to create a button that looks like a link.
that should do the trick:
UIApplication.sharedApplication().openURL(NSURL(string: "http://...")!)
If the button is actually on a webpage in WebView... the link is not really controlled by the App unless you manipulate the link in iOS. It looks like the HTML target="_new" tag on the button might work in a later version of iOS. It's a bug in iOS 7 and was fixed in 7.0.3. Try a higher iOS version target for the App with the HTML target tag on the button.
How to open Safari from a WebApp in iOS 7