How to highlight hyperlinks in PDFPage using PDFKit framework? - ios

I'm using PDFKit framework, and want to highlight all the hyperlinks with blue color in all the pages in the PDF. How do I go ahead? I have searched but could not get enough relevant post.

If you want to extract all links from pdf, then apply a regular expression and extract all links in an array like:
let text = pdfView.document?.string ?? ""
let types: NSTextCheckingResult.CheckingType = .link
do {
let detector = try NSDataDetector(types: types.rawValue)
let matchResult = detector.matches(in: text, options: .reportCompletion, range: NSRange(location: 0, length: text.count))
let linksArray: [URL] = matchResult.compactMap({ $0.url })
print("List of available links: \(linksArray)")
} catch (let error) {
print (error.localizedDescription)
}
But, if you just want to highlight the links and click action in them then PDFKit does have a property enableDataDetectors to detect links in the PDFView. You have to just enable it.
As per apple documentation:
Turns on or off data detection. If enabled, page text will be scanned for URL's as the page becomes visible. Where URL's are found, Link annotations are created in place. These are temporary annotations and are not saved.
You can use it as:
let pdfView = PDFView.init(frame: self.view.bounds)
pdfView.enableDataDetectors = true
If you need to handle click of this link, then conform to PDFViewDelegate, and it will call delegate method:
func pdfViewWillClick(onLink sender: PDFView, with url: URL) {
}

