iOS UIPrintPageRenderer prints last page for each page to be printed - ios

I am in middle of developing an iOS app and I want to export certain reports in PDF format. So I am using webView to generate report and print webView to pdf.
Here is my code:
var renderer : UIPrintPageRenderer!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
renderer = CustomPrintPageRenderer()
if let url = Bundle.main.url(forResource: "pdf", withExtension: "html", subdirectory: "web") {
let request = URLRequest(url: url);
webView.delegate = self;
webView.loadRequest(request);
}
}
func webViewDidFinishLoad(_ webView: UIWebView) {
var pages : [UIViewPrintFormatter] = []
var index = 0
for query in queryList {
var result = webView.stringByEvaluatingJavaScript(from: query)
if(!webView.isLoading) {
let printFormatter = webView.viewPrintFormatter()
pages.append(printFormatter)
}
}
for page in pages {
renderer.addPrintFormatter(page, startingAtPageAt: index)
index = index + 1
}
let pdfData = drawPDFUsingPrintPageRenderer(printPageRenderer: renderer)
pdfData?.write(toFile: "\(AppDelegate.getAppDelegate().getDocDir())/report.pdf", 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
}
Now problem is that, the generated PDF contains only last page printed multiple time (once for each page in pages which is not the expected behavior.

Related

How do I generate a PDF containing views with dimensions larger than device screen?

I have several UIViews with clear background color, and black border outlines that all resize based on values I enter in length, width, and height text fields. These views are subviews of a UIScrollView. After the views resize I create a PDF from the contents of the scroll view resulting in vectorized outlines that I can use and manipulate in Adobe Illustrator.
This process works fine when I use small dimensions for the views (i.e. 15 x 15), but when I use more ideal dimensions for my purpose (i.e. 3000 x 3000) the views extend off the bounds of the screen and the resulting PDF generated contains only a small portion of the views.
Ideally I need to be able to create much larger views and still be able to generate a PDF containing them. I'm envisioning a canvas that resizes (zooms out?) based on its contents and retains the scale of its subviews so that the resulting views in the PDF will be to-scale when viewed in Adobe Illustrator. This is my code so far:
override func viewDidLoad() {
super.viewDidLoad()
lengthTextField.delegate = self
widthTextField.delegate = self
heightTextField.delegate = self
scrollView.delegate = self
scrollView.autoresizingMask = [UIView.AutoresizingMask.flexibleWidth,UIView.AutoresizingMask.flexibleHeight]
scrollView.minimumZoomScale = 1
scrollView.maximumZoomScale = 50
scrollView.zoomScale = 1
self.setupGestureRecognizer()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
scrollView.contentSize = CGSize(width: 5000, height: 5000)
}
func setupGestureRecognizer() {
}
#IBAction func viewPDF(_ sender: Any) {
createPDFfrom(aView: self.scrollView.subviews[0], saveToDocumentsWithFileName: "MC.pdf")
// Create and add a PDFView to the view hierarchy.
let pdfView = PDFView(frame: self.scrollView.subviews[0].bounds)
pdfView.autoScales = true
view.addSubview(pdfView)
// Create a PDFDocument object and set it as PDFView's document to load the document in that view.
let documentsDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let filePath = (documentsDirectory as NSString).appendingPathComponent("MC.pdf") as String
let pdfDocument = PDFDocument(url: URL(fileURLWithPath: filePath))!
pdfView.document = pdfDocument
let document = NSData(contentsOfFile: filePath)
let vc = UIActivityViewController(activityItems: [document as Any], applicationActivities: nil)
self.present(vc, animated: true, completion: nil)
}
func createPDFfrom(aView: UIView, saveToDocumentsWithFileName fileName: String)
{
let pdfData = NSMutableData()
UIGraphicsBeginPDFContextToData(pdfData, aView.bounds, nil)
UIGraphicsBeginPDFPage()
guard let pdfContext = UIGraphicsGetCurrentContext() else { return }
aView.layer.render(in: pdfContext)
UIGraphicsEndPDFContext()
if let documentDirectories = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first {
let documentsFileName = documentDirectories + "/" + fileName
debugPrint(documentsFileName)
pdfData.write(toFile: documentsFileName, atomically: true)
}
}
#IBAction func updateDimensions(_ sender: Any) {
guard let length = NumberFormatter().number(from:
lengthTextField.text ?? "") else { return }
guard let width = NumberFormatter().number(from:
widthTextField.text ?? "") else { return }
guard let height = NumberFormatter().number(from:
heightTextField.text ?? "") else { return }
let flapHeight = CGFloat(truncating: width)/2
let lengthFloat = CGFloat(truncating: length)
let widthFloat = CGFloat(truncating: width)
let heightFloat = CGFloat(truncating: height)
UIView.animate(withDuration: 0.3) {
self.faceAWidthConstraint.constant = lengthFloat
self.faceAHeightConstraint.constant = heightFloat
self.faceBWidthConstraint.constant = widthFloat
self.faceA1HeightConstraint.constant = flapHeight
self.view.layoutIfNeeded()
}
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return scrollView.subviews[0]
}
}
I think if you use UIGraphicsPDFRenderer this might work better:
let url = URL(fileURLWithPath: filePath)
let pdfGenerator = UIGraphicsPDFRenderer(bounds: .init(x: 0, y: 0, width: 612, height: 792))
pdfGenerator.writePDF(to: url) { context in
let cgContext = context.cgContext
aView.layer.render(in: cgContext)
}
If the views are still extending outside of the PDF's bounds then you could scale the context:
cgContext.scaleBy(x: 0.25, y:0.25)

