0

I am really stuck on how to import a pdf file and store it in a table view cell. I am using the UIDocumentPicker to import the pdf file but I am having trouble figuring out on how to store the pdf files data to a table view cell. My issue is that I have created a function called insertNewFileTitle() that appends a new table view cell every time I click the addButtonTapped function. I want to make sure that the pdf data is still in the correct table view cell from where i imported the pdf file.

What im trying to do is have table view cells store the imported pdf file so then later on i can send the pdf file as an attachment for the mail composer when the table cell is selected. Any insight is appreciated, thank you.

var pdfFiles: [String] = []
var myPDFsArray : Array<MyPDFs> = []
var selectedRow : NSInteger? // optional NSInteger

@available(iOS 8.0, *)
public func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: URL) {

    let myURL = url as URL
    print("import result : \(myURL)")

    do {
        let data = try Data(contentsOf: myURL)
        print("data=\(data)")

    }
    catch {/* error handling here */}

    let pathString = myURL.path // String path for PDF
    let PDFName = myURL.lastPathComponent // string for PDF Name

    if controller.documentPickerMode == UIDocumentPickerMode.import {
        DispatchQueue.main.async(){
            self.myPDFsArray.append(MyPDFs(fileTitle: pathString, fileName: PDFName))

            }
    }
    print (pathString)
    print (PDFName)
}

@available(iOS 8.0, *)
public func documentMenu(_ documentMenu:UIDocumentMenuViewController, didPickDocumentPicker documentPicker: UIDocumentPickerViewController) {
    documentPicker.delegate = self
    present(documentPicker, animated: true, completion: nil)
    //self.present(documentPicker, animated: true, completion: nil)
}
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
    print("we cancelled")

    dismiss(animated: true, completion: nil)
}

func documentMenuWasCancelled(documentMenu: UIDocumentMenuViewController) {
    print("menu got cancelled")
    documentMenu.dismiss(animated: true, completion: nil)

}

@IBAction func addButtonTapped(_ sender: Any){
    let importMenu = UIDocumentMenuViewController(documentTypes: [String(kUTTypePDF)], in: .import)
    importMenu.delegate = self
    importMenu.modalPresentationStyle = .formSheet
    self.present(importMenu, animated: true, completion: nil)
    //importMenu.addOption(withTitle: "Create New Document", image: nil, order: .first, handler: {print("New Doc Requested")})
    insertNewFileTitle()
}

func insertNewFileTitle(){
    if addFileTextField.text!.isEmpty {
        print("Add Video Text Field is empty")
    }
    pdfFiles.append(addFileTextField.text!)

    let indexPath = IndexPath(row: pdfFiles.count - 1, section: 0)
    tableView.beginUpdates()
    tableView.insertRows(at: [indexPath], with: .automatic) //can customize
    tableView.reloadData()//reload table
    tableView.endUpdates()
    addFileTextField.text = ""
    view.endEditing(true)
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return pdfFiles.count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let fileTitleCell = pdfFiles[indexPath.row]
    let cell = tableView.dequeueReusableCell(withIdentifier: "FileCell") as! FileCell
    cell.fileTitle.text = fileTitleCell

    if myPDFsArray.contains(MyPDFs(fileTitle: "fileTitle", fileName: "fileName")){

        cell.accessoryType = .checkmark
    }

    else{
        cell.accessoryType = .none
    }
    return cell
}


func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    selectedRow = indexPath.row // indexPath.row is the value of the row you tap on, starting from first 0
    tableView.deselectRow(at: indexPath, animated: true)
    if let cell = tableView.cellForRow(at: indexPath as IndexPath) {
        //selectedRow = indexPath.row // indexPath.row is the value of the row you tap on, starting from first 0
        //if myPDFsArray.contains(MyPDFs(fileTitle: "fileTitle", fileName: "fileName")){
        if cell.accessoryType == .checkmark{
            cell.accessoryType = .none
            //myPDFsArray.remove(at: MyPDFs.index(ofAccessibilityElement: NSObject.self))

            //print(MyPDFs.init(fileTitle: "fileTitle", fileName: "fileName"))
            print("")
            print(myPDFsArray[indexPath.row].fileTitle!)
            print("")
            print(myPDFsArray[indexPath.row].fileName!)
        }
        else{
            cell.accessoryType = .checkmark
            selectedRow = indexPath.row
            myPDFsArray.append(MyPDFs(fileTitle: "fileTitle", fileName: "fileName"))
            //print(MyPDFs.init(fileTitle: "fileTitle", fileName: "fileName"))
            print("")
            print(myPDFsArray[selectedRow!].fileTitle!)
            print("")
            print(myPDFsArray[selectedRow!].fileName!)
        }
        //}
    }
}