I also had the same question and this is implemented functionality to accomplish the task.
When initializing the pdf document, we need to enable enableDataDetectors which will turns on the data detection, which adds annotations for detected URLs in a page.
PDFView *pdfView = [[PDFView alloc] initWithFrame: containerView.bounds];
pdfView.document = pdfDocument;
pdfView.enableDataDetectors = true;
Then using following function, we can extract the page hyperlinks. I converted those hyperlinks to PDFDestination for easy navigation. Hope this will helps to someone!
-(NSArray*)getHyperlinksDestinationsListFrom:(PDFDocument*)pdfDocument {
NSMutableArray *list = [NSMutableArray new];
if (pdfDocument) {
for (int i = 0; i < pdfDocument.pageCount; i++) {
PDFPage *page = [pdfDocument pageAtIndex:i];
// After enabling 'enableDataDetectors', all the detectable hyperlinks will return as 'Link' annotations
for (PDFAnnotation *anno in page.annotations) {
if ([anno.type isEqual: #"Link"]) {
PDFDestination *dest = [[PDFDestination alloc] initWithPage:page atPoint:anno.bounds.origin];
[list addObject:dest];
}
}
}
}
return list;
}
Highlight part is straight-forward once you detected the links.

Related

How to find and highlight a string in a pdf displayed in a WKWebView?

I have an iOS app where I can display html and pdf-files. I try to implement a find-in-page functionality which is available since iOS14 (find(_:configuration:completionHandler:)).
Here is my implementation:
private func searchInPage(forward: Bool) {
ktWebView.select(nil)
searchIsAtTop = false
searchIsAtEnd = false
let searchConfig = WKFindConfiguration()
searchConfig.backwards = !forward
searchConfig.caseSensitive = false
searchConfig.wraps = false
ktWebView.find(searchString, configuration: searchConfig, completionHandler: { result in
// https://stackoverflow.com/questions/64193691/ios-wkwebview-findstring-not-selecting-and-scrolling
guard result.matchFound else {
if forward { searchIsAtEnd = true }
else { searchIsAtTop = true }
return
}
ktWebView.evaluateJavaScript("window.getSelection().getRangeAt(0).getBoundingClientRect().top") { offset, _ in
guard let offset = offset as? CGFloat else { return }
ktWebView.scrollView.scrollRectToVisible(
.init(x: 0, y: offset + ktWebView.scrollView.contentOffset.y,
width: 100, height: 100), animated: true)
}
})
}
With each invocation of this function the next occurrence of the searchString will be highlighted and scrolled to.
This works fine as long as the mime type of the WKWebView content is text/html. When it is application/pdf it doesn't work.
Can anyone help me with a solution to find a string in a pdf document displayed in a WKWebView?
Thanks, Clemens
I have no clue how to approach this problem. Any help is appreciated.
It working for text files, maybe need to copy text from PDF and render it as a text / string, found some example:
Can't copy text from PDF which created with iOS Swift
I've solved it now by using PDFView for pdf files instead of WKWebView.
A sample exemplary implementation you may find at https://github.com/cs-LA/FindInPDF.
Thanks for all your efforts to help me with this problem,
Clemens
Are you sure your PDF file has text,
maybe it was converted from text to vector!

How to preview multiple PDF files in iOS similar to WhatsApp upload documents functionality?

I am integrating UIDocumentPickerViewController to show the local storage (File App) for browsing and selecting the PDF. Right now i am selecting a single PDF and previewing it by passing the URL to WKWebview which is working fine. But when i enable allowsMultipleSelection i am able to select the multiple files and getting multiple URLs
NSArray *types = #[(NSString*)kUTTypePDF];
//Create a object of document picker view and set the mode to Import
UIDocumentPickerViewController *docPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:types inMode:UIDocumentPickerModeImport];
//Set the delegate
docPicker.delegate = self;
docPicker.allowsMultipleSelection = true; // Allows multiple selection.
//present the document picker
[self presentViewController:docPicker animated:YES completion:nil];
The delegate for getting multiple URLs is :
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray <NSURL *>*)urls API_AVAILABLE(ios(11.0));
while previewing using WKWebView i am able to preview only one file as shown below:
But i want to preview both the selected files as WhatsApp does as shown below. Here i can swipe horizontally to preview the selected files
How to preview multiple files similar to WhatsApp? Please help me in this regard.
Use a QLPreviewController; you'll need to import QuickLook. It's a view controller. You show it as a presented view controller or push it onto a navigation controller's stack.
In this example, I have somewhere in my Documents directory one or more PDF or text documents. I acquire a list of their URLs and present a preview for them (self.exts has been initialized to a set consisting of ["pdf", "txt"]):
self.docs = [URL]()
do {
let fm = FileManager.default
let docsurl = try fm.url(for:.documentDirectory,
in: .userDomainMask, appropriateFor: nil, create: false)
let dir = fm.enumerator(at: docsurl, includingPropertiesForKeys: nil)!
for case let f as URL in dir {
if self.exts.contains(f.pathExtension) {
if QLPreviewController.canPreview(f as QLPreviewItem) {
self.docs.append(f)
}
}
}
guard self.docs.count > 0 else { return }
let preview = QLPreviewController()
preview.dataSource = self
preview.currentPreviewItemIndex = 0
self.present(preview, animated: true)
} catch {
print(error)
}
You'll notice that I haven't told the QLPreviewController what documents to preview. That is the job of QLPreviewController's data source. In my code, I (self) am also the data source. I simply fetch the requested information from the list of URLs, which I previously saved into self.docs:
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
return self.docs.count
}
func previewController(_ controller: QLPreviewController,
previewItemAt index: Int) -> QLPreviewItem {
return self.docs[index] as QLPreviewItem
}
The second data source method requires us to return an object that adopts the QLPreviewItem protocol. URL does adopt this protocol.

Getting Climacons to display in UILabel with CZWeatherKit in Swift