Memory leaks after generating QR Code and making PDF file in Swift 4.2

I am creating an app that, in the end of one cycle, generates a QR Code and then creates a PDF file. After running the app several times (tested for almost 5 days) the app stopped doing its other required functionalities. I found out that every time the app generates QR and makes PDF, the memory graph grows higher and higher in successive cycles of app. Debug memory graph indicates 3 memory leaks: one at viewDidAppear event, another at createPDF method, and third at generateQrCode method. I am deleting the PDF file every time user starts a new cycle. I don't know how to handle these memory leaks. Can someone help? Here is the code:
let html = ""
override func viewDidAppear(_ animated: Bool) {
createPDF()
return
}
func createPDF() {
html = "<div style='display: flex; justify-content: center;'></div>"
generateQrCode(arg: true, completion: { (success) -> Void in
print("QR Code Generated")
let image1 = captureView()
let imageData1 = image1.pngData() ?? nil
let base64String1 = imageData1?.base64EncodedString() ?? ""
if success {
self.html += "<html><body><div style='text-align: center'><p><br></p><p><b><img width='50%' height='20%' src='data:image/png;base64,\(String(describing: base64String1))'/><p><br></p><p><b></b></p></div></body></html>"
weak var webView = WKWebView(frame: self.view.frame)
self.view.addSubview(webView!)
webView!.navigationDelegate = self
webView!.loadHTMLString(html, baseURL: nil)
webView!.isUserInteractionEnabled = false
} else {
print("false")
}
})
}
func generateQrCode(arg: Bool, completion: (Bool) -> ()){
let data = self.dataToQr.data(using: .ascii, allowLossyConversion: false)
let filter = CIFilter(name: "CIQRCodeGenerator")
filter?.setValue(data, forKey: "inputMessage")
let img = UIImage(ciImage: (filter?.outputImage)!)
self.qrImageView.image = img
completion(true)
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
let render = UIPrintPageRenderer()
render.addPrintFormatter(webView.viewPrintFormatter(), startingAtPageAt: 0)
let page = CGRect(x: 0, y: 0, width: 595.2, height: 841.8) // A4, 72 dpi
let printable = page.insetBy(dx: 0, dy: 0)
render.setValue(NSValue(cgRect: page), forKey: "paperRect")
render.setValue(NSValue(cgRect: printable), forKey: "printableRect")
let pdfData = NSMutableData()
UIGraphicsBeginPDFContextToData(pdfData, .zero, nil)
for i in 1...render.numberOfPages {
UIGraphicsBeginPDFPage();
let bounds = UIGraphicsGetPDFContextBounds()
render.drawPage(at: i - 1, in: bounds)
}
UIGraphicsEndPDFContext();
let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
pdfData.write(toFile: "\(documentsPath)/\(UserDefaults.standard.string(forKey: "yourName")!)_\(UserDefaults.standard.string(forKey: "gameName")!)_Kapper.pdf", atomically: true)
print("\(documentsPath)/apper.pdf")
webView.removeFromSuperview()
}
From what I've seen of the code, your memory leak seems to be caused by the strong reference you have to the self in this closure
completion: { (success) -> Void in
print("QR Code Generated")
let image1 = captureView()
let imageData1 = image1.pngData() ?? nil
let base64String1 = imageData1?.base64EncodedString() ?? ""
if success {
self.html += "<html><body><div style='text-align: center'><p><br></p><p><b><img width='50%' height='20%' src='data:image/png;base64,\(String(describing: base64String1))'/><p><br></p><p><b></b></p></div></body></html>"
weak var webView = WKWebView(frame: self.view.frame)
self.view.addSubview(webView!)
webView!.navigationDelegate = self
webView!.loadHTMLString(html, baseURL: nil)
webView!.isUserInteractionEnabled = false
} else {
print("false")
}
}
Here's what to do:
Create your webView in your viewDidLoad method for the controller, not inside the closure or make it an IBOutlet on your storyboard. Then make the self reference in the closure a weak one like below.
generateQrCode(arg: true, completion: { [weak self] (success) -> Void in
print("QR Code Generated")
let image1 = captureView()
let imageData1 = image1.pngData() ?? nil
let base64String1 = imageData1?.base64EncodedString() ?? ""
if success {
self?.html += "<html><body><div style='text-align: center'><p><br></p><p><b><img width='50%' height='20%' src='data:image/png;base64,\(String(describing: base64String1))'/><p><br></p><p><b></b></p></div></body></html>"
self?.webView.navigationDelegate = self
self?.webView.loadHTMLString(html, baseURL: nil)
self?.webView.isUserInteractionEnabled = false
} else {
print("false")
}
})
This should work. Let me know how it turns out.
For more context, check out these links:
How to Correctly handle Weak Self in Swift Blocks with Arguments
Block retain cycles in Swift?
https://medium.com/#sergueivinnitskii/most-common-memory-leak-trap-in-swift-4565dbae5445
P.S: I haven't answered a StackOverflow question in years. :)

