Swift: UIDocumentInteractionController is not working? - ios

UIDocumentInteractionController is not working with large pdf files with multiple pages.
Here in my code,
var docController:UIDocumentInteractionController!
...
DispatchQueue.main.async (execute: { [weak self] in
self?.docController = UIDocumentInteractionController(url: pdfFileURL!)
self?.docController.delegate = self
self?.docController.name = pdfFileURL?.lastPathComponent
self?.docController.presentPreview(animated: true)
})
and delegate method,
func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController {
return self
}
This is the warning in console,
2017-05-26 12:46:51.178894 MyApp [3350:1136818] [default] View service did terminate with error: Error Domain=_UIViewServiceInterfaceErrorDomain Code=3 "(null)" UserInfo={Message=Service Connection Interrupted} #Remote
Below attached is the blank image,
Please help me out thanks.

Try Like This:
Declare : var interaction: UIDocumentInteractionController?
Then add
interaction = UIDocumentInteractionController(url: URL(string: "<PDF FILE PATH>")!)
interaction.delegate = self
interaction.presentPreview(animated: true) // IF SHOW DIRECT
OR if need any suggestion popup
interaction.presentOpenInMenu(from: /*<SOURCE BUTTON FRAME>*/, in: self.view, animated: true)
Implement Delegate -> UIDocumentInteractionControllerDelegate
public func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController {
return self
}
public func documentInteractionControllerDidEndPreview(_ controller: UIDocumentInteractionController) {
interaction = nil
}

Be sure that your file is actually pdf file by checking its extension or if url path contains ".pdf"
Otherwise it will recognize it as text file and will not open it.

I just implemented UIDocumentInteractionController last week and it's working fine.
Do one thing declare as UIDocumentInteractionController as var below to class
then do allocation and initialization in viewDidLoad()as I did
documentInteractionController = UIDocumentInteractionController.init()
documentInteractionController?.delegate = self
and at the time of presenting I make it like this
documentInteractionController?.url = url
documentInteractionController?.presentPreview(animated: true)
Hope it will resolve your problem.

(1) This issue usually occur in simulator. Check it on real device.
If that not the case
(2) Try this,
class ViewController: UIViewController, UIDocumentInteractionControllerDelegate {
var docController:UIDocumentInteractionController!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
if let fileURL = NSBundle.mainBundle().pathForResource("SamplePDF1", ofType: "pdf") { // Use if let to unwrap to fileURL variable if file exists
docController = UIDocumentInteractionController(URL: NSURL(fileURLWithPath: fileURL))
docController.name = NSURL(fileURLWithPath: fileURL).lastPathComponent
docController.delegate = self
docController.presentPreviewAnimated(true)
}
}
func documentInteractionControllerViewControllerForPreview(controller: UIDocumentInteractionController) -> UIViewController {
return self
}
func documentInteractionControllerDidEndPreview(controller: UIDocumentInteractionController) {
docController = nil
}

If you're loading a file from Bundle, this is a known bug in iOS 11.2.x. A workaround is copying the file to tmp:
let docUrl = Bundle.main.url(forResource: "Doc", withExtension: "pdf")!
// copy to tmp, because there is a bug in iOS 11.2.x,
// which will make pdf in UIDocumentInteractionController blank
let temporaryDirectoryPath = NSTemporaryDirectory()
let tmpDir = URL(fileURLWithPath: temporaryDirectoryPath, isDirectory: true)
let tmpUrl = tmpDir.appendingPathComponent("Doc.pdf")
do {
if FileManager.default.fileExists(atPath: tmpUrl.path) {
try FileManager.default.removeItem(at: tmpUrl)
}
try FileManager.default.copyItem(at: docUrl, to: tmpUrl)
let reader = UIDocumentInteractionController(url: tmpUrl)
reader.delegate = self
reader.presentPreview(animated: true)
} catch let error {
print("Cannot copy item at \(docUrl) to \(tmpUrl): \(error)")
}
Reference: https://forums.developer.apple.com/thread/91835

It does not issue with UIDocumentInteractionController.
In my case the PDF file is corrupted, that's why it is showing me the same screen.
Try to upload/open verified pdf file and you can preview same with UIDocumentInteractionController

Related