This next part is my button action to send the email

@IBAction func sendEmail(_sender: Any) {     
    let subject = "subject"
    let messageBody = "pdf file names"
    lblValidationMessage.isHidden = true

    let allPaths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
    let documentsDirectory = allPaths.first!
    let myPath = "/(MyPDFs[selectedRow].fileName)" //is it a forward slash or backslash? 
    let pathForFile = documentsDirectory.appending(myPath)
    print("")
    print(pathForFile)
    print("")

    guard let email = emailNew.text, emailNew.text?.characters.count != 0  else {
        lblValidationMessage.isHidden = false
        lblValidationMessage.text = "Please enter your email"
        return
    }

    if isValidEmail(emailNew: email) == false {
        lblValidationMessage.isHidden = false
        lblValidationMessage.text = "Please enter a valid email address"
        return
    }

    if( MFMailComposeViewController.canSendMail() ) {
        print("Can send email.")

        let mailComposer = MFMailComposeViewController()
        mailComposer.mailComposeDelegate = self

        //Set the subject and message of the email
        mailComposer.setToRecipients([email])
        mailComposer.setSubject(subject)
        mailComposer.setMessageBody(messageBody, isHTML: false)


        // Add attachment
        if let fileData = NSData(contentsOfFile: pathForFile) {
            print("file data loaded")
                mailComposer.addAttachmentData(fileData as Data, mimeType: "application/pdf", fileName: "fileName")
            }

        //this will compose and present mail to user"
        self.present(mailComposer, animated: true, completion: nil)
     }
    else {
        self.showSendMailErrorAlert()
    }
}
Carlos27
  • 71
  • 5

1 Answers1

0

I am not sure I understand this correctly. Why do you need to store the PDF inside the UITableView cell for this?

Why not just create a model like this:

class MyPDFs : NSObject
{
    var fileTitle : String?
    var fileName : String?

    override init(){}

    convenience init(fileTitle : String , fileName : String)
    {
        self.init()

        self.fileTitle = fileTitle
        self.fileName  = fileName
    }
}

And everytime you tap the addButton, create a new object, append it to an array of type MyPDFs, then reload the table.

var myPDFsArray : Array<MyPDFs> = []
myPDFsArray.append(MyPDFsObject)
myUITableView.reloadData()

This way you can get the PDF properties when you select the UITableView cell using indexPath.row and you can get the name and file path to attach to MailComposer.