Present preview of downloaded file directly in app

My application have file download option which downloads the file using alamofire download method. When the download completes i need to present the preview of the file without saving it to internal/cloud storage. How can i achieve this whatsapp like function that shows the preview after downloading the file.
func downloadFile(fileUrl: URL) {
let destination = DownloadRequest.suggestedDownloadDestination(for: .documentDirectory)
Alamofire.download(fileUrl, to: destination)
.response(completionHandler: { (downloadResponse) in
self.dic.url = downloadResponse.destinationURL
self.dic.uti = downloadResponse.destinationURL!.uti
let rect = CGRect(x: 0, y: 0, width: 100, height: 100)
self.dic.presentOpenInMenu(from: rect, in: self.view, animated: true)
})
}
To present a preview of a file use Appleā€™s QuickLook framework that lets you embed previewing for a huge range of file types, including iWork documents, Microsoft Office documents, PDFs, images, and more, all without writing much code.
First, import the QuickLook framework, then make your view controller conform to the QLPreviewControllerDataSource protocol.
Reference:
https://www.hackingwithswift.com/example-code/libraries/how-to-preview-files-using-quick-look-and-qlpreviewcontroller
https://github.com/gargsStack/QLPreviewDemo
https://www.appcoda.com/quick-look-framework/
Code:
class ViewController: UIViewController {
var previewItem = URL!
func downloadFile(fileUrl: URL) {
let destination = DownloadRequest.suggestedDownloadDestination(for: .documentDirectory)
Alamofire.download(fileUrl, to: destination)
.response(completionHandler: { (downloadResponse) in
let previewController = QLPreviewController()
previewController.dataSource = self
self.previewItem = downloadResponse.destinationURL
self.present(previewController, animated: true, completion: nil)
})
}
}
extension ViewController: QLPreviewControllerDataSource {
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
return 1
}
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
return self.previewItem as QLPreviewItem
}
}
Here is one solution using Alamofire. Someone may help.
Steps:
Alamofire has excellent staff to direct download and also save/write
your file into disc.
Return a path where downloaded file saved.
Using UIDocumentInteractionController pass the file path
Then present this view
import Alamofire
extension UIViewController : UIDocumentInteractionControllerDelegate{
func downloadFileForPreview(fileName: String, fileExtension: String, filePath: String ) {
let destination: DownloadRequest.DownloadFileDestination = { _, _ in
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let fileWithExtension = "file.\(fileExtension)"
let fileURL = documentsURL.appendingPathComponent(fileWithExtension)
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}
//UtilitySwift.showUniversalLoadingView(true)
Alamofire.download(filePath, to: destination).response { response in
debugPrint(response)
//UtilitySwift.showUniversalLoadingView(false)
if response.error == nil, let storeFilePath = response.destinationURL?.path {
//let image = UIImage(contentsOfFile: imagePath)
self.previewDocument(withFilePath: response.destinationURL)
print(storeFilePath)
}
else{
UtilitySwift.showErrorMessage(message: response.error?.localizedDescription ?? "Error occured when downloading" )
print(response.error?.localizedDescription ?? "")
}
}
}
// Converted to Swift 5.1 by Swiftify v5.1.29672 - https://objectivec2swift.com/
func previewDocument(withFilePath filePath: URL?) {
var documentInteractionController: UIDocumentInteractionController?
if filePath != nil {
// Initialize Document Interaction Controller
if let filePath = filePath {
documentInteractionController = UIDocumentInteractionController(url: filePath)
}
// Configure Document Interaction Controller
documentInteractionController?.delegate = self as UIDocumentInteractionControllerDelegate
//if not possible to open
if !documentInteractionController!.presentPreview(animated: true) {
documentInteractionController?.presentOptionsMenu(from: CGRect.zero, in: self.view, animated: true)
}
} else {
// error
print("file preview error")
}
}
//UIDocumentInteractionControllerDelegate
public func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController {
self
}
}
Call from any UIViewController
self.downloadFileForPreview(fileName: "file", fileExtension: fileExt ?? "", filePath: REPLACE_WITH_DOWNLOAD_URL)