So I am using the CZWeatherKit library to grab weather data from forecast.io.
When I get results, it sends a climacon UInt8 char, which should match to an icon if the climacon font is installed. I did that but it only shows the char, not the actual icon. Here is the code, it prints a quote i.e. " which is the correct mapping to ClimaconCloudSun, but the icon doesn't show. I followed these instructions to install the climacons.ttf font
request.sendWithCompletion { (data, error) -> Void in
if let error = error {
print(error)
} else if let weather = data {
let forecast = weather.dailyForecasts.first as! CZWeatherForecastCondition
dispatch_async(dispatch_get_main_queue(), { () -> Void in
// I get back good results, this part works
let avgTempFloat = (forecast.highTemperature.f + forecast.lowTemperature.f) / 2
let avgTemp = NSDecimalNumber(float: avgTempFloat).decimalNumberByRoundingAccordingToBehavior(rounder)
self.temperatureLabel.text = String(avgTemp)
self.weatherLabel.text = forecast.summary
// this part does not work, it has the right char, but does not display icon
// I tried setting self.climaconLabel.font = UIFont(name: "Climacons-Font", size: 30) both in IB and programmatically
let climaChar = forecast.climacon.rawValue
let climaString = NSString(format: "%c", climaChar)
self.climaconLabel.text = String(climaString)
})
}
}
I solved the exact same issue, the problem was the font file. Replace your current font with the one provided here: https://github.com/comyar/Sol/blob/master/Sol/Sol/Resources/Fonts/Climacons.ttf
You've probably moved on from this problem by now, but I'll leave this here for future use.
You need to call setNeedsLayout on the label after you change the title text to the desired value, and the label will change to the corresponding icon.

How can I parse content from a PDF page with Swift

The documentation is not really clear to me. So far I reckon I need to set up a CGPDFOperatorTable and then create a CGPDFContentStreamCreateWithPage and CGPDFScannerCreate per PDF page.
The documentation refers to setting up Callbacks, but it's unclear to me how. How to actually obtain the content from a page?
This is my code so far.
let pdfURL = NSBundle.mainBundle().URLForResource("titleofdocument", withExtension: "pdf")
// Create pdf document
let pdfDoc = CGPDFDocumentCreateWithURL(pdfURL)
// Nr of pages in this PF
let numberOfPages = CGPDFDocumentGetNumberOfPages(pdfDoc) as Int
if numberOfPages <= 0 {
// The number of pages is zero
return
}
let myTable = CGPDFOperatorTableCreate()
// lets go through every page
for pageNr in 1...numberOfPages {
let thisPage = CGPDFDocumentGetPage(pdfDoc, pageNr)
let myContentStream = CGPDFContentStreamCreateWithPage(thisPage)
let myScanner = CGPDFScannerCreate(myContentStream, myTable, nil)
CGPDFScannerScan(myScanner)
// Search for Content here?
// ??
CGPDFScannerRelease(myScanner)
CGPDFContentStreamRelease(myContentStream)
}
// Release Table
CGPDFOperatorTableRelease(myTable)
It's a similar question to: PDF Parsing with SWIFT but has no answers yet.
Here is an example of the callbacks implemented in Swift:
let operatorTableRef = CGPDFOperatorTableCreate()
CGPDFOperatorTableSetCallback(operatorTableRef, "BT") { (scanner, info) in
print("Begin text object")
}
CGPDFOperatorTableSetCallback(operatorTableRef, "ET") { (scanner, info) in
print("End text object")
}
CGPDFOperatorTableSetCallback(operatorTableRef, "Tf") { (scanner, info) in
print("Select font")
}
CGPDFOperatorTableSetCallback(operatorTableRef, "Tj") { (scanner, info) in
print("Show text")
}
CGPDFOperatorTableSetCallback(operatorTableRef, "TJ") { (scanner, info) in
print("Show text, allowing individual glyph positioning")
}
let numPages = CGPDFDocumentGetNumberOfPages(pdfDocument)
for pageNum in 1...numPages {
let page = CGPDFDocumentGetPage(pdfDocument, pageNum)
let stream = CGPDFContentStreamCreateWithPage(page)
let scanner = CGPDFScannerCreate(stream, operatorTableRef, nil)
CGPDFScannerScan(scanner)
CGPDFScannerRelease(scanner)
CGPDFContentStreamRelease(stream)
}
You've actually specified exactly how to do it, all you need to do is put it together and try until it works.
First of all, you need to setup a a table with callbacks as you state yourself in the beginning of your question (all code in Objective C, NOT Swift):
CGPDFOperatorTableRef operatorTable = CGPDFOperatorTableCreate();
CGPDFOperatorTableSetCallback(operatorTable, "q", &op_q);
CGPDFOperatorTableSetCallback(operatorTable, "Q", &op_Q);
This table contains a list of the PDF operators you want to get called for and associates a callback with them. Those callbacks are simply functions you define elsewhere:
static void op_q(CGPDFScannerRef s, void *info) {
// Do whatever you have to do in here
// info is whatever you passed to CGPDFScannerCreate
}
static void op_Q(CGPDFScannerRef s, void *info) {
// Do whatever you have to do in here
// info is whatever you passed to CGPDFScannerCreate
}
And then you create the scanner and get it going, while passing it the information you just defined.
// Passing "self" is just an example, you can pass whatever you want and it will be provided to your callback whenever it is called by the scanner.
CGPDFScannerRef contentStreamScanner = CGPDFScannerCreate(contentStream, operatorTable, self);
CGPDFScannerScan(contentStreamScanner);
If you want to see a complete example with sourcecode on how to find and process images, check this website.
To understand why a parser works this way, you need to read the PDF specification a bit better. A PDF file contains something close to printing instructions. Such as "move to this coordinate, print this character, move there, change the color, print the character number 23 from the font #23", etc.
The parser gives you callbacks for each instructions, with the possibility to retrieve the instruction parameters. That's all.
So, in order to get the content from a file, you need to rebuild its state manually. Which means, recompute the frames for all characters, and try to reverse-engineer the page layout. This is clearly not an easy task, and that's why people have created libraries to do so.
You may want to have a look at PDFKitten , or PDFParser which is a Swift port with some improvement that i did.