daydr3am3r
  • 920
  • 4
  • 12
  • 31
  • thank you so much for the insight. I was able to create a model to store the PDF when it is imported. Right now I am having trouble figuring out how to get the PDF properties when I select a UITableView cell. I will update my code to show what I have so far. – Carlos27 Apr 19 '18 at 07:49
  • Since you are adding all your MyPDFsObjects to an array of the same tyoe, I usually try something like this in didSelectRowAt: print(myPDFsArray[indexPath.row].fileTitle) print(myPDFsArray[indexPath.row].fileName) myPDFsArray[indexPath.row] is basically an object of type MyPDFs. – daydr3am3r Apr 19 '18 at 08:44
  • thanks! both my url strings print when I select the cell. Although I cant figure out how to attach all the selected table rows to the MailComposer. Can i post my code here? or submit a new question? – Carlos27 Apr 20 '18 at 11:43
  • @Carlos27- You can post it here if you like. Not sure if it's against SO rules though. – daydr3am3r Apr 20 '18 at 11:48
  • ok I have updated my code to include my button action to send the email. Basically I have everything working fine except for the addAttachment function. How do I tell the addAttachment to include all the selected files that are checkmarked? I already have the urls paths formatted to string, so would I just have to call the tableview didSelectRowAt? – Carlos27 Apr 20 '18 at 12:07
  • I would create a NSInteger variable, say selectedRow, and in didSelectRowAtIndexPath I would give to that variable the value of indexPath.row and use that to get the file name from the array, instead of calling didSelectRowAt. let allPaths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true) let documentsDirectory = allPaths.first! let myPath = "/\(myPDFsArray[selectedRow].fileName)" let pathForFile = documentsDirectory.appending(myPath) – daydr3am3r Apr 20 '18 at 12:23
  • Then I would just attach the file using pathForFile: if let fileData = NSData(contentsOfFile: pathForLog) { mailComposer.addAttachmentData(fileData as Data, mimeType: "application/pdf", fileName: "MyPDF.pdf") }. Oh, and don't forget to import import MessageUI, import MobileCoreServices and to call the mailComposeController didFinishWith delegate to dismiss the MFMailCompose controller when sending the email. – daydr3am3r Apr 20 '18 at 12:25
  • thank you for your help again. I have updated my code again. I have implemented everything you mentioned but I am getting some issues. Did i create the NSInteger variable correctly? it is giving me an error saying "Use of unresolved identifier "indexPath". And is the rest of the addAttachment method look proper? This is a new area for me with Swift – Carlos27 Apr 20 '18 at 21:54
  • indexPath is visible in the tableView delegate methods, so the error makes sense, since there is no indexPath declared at that point. Declare selectedRow globaly: class MyViewController: UIViewController // this is your ViewController { var selectedRow : NSInteger? // optional NSInteger // rest of the code here } Now, in didSelectRowAtIndexPath, get the value of the indexPath. func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { selectedRow = indexPath.row // indexPath.row is the value of the row you tap on, starting from first 0 } – daydr3am3r Apr 21 '18 at 10:55
  • sweet I dont get any more errors but I do seem to have a strange warning: Instance method 'tableView(_:didSelectRowAtIndexPath:)' nearly matches optional requirement 'tableView(_:didSelectRowAt:)' of protocol 'UITableViewDelegate' – Carlos27 Apr 21 '18 at 20:18
  • I guess the didSelectRowAtIndexPath method I wrote above is for a lower version of Swift. You can delete the method and let Xcode generate it for you using auto-complete or simply click the warning. You should see an option to fix it. – daydr3am3r Apr 21 '18 at 21:50
  • Ive been spending days trying to figure the rest out on my own but I just cant seem to get the selected PDFs to attach to the mailcomposer. I can send out an email no problem but the pdfs dont show up as attachments. Xcode also wont let me include NSIndexPath in the didSelectRowAtIndexPath function. Maybe the newer version of Swift has a different method? I have updated my code again, but i am really stuck right on it right now. – Carlos27 Apr 25 '18 at 03:15
  • i also noticed that my "if let fileData = NSData(contentsOfFile: pathForFile)" method to add attachments doesnt catch in the function. Would it have something to do with not having any actual NSData when its ran? – Carlos27 Apr 25 '18 at 03:27
  • Try this: replace the if let fileData... with let fileData = try? Data(contentsOf: URL(fileURLWithPath: self.fileToSend!)) self.addAttachmentData(fileData!, mimeType: "application/pdf", fileName: self.fileToSend!). fileToSend must be, in all cases, the file path as a string and is should look like this (file path in simulator, in this case) : /Users/user/Library/Developer/CoreSimulator/Devices/F1D33D91-402D-41C3-A486-FD54534A2369/data/Containers/Data/Application/4C89E4D6-1ECB-4C20-B99D-7DB54F52B8ED/Documents/mypdf.pdf. And to answer you comment, it is / (Unix path), not \ (Windows path). – daydr3am3r Apr 25 '18 at 12:19
  • I keep getting an "Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value" error no matter what I try. THe error shows up on this line of "mailComposer.addAttachmentData(fileData!, mimeType: "application/pdf", fileName: "fileName")" – Carlos27 Apr 27 '18 at 20:01
  • fileName should not be a string with the value "fileName", it should be the file path AS STRING that should look like this: "/Users/user/Library/Developer/CoreSimulator/Devices/F1D33D91-402D-41C3-A486-FD54534A2369/data/Containers/Data/Application/4C89E4D6-1ECB-4C20-B99D-7DB54F52B8ED/Documents/mypdf.pdf". Try to see if fileData and fileName return a value and the value has the requested type. Also, check fileData. You are force unwrapping it without checking if this is possible. – daydr3am3r Apr 28 '18 at 20:27