iOS PDFKit textfield form input

I am currently developing a iOS app which can download PDF(Have form on it) from server to iPad, then user can fill in the form on the iPad.
The problem is we need to support chinese on the field input.
Here is the field.
When I use "Quick" input method to type any character, it repeat 3 times.
When I type one more time, it repeat again.
Do anyone have issues with typing Chinese in textfield using PDFKit on iOS?
Update, Add code work
For the server-side, just a endpoint can download pdf, the pdf already added textfield on it.
On client-side, pdf is downloaded in viewDidLoad() and set into pdfView as following code.
override func viewDidLoad() {
super.viewDidLoad()
if let code = self.code {
let url = URL(string : API.pdf + "/" + code)
let loading = self.showLoading()
var request = URLRequest(url: url!)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request, completionHandler: { (responseData: Data?, response: URLResponse?, error: Error?) in
DispatchQueue.main.async {
self.pdfView.document = PDFDocument(data : responseData!)
self.pdfView.scaleFactor = 1
self.pdfView.backgroundColor = UIColor.lightGray
self.pdfView.autoScales = true
loading.dismiss(animated: true, completion: nil)
}
})
task.resume()
}
}
In story board, it is just a UIView and set custom class as PDFView
Updated 2
After that, i do a simple testing.
I create a simple testing controller and load pdf with just a simple textfield annotation. Result is same.
import UIKit
import PDFKit
class PDFTestViewController : ViewController {
#IBOutlet weak var pdfView: PDFView!
override func viewDidLoad() {
super.viewDidLoad()
if let url = Bundle.main.url(forResource: "4547315264964", withExtension: "pdf"),
let doc = PDFDocument(url: url){
self.pdfView.document = doc
self.pdfView.scaleFactor = 1
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}

xcode9 swift4 Fatal error: Unexpectedly found nil while unwrapping an Optional value

I am trying to play a mp3 file(5 seconds) when app launched and I keep getting the error:
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
import UIKit
import AudioToolbox
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//create SystemSoundID
var soundID:SystemSoundID = 0
let path = Bundle.main.path(forResource: "iPhone100Voice", ofType: "mp3")
let baseURL = NSURL(fileURLWithPath: path!)
AudioServicesCreateSystemSoundID(baseURL, &soundID)
//play
AudioServicesPlaySystemSound(soundID)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Can anyone explain why and how I can fix this?
Check these points, all conditions must be true:
Is the file in the project navigator?
Is the Target Membership checkbox checked in the File Inspector (to get it select the file and press ⌥⌘1)
Is the file name spelled correctly (iPhone100Voice.mp3)?
Please check your file name, maybe filename is incorrect or not in the bundle
So please check file name with extension.
Use if let or guard to unwrap the path:
override func viewDidLoad() {
super.viewDidLoad()
var soundID:SystemSoundID = 0
if let path = Bundle.main.path(forResource: "iPhone100Voice", ofType: "mp3") {
let baseURL = NSURL(fileURLWithPath: path)
AudioServicesCreateSystemSoundID(baseURL, &soundID)
AudioServicesPlaySystemSound(soundID)
} else {
let alert = UIAlertController(title: "Alert", message: "File not found in your bundle", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.cancel, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
It may be help's to you to play video. While adding video file into project make sure target checkbox selected or not in file inspector.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
if let path = Bundle.main.path(forResource: "video_1523966087401 (1)", ofType: ".mp4"){
let player = AVPlayer(url: URL(fileURLWithPath: path))
let playerController = AVPlayerViewController()
playerController.player = player
present(playerController, animated: true) {
player.play()
}
}else{
//show dialog to user
print("Error: File not found")
}
}
! means crash operator, it unwraps optional value. Value there works as like Optional(53) , for say. So the variable should contain value except nil, when path! tries to unwrap optional and appends value 53 in path variable.
to fix this we can use two ways
Using guard,
guard let pathValue = path else {
return
}
let baseURL = NSURL(fileURLWithPath: pathValue) //no need to use pathValue, if you use then you have to use path!, although it makes no crash now.
AudioServicesCreateSystemSoundID(baseURL, &soundID)
//play
AudioServicesPlaySystemSound(soundID)
if path is nil, it enters the block and return , if it has value , it continues to next line.
using if-let, knowing optional binding
if let pathValue = path {
let baseURL = NSURL(fileURLWithPath: pathValue) //same comment
AudioServicesCreateSystemSoundID(baseURL, &soundID)
//play
AudioServicesPlaySystemSound(soundID)
}
Now, if path is optional , can't reach the block and pass to next line

PDF file not open in UIDocumentInteractionController

I am working on the simulator any there are no apps like adobe to open the pdf files....i am using UIDocumentInteractionController to open the pdf file like below.
let url=URL(fileURLWithPath: "/Users/sai/Library/Developer/CoreSimulator/Devices/425D8615-ADEC-4334-A639-C30B1E675EFA/data/Containers/Data/Application/AB3787BF-F6AC-4111-9847-A0FDC4E899F8/tmp/samplepdf.pdf")
print("--------------------------------- str is \(url)")
if (url != nil) {
// Initialize Document Interaction Controller
self.documentInteractionController = UIDocumentInteractionController(url: url)
// Configure Document Interaction Controller
self.documentInteractionController?.delegate = self
// Preview PDF
self.documentInteractionController?.presentOptionsMenu(from: self.view.frame, in: self.view, animated: true)
}
func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController {
return self
}
My screen is getting displayed like below
No pdf is being displayed here...is it because there is no app in my simulator which could open pdf files?
Not sure if any Simulator apps will handle pdf documents - maybe News? But you can just add a webView to a view controller and use that to view your pdf.
#IBOutlet weak var webView: UIWebView!
Do the usual safety checks on your url and then:
webView.loadRequest(NSURLRequest(url: url as URL) as URLRequest)
I think there is a path issue.
Replace
let url=URL(fileURLWithPath: "/Users/sai/Library/Developer/CoreSimulator/Devices/425D8615-ADEC-4334-A639-C30B1E675EFA/data/Containers/Data/Application/AB3787BF-F6AC-4111-9847-A0FDC4E899F8/tmp/samplepdf.pdf")
With
let url = NSURL(fileURLWithPath: NSTemporaryDirectory()).URLByAppendingPathComponent("samplepdf.pdf")
for Swift 3/Xcode 8
let url = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("samplepdf.pdf") as NSURL
Hope that will help.

Import PDF into own App with iOS Action Extension

I'm looking for a possibility to import a PDF in order to do some further tasks with it, just like described in this Question: Importing PDF files to app
After two days of looking around in the inter webs I found that an action extension might be the solution, this is how far I got:
override func viewDidLoad() {
super.viewDidLoad()
let fileItem = self.extensionContext!.inputItems.first as! NSExtensionItem
let textItemProvider = fileItem.attachments!.first as! NSItemProvider
let identifier = kUTTypePDF as String
if textItemProvider.hasItemConformingToTypeIdentifier(identifier) {
textItemProvider.loadItemForTypeIdentifier(identifier, options: nil, completionHandler: handleCompletion)
}
}
func handleCompletion(pdfFile: NSSecureCoding?, error: NSError!) {
print("PDF loaded - What to do now?")
}
The completion handler is called properly so I assume the PDF is loaded - but then I don't now how to proceed. If the action extension only handles images or text it could easily be downcasted, but the only way to work with files I know is with path names - which I do not have and don't know how to obtain. Plus, I'm pretty sure Sandboxing is also part of the party.
I guess I only need a push in the right direction which Class or Protocol could be suitable for my need - any suggestions highly appreciated.
For anyone else looking for an answer - I found out by myself, and it's embarrassingly easy:
func handleCompletion(fileURL: NSSecureCoding?, error: NSError!) {
if let fileURL = fileURL as? NSURL {
let newFileURL = NSURL(fileURLWithPath: NSTemporaryDirectory().stringByAppendingString("test.pdf"))
let fileManager = NSFileManager.defaultManager()
do {
try fileManager.copyItemAtURL(fileURL, toURL: newFileURL)
// Do further stuff
}
catch {
print(error)
}
}
}

How to access files in iCloud Drive from within my iOS app?

Is there a way to choose file from iCloud Drive similar way to UIImagePickerController()?
You can present controller the following way:
import MobileCoreServices
let documentPickerController = UIDocumentPickerViewController(documentTypes: [String(kUTTypePDF), String(kUTTypeImage), String(kUTTypeMovie), String(kUTTypeVideo), String(kUTTypePlainText), String(kUTTypeMP3)], inMode: .Import)
documentPickerController.delegate = self
presentViewController(documentPickerController, animated: true, completion: nil)
In your delegate implement the method:
func documentPicker(controller: UIDocumentPickerViewController, didPickDocumentAtURL url: NSURL)
Note that you don't need to set up iCloud Entitlement to use UIDocumentPickerViewController. Apple provides sample code that demonstrates how to use this controller here
Swift 5, iOS 13
Jhonattan's and Ashu's answers are definitely on the right track for the core functionality, there are a number of issues with multiple-document-selection, error outcomes and deprecated document picker API.
The code below shows a modern start-to-finish version of a common use case: pick an external iCloud document to import into app and do something with it.
Note that you have to have your app's Capabilities set up to use iCloud documents and have a ubiquity container set up in your app's .plist... See for example:
Swift write/save/move a document file to iCloud drive
class ViewController: UIViewController {
#IBAction func askForDocument(_ sender: Any) {
if FileManager.default.url(forUbiquityContainerIdentifier: nil) != nil {
let iOSPickerUI = UIDocumentPickerViewController(documentTypes: ["public.text"], in: .import)
iOSPickerUI.delegate = self
iOSPickerUI.modalPresentationStyle = .formSheet
if let popoverPresentationController = iOSPickerUI.popoverPresentationController {
popoverPresentationController.sourceView = sender as? UIView
}
self.present(iOSPickerUI, animated: true, completion: nil)
}
}
func processImportedFileAt(fileURL: URL) {
// ...
}
}
extension ViewController: UIDocumentPickerDelegate, UINavigationControllerDelegate {
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
dismiss(animated: true, completion: nil)
}
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
if controller.allowsMultipleSelection {
print("WARNING: controller allows multiple file selection, but coordinate-read code here assumes only one file chosen")
// If this is intentional, you need to modify the code below to do coordinator.coordinate
// on MULTIPLE items, not just the first one
if urls.count > 0 { print("Ignoring all but the first chosen file") }
}
let firstFileURL = urls[0]
let isSecuredURL = (firstFileURL.startAccessingSecurityScopedResource() == true)
print("UIDocumentPickerViewController gave url = \(firstFileURL)")
// Status monitoring for the coordinate block's outcome
var blockSuccess = false
var outputFileURL: URL? = nil
// Execute (synchronously, inline) a block of code that will copy the chosen file
// using iOS-coordinated read to cooperate on access to a file we do not own:
let coordinator = NSFileCoordinator()
var error: NSError? = nil
coordinator.coordinate(readingItemAt: firstFileURL, options: [], error: &error) { (externalFileURL) -> Void in
// WARNING: use 'externalFileURL in this block, NOT 'firstFileURL' even though they are usually the same.
// They can be different depending on coordinator .options [] specified!
// Create file URL to temp copy of file we will create:
var tempURL = URL(fileURLWithPath: NSTemporaryDirectory())
tempURL.appendPathComponent(externalFileURL.lastPathComponent)
print("Will attempt to copy file to tempURL = \(tempURL)")
// Attempt copy
do {
// If file with same name exists remove it (replace file with new one)
if FileManager.default.fileExists(atPath: tempURL.path) {
print("Deleting existing file at: \(tempURL.path) ")
try FileManager.default.removeItem(atPath: tempURL.path)
}
// Move file from app_id-Inbox to tmp/filename
print("Attempting move file to: \(tempURL.path) ")
try FileManager.default.moveItem(atPath: externalFileURL.path, toPath: tempURL.path)
blockSuccess = true
outputFileURL = tempURL
}
catch {
print("File operation error: " + error.localizedDescription)
blockSuccess = false
}
}
navigationController?.dismiss(animated: true, completion: nil)
if error != nil {
print("NSFileCoordinator() generated error while preparing, and block was never executed")
return
}
if !blockSuccess {
print("Block executed but an error was encountered while performing file operations")
return
}
print("Output URL : \(String(describing: outputFileURL))")
if (isSecuredURL) {
firstFileURL.stopAccessingSecurityScopedResource()
}
if let out = outputFileURL {
processImportedFileAt(fileURL: out)
}
}
}
The document picker calls the delegate’s documentPicker:didPickDocumentAtURL: method when the user selects a destination outside your app’s sandbox. The system saves a copy of your document to the specified destination. The document picker provides the copy’s URL to indicate success; however, your app does not have access to the file referred to by this URL. Link
This code work for me:
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
let url = urls[0]
let isSecuredURL = url.startAccessingSecurityScopedResource() == true
let coordinator = NSFileCoordinator()
var error: NSError? = nil
coordinator.coordinate(readingItemAt: url, options: [], error: &error) { (url) -> Void in
_ = urls.compactMap { (url: URL) -> URL? in
// Create file URL to temporary folder
var tempURL = URL(fileURLWithPath: NSTemporaryDirectory())
// Apend filename (name+extension) to URL
tempURL.appendPathComponent(url.lastPathComponent)
do {
// If file with same name exists remove it (replace file with new one)
if FileManager.default.fileExists(atPath: tempURL.path) {
try FileManager.default.removeItem(atPath: tempURL.path)
}
// Move file from app_id-Inbox to tmp/filename
try FileManager.default.moveItem(atPath: url.path, toPath: tempURL.path)
YourFunction(tempURL)
return tempURL
} catch {
print(error.localizedDescription)
return nil
}
}
}
if (isSecuredURL) {
url.stopAccessingSecurityScopedResource()
}
navigationController?.dismiss(animated: true, completion: nil)
}
This changed once again in iOS 14!!
Working example for JSON:
import UIKit
import MobileCoreServices
import UniformTypeIdentifiers
func selectFiles() {
let types = UTType.types(tag: "json",
tagClass: UTTagClass.filenameExtension,
conformingTo: nil)
let documentPickerController = UIDocumentPickerViewController(
forOpeningContentTypes: types)
documentPickerController.delegate = self
self.present(documentPickerController, animated: true, completion: nil)
}
Swift 4.X
You need to enable iCloud entitlements in XCode Capabilities. Also you have to turn on iCloud in you app bundle in developer account of Apple. Once you do this, you are able to present document picker controller by following way:
Use UIDocumentPickerDelegate methods
extension YourViewController : UIDocumentMenuDelegate, UIDocumentPickerDelegate,UINavigationControllerDelegate {
func documentMenu(_ documentMenu: UIDocumentMenuViewController, didPickDocumentPicker documentPicker: UIDocumentPickerViewController) {
documentPicker.delegate = self
self.present(documentPicker, animated: true, completion: nil)
}
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: URL) {
print("url = \(url)")
}
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
dismiss(animated: true, completion: nil)
}
}
Add below code for Button Action
#IBAction func didPressAttachment(_ sender: UIButton) {
let importMenu = UIDocumentMenuViewController(documentTypes: [String(kUTTypePDF)], in: .import)
importMenu.delegate = self
importMenu.modalPresentationStyle = .formSheet
if let popoverPresentationController = importMenu.popoverPresentationController {
popoverPresentationController.sourceView = sender
// popoverPresentationController.sourceRect = sender.bounds
}
self.present(importMenu, animated: true, completion: nil)
}
iCloudUrl.startAccessingSecurityScopedResource()
// is returning true for me at this point,
However the following code gave the error:
try FileManager.default.createDirectory(atPath: iCloudUrl, withIntermediateDirectories: true, attributes: nil)
"You can’t save the file “xyz” because the volume is read only."
This actually works :
try FileManager.default.createDirectory(at: iCloudUrl, withIntermediateDirectories: true, attributes: nil)
Which makes sense because the URL probably is carrying around it’s security access, but this little oversight stumped me for half a day…
For my swiftUI users: It's quite easy.
struct HomeView: View {
#State private var showActionSheet = false
var body: some View {
Button("Press") {
showActionSheet = true
}
.fileImporter(isPresented: $showActionSheet, allowedContentTypes: [.data]) { (res) in
print("!!!\(res)")
}
}
}
I use
try FileManager.default.copyItem(at: url, to: destinationUrl)
instead of moveItem. Otherwise, the files are removed from iCloud Drive, which is not what I want.

Resources