UIDocumentInteractionController displaying blank pdf

I'm trying to display a pdf on iOS devices using the UIDocumentInteractionController presentPreviewAnimated method, but it keeps displaying a blank document. I think it might have to do with the character encoding, but I'm not sure. If I use a UIWebView, I can get the pdf to display, just not with the document interaction controller.
// UPDATE 9/18/14
This is now working with the GM release of Xcode 6.
// UPDATE 8/22/14
Oddly enough, from the DocumentInteractionController, if I tap on the "Open In" icon in the top right corner and choose something like iBooks, the pdf displays correctly. It seems as though it's just the preview that doesn't want to display it on the screen.
Here's my code (in Swift):
// data is coming in as NSISOLatin1StringEncoding
func displayPdfInUIDocumentInteractionController(data: NSData) {
let fileName = NSTemporaryDirectory().stringByAppendingPathComponent("myFile.pdf")
let url: NSURL! = NSURL(fileURLWithPath: fileName)
// this does not seem to make a difference
// let pdfString = NSString(data: data, encoding: NSISOLatin1StringEncoding)
// pdfString.writeToURL(url!, atomically: true, encoding: NSISOLatin1StringEncoding, error: nil)
data.writeToURL(url, atomically: true)
if url != nil {
let docController = UIDocumentInteractionController(URL: url)
docController.UTI = "com.adobe.pdf"
docController.delegate = self
docController.presentPreviewAnimated(true)
}
}
This code does display the pdf correctly:
// data is coming in as NSISOLatin1StringEncoding
func displayPdfInUIWebView(data: NSData) {
let rect = UIScreen.mainScreen().bounds
let screenSize = rect.size
let webView = UIWebView(frame: CGRectMake(0,0,screenSize.width,screenSize.height))
webView.autoresizesSubviews = true
webView.autoresizingMask = (UIViewAutoresizing.FlexibleHeight | UIViewAutoresizing.FlexibleWidth)
webView.loadData(data, MIMETYype: "application/pdf", textEncodingName: "ISO-8859-1", baseUrl: nil)
self.view.addSubview(webView)
}
Is there any reason the first function should not be working? It doesn't error out, just displays a blank page.
I'm not using Swift, but I had basically the same problem with straight up Objective-C. Before iOS8, my UIDocumentInteractionController displayed pretty much every file type i threw at it including PDF. But in iOS8, the PDF files would no longer display for me.
I WAS creating it this way:
[[[UIDocumentInteractionController alloc] init] autorelease]
I changed the call to create it like this:
[UIDocumentInteractionController interactionControllerWithURL:myUrl]
And now my PDF files display again (and the others appear to be ok too still).
This is working with the GM release of Xcode. Guess it was just a bug.

Resources