I am writing an app that will contain multiple PDF documents that I will display on screen depending on the user's input.
Once displayed, I would like to allow the user to draw/annotate on the PDF. I would then like to save the PDF with the drawings/annotations on for later use.
I have searched endlessly for tutorials on annotating a PDF but I am coming back with not much at all!
I have found a cocoapod on GitHub called 'UXMPDF'. Has anyone used this?
Any information on performing this type of operation would be hugely appreciated! Thanks
First Create PDFView and Load pdfFile:
override func viewDidLoad() {
super.viewDidLoad()
let pdfView = PDFView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height))
guard let document = PDFDocument(url: YOUR_FILE_PATH) else {
return
}
pdfView.document = document
pdfView.displayMode = .singlePageContinuous
pdfView.autoScales = true
pdfView.displayDirection = .horizontal
pdfView.autoresizingMask = [.flexibleWidth, .flexibleHeight, .flexibleTopMargin, .flexibleBottomMargin]
pdfView.maxScaleFactor = 4
pdfView.delegate = self
pdfView.minScaleFactor = pdfView.scaleFactorForSizeToFit
pdfView.usePageViewController(true, withViewOptions: nil)
pdfView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(pdfView)
self.pdfView = pdfView
self.pdfDocument = document
let highLightItem = UIMenuItem(title:"Highlight"), action: #selector(highlightFromMenuItem))
}
you can use pdfAnnotation from UIMenuItem like that:
#objc func highlightFromMenuItem() {
if let document = self.pdfDocument {
let pdfSelection : PDFSelection = PDFSelection(document: document)
pdfSelection.add((self.pdfView?.currentSelection)!)
let arrayByLines = self.pdfView?.currentSelection?.selectionsByLine()
arrayByLines?.forEach({ (selection) in
let annotation = PDFAnnotation(bounds: selection.bounds(for: (self.pdfView?.currentPage)!), forType: .highlight, withProperties: nil)
annotation.color = .yellow
self.pdfView?.currentPage?.addAnnotation(annotation)
})
}
}
If you want to save what did you did:
self.pdfView.document.write(to:YOUR_FILE_URL)
other pdfAnnotation you can use:
.underline
.strikeOut
.circle
.square
Related
Why is using PDFView in .singlePage mode when I try to zoom in and zoom out is lagging?
I don't have any kinds of strange settings, just create a project from scratch to replicate this.
func displayPDF() {
pdfView = PDFView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height))
if let pdf = Bundle.main.url(forResource: "sample_pdf", withExtension: "pdf", subdirectory: nil, localization: nil) {
let doc = PDFDocument(url: pdf)
pdfView.document = doc
}
pdfView.displayMode = .singlePage
view.addSubview(pdfView)
}
This is an example of what is going on:
https://streamable.com/eyxiw2
I'm subclassing the login screen of Firebaseui with:
import UIKit
import FirebaseUI
class LoginViewControllerCustom: FUIAuthPickerViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .red
let arr = Bundle.main.loadNibNamed("LoginText", owner: nil)!
let v = arr[0] as! UIView
self.view.addSubview(v)
}
}
My implementation works as I see the xib LoginText loaded on login screen.
But the background color is royally ignored.
How to enforce a bg color on the login screen from that subclass?
Edit: if I apply the answer below with view.insertSubview(imageViewBackground, at: 0)
Here is what I get:
As you can see the image gets inserted under the view that holds the login button. If I set "at: 1" it completely cover the buttons and they can't be used.
I resolved the problem in an unexpected way.
On the delegate method that would load this controller, I changed:
func authPickerViewController(forAuthUI authUI: FUIAuth) -> FUIAuthPickerViewController {
return LoginViewControllerCustom(authUI: authUI)
}
to
func authPickerViewController(forAuthUI authUI: FUIAuth) -> FUIAuthPickerViewController {
return LoginViewControllerCustom(nibName: nil, bundle: Bundle.main, authUI: authUI)
}
The addition of Bundle.main solved the issue, and replaced the original controller by mine, which was several levels deeper until that.
Not sure exactly why, but this did solve the issue.
you can try to put "fake" image background:
override func viewDidLoad() {
super.viewDidLoad()
let width = UIScreen.main.bounds.size.width
let height = UIScreen.main.bounds.size.height
let imageViewBackground = UIImageView(frame: CGRect(x: 0, y: 0, width:
width, height: height))
imageViewBackground.backgroundColor = .red
view.insertSubview(imageViewBackground, at: 0)
let arr = Bundle.main.loadNibNamed("LoginText", owner: nil)!
let v = arr[0] as! UIView
self.view.addSubview(v)
}
Edit: try this it's not elegant but it solves the problem.
override func viewDidLoad() {
let scrollView = view.subviews[0]
scrollView.backgroundColor = .clear
let contentView = scrollView.subviews[0]
contentView.backgroundColor = .red
let width = UIScreen.main.bounds.size.width
let height = UIScreen.main.bounds.size.height
let backgroundImage = UIImageView(frame: CGRect(x: 0, y: -1, width: width, height: height))
view.backgroundColor = .red
backgroundImage.contentMode = UIView.ContentMode.scaleAspectFill
view.insertSubview(backgroundImage, at: 0)
}
I just want to show image according to image size if the image height is grater then width then image will show horizentally or if the width is grater then height then it will show in centre.
I have already tried and I am showing my code. What I am trying ?
override func viewDidLoad() {
super.viewDidLoad()
var profileImagesData:Array<UIImage> = []
for i in 0..<profileImages.count {
// for venueImageTemp in profileImages {
var statusDict = profileImages[i] as! Dictionary<String,Any>
var imageUrl = String(format:"%#",statusDict["imageId"] as! CVarArg)
print(imageUrl)
if imageUrl.contains("cloudinary.com") {
let imgNameArr = imageUrl.components(separatedBy: "upload/")
print(imgNameArr)
let subFisrtStr = imgNameArr[0]
print(subFisrtStr)
let subStr = imgNameArr[1]
print(subStr)
let subImgNameArr = subStr.components(separatedBy: "/")
print(subImgNameArr)
let subNameStr = subImgNameArr[1]
print(subNameStr)
imageUrl = ""
imageUrl = subFisrtStr + "upload/h_600,w_600/" + subNameStr
print(imageUrl)
}
let url = URL(string: imageUrl)
print(url)
let data = try? Data(contentsOf: url!) //make sure your image in this url does exist, otherwise unwrap in
let image = UIImage(data: (data)!)
profileImagesData.append(image!)
}
let imageView = CYCImageScrollView(images: profileImagesData, frame: CGRect(x: 0, y: 20, width: self.view.frame.size.width, height: self.view.frame.size.height-150), pageColor: UIColor.lightGray, currentPageColor: UIColor.orange)
// imageView.layer.cornerRadius = 30
imageView.backgroundColor = UIColor.clear
imageView.contentMode = .scaleAspectFit
imageView.clipsToBounds = true
imageView.layer.masksToBounds = true
self.view.addSubview(imageView)
}
I just want to show image according to their sizes.
Use content mode scaleAspectFill instead of scaleAspectFit
imageView.contentMode = .scaleAspectFill
I would like to make the Text Widget PDFAnnotation readonly. I tried to set the isReadOnly flag to true, but it doesn't seem to make any difference. The user is still able to edit the annotation after tapping it.
I have been working on this problem for a while now and I finally found something that works. The solution for me was to use a .widget over .freeText. This method keeps the text from being selected and modified after export. I should point out that PDFs are not infallible, decompilation is possible with any PDF but for the every day office worker this is a perfect solution.
//Don't use this - This is what most tutorials show
//Which can be easily changed after export
let annotation = PDFAnnotation(bounds: CGRect(x: 10 , y: 720 , width: 100, height: 50), forType: .freeText, withProperties: nil)
Use this - Swift 5
func addText(x: Int, y: Int, width: Int, height: Int, text: String, fontSize: CGFloat, centerText: Bool){
//I'm using a PDFView but, if you are not displaying the PDF in the app then just use
//let fileURL = Bundle.main.url(forResource: "MyPDF", withExtension: "pdf")!
//let pdfDocument = PDFDocument(url: fileURL)
//guard let document = pdfDocument else { return }
guard let document = pdfView.document else { return }
//I'm only using one page for my PDF but this is easy to change
let lastPage = document.page(at: document.pageCount - 1)
let annotation = PDFAnnotation(bounds: CGRect(x: x , y: y, width: width, height: height), forType: .widget, withProperties: nil)
//Don't use contents and caption
//annotation.contents = text
//annotation.caption = text
//Use this instead
annotation.widgetFieldType = .text
annotation.widgetStringValue = text
//Check if the text should be centered
if (centerText){ annotation.alignment = .center }
//I'm using a custom font
annotation.font = UIFont(name: "calibri", size: fontSize)
//You can use this instead
//annotation.font = UIFont.systemFont(ofSize: fontSize)
//Set the color
annotation.fontColor = .black
annotation.color = .clear
annotation.backgroundColor = .clear
//This is useless
annotation.isReadOnly = true
//Add the annotation to the last page
lastPage?.addAnnotation(annotation)
}
It seems to be a bug/oversight that PDFKit doesn't honor the isReadOnly attribute on annotations. However I was able to work around this by adding a blank annotation over other annotations in the document. I added a makeReadOnly() extension to PDF document that does this for all annotations to make the whole document read only. Here's the code:
// A blank annotation that does nothing except serve to block user input
class BlockInputAnnotation: PDFAnnotation {
init(forBounds bounds: CGRect, withProperties properties: [AnyHashable : Any]?) {
super.init(bounds: bounds, forType: PDFAnnotationSubtype.stamp, withProperties: properties)
self.fieldName = "blockInput"
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(with box: PDFDisplayBox, in context: CGContext) {
}
}
extension PDFDocument {
func makeReadOnly() {
for pageNumber in 0..<self.pageCount {
guard let page = self.page(at: pageNumber) else {
continue
}
for annotation in page.annotations {
annotation.isReadOnly = true // This _should_ be enough, but PDFKit doesn't recognize the isReadOnly attribute
// So we add a blank annotation on top of the annotation, and it will capture touch/mouse events
let blockAnnotation = BlockInputAnnotation(forBounds: annotation.bounds, withProperties: nil)
blockAnnotation.isReadOnly = true
page.addAnnotation(blockAnnotation)
}
}
}
}
I use PDFView to display PDFs. If I scroll quickly I get memory leaks.
let pdfView = PDFView.init(frame: CGRect.zero)
self.view.insertSubview(pdfView, belowSubview: bottomBar)
pdfView.snp.makeConstraints { (make) in
make.top.equalTo(topBar.snp.bottom).offset(8)
make.left.equalTo(8)
make.right.equalTo(-8)
make.bottom.equalTo(-54)
}
let url = Bundle.main.url(forResource: "swift", withExtension: "pdf")!
let doc = PDFDocument.init(url: url)
pdfView.document = doc
if let page = doc?.page(at: 0) {
let rect = page.bounds(for: .mediaBox)
let scale = (UIScreen.width - 16) / rect.width
pdfView.minScaleFactor = scale
pdfView.maxScaleFactor = scale
pdfView.scaleFactor = scale
pdfView.displaysPageBreaks = true
pdfView.pageBreakMargins = UIEdgeInsets.init(top: 0, left: 0, bottom: 8/scale, right: 0)
pdfView.layoutDocumentView()
}
View Memory Graph Image
Can you try to display your PDF in a WebView instead ?
Something like this :
let url = yourLocalURL
webView.loadRequest(URLRequest(url: URL(string: url)!))