PDFView unable to move to nextpage

Code:
if let path = Bundle.main.path(forResource: "test", ofType: "pdf") {
let url = URL(fileURLWithPath: path)
if let pdfDocument = PDFDocument(url: url) {
pdfView.displayMode = .singlePage
pdfView.autoScales = true
pdfView.displayDirection = .horizontal
pdfView.document = pdfDocument
}
}
i try to show pdf file in app.it is working fine.how to implement horizontal scrolling(page by page) and search function(like highlight search word) features.any help will be appericated.thanks in advance
you need to add this line of code:
pdfView.usePageViewController(true, withViewOptions: nil)
****UPDATE**
PDFDocument . has some notifications about search. Have a look.
try this code for searching string.in this line beginFindString you can set string
var searchedString: PDFSelection?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.document?.beginFindString("StringName", withOptions: .caseInsensitive)
}
func documentDidEndDocumentFind(_ notification: Notification) {
pdfView.setCurrentSelection(searchedString, animate: true)
}
func documentDidFindMatch(_ notification: Notification) {
if let selection = notification.userInfo?.first?.value as? PDFSelection {
selection.color = .Red
if searchedString == nil {
// The first found item sets the object.
searchedString = selection
} else {
// All other found selection will be nested
searchedString!.add(selection)
}
}
}

How can I render an entire UICollectionView into a PDF (in Swift), rather than just the part that was on the screen?

I have a UICollectionView which scrolls through multiple pages. I want to put the entire collection view into PDF form, whereas now, all I get in PDF form is the part of the collection view that was last on the screen. The code is:
func createPdfFile() -> URL?
{
if let myView = myView {
let pdfData = NSMutableData()
UIGraphicsBeginPDFContextToData(pdfData, myView.bounds, nil)
UIGraphicsBeginPDFPage()
guard let pdfContext = UIGraphicsGetCurrentContext() else { return nil }
myView.layer.render(in: pdfContext)
UIGraphicsEndPDFContext()
let fileName = "temp.pdf"
let fileURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(fileName)
if pdfData.write(to: fileURL, atomically: true) {
print("Successfully wrote to \(fileURL)")
return fileURL
}
else {
return nil
}
}
else {
DLog("Error: Could not create pdf")
return nil
}
}
func printView() {
if let fileURL = createPdfFile() {
let webView = UIWebView()
webView.loadRequest(URLRequest(url:
fileURL))
self.view = webView
}
else {
DLog("Error: Could not print the pdf. Write to disk failed.")
alertOk("Failed to generate PDF.", title: "Error")
}
}
How can I get it to render all of the view into a PDF?

Resources