Repository: appcoda/Print2PDF Branch: master Commit: 03146b16766d Files: 17 Total size: 78.7 KB Directory structure: gitextract_s4cb0nz0/ ├── .gitignore ├── Print2PDF/ │ ├── AddItemViewController.swift │ ├── AppDelegate.swift │ ├── Assets.xcassets/ │ │ └── AppIcon.appiconset/ │ │ └── Contents.json │ ├── Base.lproj/ │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── CreatorViewController.swift │ ├── CustomPrintPageRenderer.swift │ ├── Info.plist │ ├── InvoiceComposer.swift │ ├── InvoiceListViewController.swift │ └── PreviewViewController.swift ├── Print2PDF.xcodeproj/ │ └── project.pbxproj ├── README.md ├── invoice.html ├── last_item.html └── single_item.html ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ build xcuserdata *.mode* *pbxuser *.perspectivev3 project.xcworkspace *.xcuserstate *tmproj ~* .DS_Store *.orig ================================================ FILE: Print2PDF/AddItemViewController.swift ================================================ // // AddItemViewController.swift // Print2PDF // // Created by Gabriel Theodoropoulos on 14/06/16. // Copyright © 2016 Appcoda. All rights reserved. // import UIKit class AddItemViewController: UIViewController, UITextFieldDelegate { @IBOutlet weak var txtItemDescription: UITextField! @IBOutlet weak var txtPrice: UITextField! var currentTextfield: UITextField! var saveCompletionHandler: ((_ itemDescription: String, _ price: String) -> Void)! override func viewDidLoad() { super.viewDidLoad() // Set self as the delegate of the textfields. txtItemDescription.delegate = self txtPrice.delegate = self // Add a tap gesture recognizer to the view to dismiss the keyboard. let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(AddItemViewController.dismissKeyboard)) view.addGestureRecognizer(tapGestureRecognizer) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } /* // MARK: - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { // Get the new view controller using segue.destinationViewController. // Pass the selected object to the new view controller. } */ // MARK: Custom Methods func dismissKeyboard() { if currentTextfield != nil { currentTextfield.resignFirstResponder() currentTextfield = nil } } func presentAddItemViewControllerInViewController(originatingViewController: UIViewController, saveItemCompletionHandler: @escaping (_ itemDescription: String, _ price: String) -> Void) { saveCompletionHandler = saveItemCompletionHandler originatingViewController.navigationController?.pushViewController(self, animated: true) } // MARK: IBAction Methods @IBAction func saveItem(_ sender: AnyObject) { if (txtItemDescription.text?.characters.count)! > 0 && (txtPrice.text?.characters.count)! > 0 { if saveCompletionHandler != nil { // Call the save completion handler to pass the item description and the price back to the CreatorViewController object. saveCompletionHandler(_ : txtItemDescription.text!, _: txtPrice.text!) // Pop the view controller. _ = navigationController?.popViewController(animated: true) } } } // MARK: UITextFieldDelegate Methods func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { currentTextfield = textField return true } func textFieldShouldReturn(_ textField: UITextField) -> Bool { if textField == txtItemDescription { textField.resignFirstResponder() txtPrice.becomeFirstResponder() } return true } } ================================================ FILE: Print2PDF/AppDelegate.swift ================================================ // // AppDelegate.swift // Print2PDF // // Created by Gabriel Theodoropoulos on 14/06/16. // Copyright © 2016 Appcoda. All rights reserved. // import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? let currencyCode = "eur" func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool { // Override point for customization after application launch. return true } // MARK: Custom Methods class func getAppDelegate() -> AppDelegate { return UIApplication.shared.delegate as! AppDelegate } func getStringValueFormattedAsCurrency(value: String) -> String { let numberFormatter = NumberFormatter() numberFormatter.numberStyle = NumberFormatter.Style.currency numberFormatter.currencyCode = currencyCode numberFormatter.maximumFractionDigits = 2 let formattedValue = numberFormatter.string(from: NumberFormatter().number(from: value)!) return formattedValue! } func getDocDir() -> String { return NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] } } ================================================ FILE: Print2PDF/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "iphone", "size" : "29x29", "scale" : "2x" }, { "idiom" : "iphone", "size" : "29x29", "scale" : "3x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "2x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "3x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "2x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "3x" }, { "idiom" : "ipad", "size" : "29x29", "scale" : "1x" }, { "idiom" : "ipad", "size" : "29x29", "scale" : "2x" }, { "idiom" : "ipad", "size" : "40x40", "scale" : "1x" }, { "idiom" : "ipad", "size" : "40x40", "scale" : "2x" }, { "idiom" : "ipad", "size" : "76x76", "scale" : "1x" }, { "idiom" : "ipad", "size" : "76x76", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Print2PDF/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: Print2PDF/Base.lproj/Main.storyboard ================================================ ================================================ FILE: Print2PDF/CreatorViewController.swift ================================================ // // CreatorViewController.swift // Print2PDF // // Created by Gabriel Theodoropoulos on 14/06/16. // Copyright © 2016 Appcoda. All rights reserved. // import UIKit class CreatorViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { @IBOutlet weak var tblInvoiceItems: UITableView! @IBOutlet weak var bbiTotal: UIBarButtonItem! @IBOutlet weak var tvRecipientInfo: UITextView! var invoiceNumber: String! var items: [[String: String]]! var totalAmount = "0" var saveCompletionHandler: ((_ invoiceNumber: String, _ recipientInfo: String, _ totalAmount: String, _ items: [[String: String]]) -> Void)! var firstAppeared = true var nextNumberAsString: String! override func viewDidLoad() { super.viewDidLoad() // Set self as the delegate and the datasource of the tableview. tblInvoiceItems.delegate = self tblInvoiceItems.dataSource = self // Add a tap gesture recognizer to the view to dismiss the keyboard. let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(AddItemViewController.dismissKeyboard)) view.addGestureRecognizer(tapGestureRecognizer) } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) if firstAppeared { determineInvoiceNumber() displayTotalAmount() firstAppeared = false } } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } /* // MARK: - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { // Get the new view controller using segue.destinationViewController. // Pass the selected object to the new view controller. } */ // MARK: IBAction Methods @IBAction func addItem(_ sender: AnyObject) { let addItemViewController = storyboard?.instantiateViewController(withIdentifier: "idAddItem") as! AddItemViewController addItemViewController.presentAddItemViewControllerInViewController(originatingViewController: self) { (itemDescription, price) in DispatchQueue.main.async { if self.items == nil { self.items = [[String: String]]() } self.items.append(["item": itemDescription, "price": price]) self.tblInvoiceItems.reloadData() self.displayTotalAmount() } } } @IBAction func saveInvoice(_ sender: AnyObject) { if saveCompletionHandler != nil { if nextNumberAsString != nil { UserDefaults.standard.set(nextNumberAsString, forKey: "nextInvoiceNumber") } else { UserDefaults.standard.set("002", forKey: "nextInvoiceNumber") } saveCompletionHandler(_: invoiceNumber, _: tvRecipientInfo.text, _: bbiTotal.title!, _: items) _ = navigationController?.popViewController(animated: true) } } // MARK: Custom Methods func presentCreatorViewControllerInViewController(originalViewController: UIViewController, saveCompletionHandler: @escaping (_ invoiceNumber: String, _ recipientInfo: String, _ totalAmount: String, _ items: [[String: String]]) -> Void) { self.saveCompletionHandler = saveCompletionHandler originalViewController.navigationController?.pushViewController(self, animated: true) } func determineInvoiceNumber() { // Get the invoice number from the user defaults if exists. if let nextInvoiceNumber = UserDefaults.standard.object(forKey: "nextInvoiceNumber") { invoiceNumber = nextInvoiceNumber as! String // Save the next invoice number to the user defaults. let nextNumber = Int(nextInvoiceNumber as! String)! + 1 if nextNumber < 10 { nextNumberAsString = "00\(nextNumber)" } else if nextNumber < 100 { nextNumberAsString = "0\(nextNumber)" } else { nextNumberAsString = "\(nextNumber)" } } else { // Specify the first invoice number. invoiceNumber = "001" } // Set the invoice number to the navigation bar's title. navigationItem.title = invoiceNumber } func calculateTotalAmount() { var total: Double = 0.0 if items != nil { for invoiceItem in items { let priceAsNumber = NumberFormatter().number(from: invoiceItem["price"]!) total += Double(priceAsNumber!) } } totalAmount = AppDelegate.getAppDelegate().getStringValueFormattedAsCurrency(value: "\(total)") } func displayTotalAmount() { calculateTotalAmount() bbiTotal.title = totalAmount } func dismissKeyboard() { if tvRecipientInfo.isFirstResponder { tvRecipientInfo.resignFirstResponder() } } // MARK: UITableView Delegate and Datasource Methods func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return (items != nil) ? items.count : 0 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { var cell: UITableViewCell! = tableView.dequeueReusableCell(withIdentifier: "itemCell", for: indexPath as IndexPath) if cell == nil { cell = UITableViewCell(style: UITableViewCellStyle.value1, reuseIdentifier: "itemCell") } cell.textLabel?.text = items[indexPath.row]["item"] cell.detailTextLabel?.text = AppDelegate.getAppDelegate().getStringValueFormattedAsCurrency(value: items[indexPath.row]["price"]!) return cell } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 60.0 } func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { if editingStyle == UITableViewCellEditingStyle.delete { items.remove(at: indexPath.row) tblInvoiceItems.reloadData() displayTotalAmount() } } } ================================================ FILE: Print2PDF/CustomPrintPageRenderer.swift ================================================ // // CustomPrintPageRenderer.swift // Print2PDF // // Created by Gabriel Theodoropoulos on 24/06/16. // Copyright © 2016 Appcoda. All rights reserved. // import UIKit class CustomPrintPageRenderer: UIPrintPageRenderer { let A4PageWidth: CGFloat = 595.2 let A4PageHeight: CGFloat = 841.8 override init() { super.init() // Specify the frame of the A4 page. let pageFrame = CGRect(x: 0.0, y: 0.0, width: A4PageWidth, height: A4PageHeight) // Set the page frame. self.setValue(NSValue(cgRect: pageFrame), forKey: "paperRect") // Set the horizontal and vertical insets (that's optional). // self.setValue(NSValue(CGRect: pageFrame), forKey: "printableRect") self.setValue(NSValue(cgRect: pageFrame.insetBy(dx: 10.0, dy: 10.0)), forKey: "printableRect") self.headerHeight = 50.0 self.footerHeight = 50.0 } override func drawHeaderForPage(at pageIndex: Int, in headerRect: CGRect) { // Specify the header text. let headerText: NSString = "Invoice" // Set the desired font. let font = UIFont(name: "AmericanTypewriter-Bold", size: 30.0) // Specify some text attributes we want to apply to the header text. let textAttributes = [NSFontAttributeName: font!, NSForegroundColorAttributeName: UIColor(red: 243.0/255, green: 82.0/255.0, blue: 30.0/255.0, alpha: 1.0), NSKernAttributeName: 7.5] as [String : Any] // Calculate the text size. let textSize = getTextSize(text: headerText as String, font: nil, textAttributes: textAttributes as [String : AnyObject]!) // Determine the offset to the right side. let offsetX: CGFloat = 20.0 // Specify the point that the text drawing should start from. let pointX = headerRect.size.width - textSize.width - offsetX let pointY = headerRect.size.height/2 - textSize.height/2 // Draw the header text. headerText.draw(at: CGPoint(x: pointX, y: pointY), withAttributes: textAttributes) } override func drawFooterForPage(at pageIndex: Int, in footerRect: CGRect) { let footerText: NSString = "Thank you!" let font = UIFont(name: "Noteworthy-Bold", size: 14.0) let textSize = getTextSize(text: footerText as String, font: font!) let centerX = footerRect.size.width/2 - textSize.width/2 let centerY = footerRect.origin.y + self.footerHeight/2 - textSize.height/2 let attributes = [NSFontAttributeName: font!, NSForegroundColorAttributeName: UIColor(red: 205.0/255.0, green: 205.0/255.0, blue: 205.0/255, alpha: 1.0)] footerText.draw(at: CGPoint(x: centerX, y: centerY), withAttributes: attributes) // Draw a horizontal line. let lineOffsetX: CGFloat = 20.0 let context = UIGraphicsGetCurrentContext() context!.setStrokeColor(red: 205.0/255.0, green: 205.0/255.0, blue: 205.0/255, alpha: 1.0) context!.move(to: CGPoint(x: lineOffsetX, y: footerRect.origin.y)) context!.addLine(to: CGPoint(x: footerRect.size.width - lineOffsetX, y: footerRect.origin.y)) context!.strokePath() } func getTextSize(text: String, font: UIFont!, textAttributes: [String: AnyObject]! = nil) -> CGSize { let testLabel = UILabel(frame: CGRect(x: 0.0, y: 0.0, width: self.paperRect.size.width, height: footerHeight)) if let attributes = textAttributes { testLabel.attributedText = NSAttributedString(string: text, attributes: attributes) } else { testLabel.text = text testLabel.font = font! } testLabel.sizeToFit() return testLabel.frame.size } } ================================================ FILE: Print2PDF/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight NSAppTransportSecurity NSAllowsArbitraryLoads ================================================ FILE: Print2PDF/InvoiceComposer.swift ================================================ // // InvoiceComposer.swift // Print2PDF // // Created by Gabriel Theodoropoulos on 23/06/16. // Copyright © 2016 Appcoda. All rights reserved. // import UIKit class InvoiceComposer: NSObject { let pathToInvoiceHTMLTemplate = Bundle.main.path(forResource: "invoice", ofType: "html") let pathToSingleItemHTMLTemplate = Bundle.main.path(forResource: "single_item", ofType: "html") let pathToLastItemHTMLTemplate = Bundle.main.path(forResource: "last_item", ofType: "html") let senderInfo = "Gabriel Theodoropoulos
123 Somewhere Str.
10000 - MyCity
MyCountry" let dueDate = "" let paymentMethod = "Wire Transfer" let logoImageURL = "http://www.appcoda.com/wp-content/uploads/2015/12/blog-logo-dark-400.png" var invoiceNumber: String! var pdfFilename: String! override init() { super.init() } func renderInvoice(invoiceNumber: String, invoiceDate: String, recipientInfo: String, items: [[String: String]], totalAmount: String) -> String! { // Store the invoice number for future use. self.invoiceNumber = invoiceNumber do { // Load the invoice HTML template code into a String variable. var HTMLContent = try String(contentsOfFile: pathToInvoiceHTMLTemplate!) // Replace all the placeholders with real values except for the items. // The logo image. HTMLContent = HTMLContent.replacingOccurrences(of: "#LOGO_IMAGE#", with: logoImageURL) // Invoice number. HTMLContent = HTMLContent.replacingOccurrences(of: "#INVOICE_NUMBER#", with: invoiceNumber) // Invoice date. HTMLContent = HTMLContent.replacingOccurrences(of: "#INVOICE_DATE#", with: invoiceDate) // Due date (we leave it blank by default). HTMLContent = HTMLContent.replacingOccurrences(of: "#DUE_DATE#", with: dueDate) // Sender info. HTMLContent = HTMLContent.replacingOccurrences(of: "#SENDER_INFO#", with: senderInfo) // Recipient info. HTMLContent = HTMLContent.replacingOccurrences(of: "#RECIPIENT_INFO#", with: recipientInfo.replacingOccurrences(of: "\n", with: "
")) // Payment method. HTMLContent = HTMLContent.replacingOccurrences(of: "#PAYMENT_METHOD#", with: paymentMethod) // Total amount. HTMLContent = HTMLContent.replacingOccurrences(of: "#TOTAL_AMOUNT#", with: totalAmount) // The invoice items will be added by using a loop. var allItems = "" // For all the items except for the last one we'll use the "single_item.html" template. // For the last one we'll use the "last_item.html" template. for i in 0.. NSData! { let data = NSMutableData() UIGraphicsBeginPDFContextToData(data, CGRect.zero, nil) for i in 0.. String { let dateFormatter = DateFormatter() dateFormatter.dateStyle = DateFormatter.Style.medium return dateFormatter.string(from: NSDate() as Date) } // MARK: UITableView Delegate and Datasource Methods func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return (invoices != nil) ? invoices.count : 0 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { var cell: UITableViewCell! = tableView.dequeueReusableCell(withIdentifier: "invoiceCell", for: indexPath as IndexPath) if cell == nil { cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "invoiceCell") } cell.textLabel?.text = "\(invoices[indexPath.row]["invoiceNumber"] as! String) - \(invoices[indexPath.row]["invoiceDate"] as! String) - \(invoices[indexPath.row]["totalAmount"] as! String)" return cell } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 60.0 } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { selectedInvoiceIndex = indexPath.row performSegue(withIdentifier: "idSeguePresentPreview", sender: self) } func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { if editingStyle == UITableViewCellEditingStyle.delete { invoices.remove(at: indexPath.row) tblInvoices.reloadData() UserDefaults.standard.set(self.invoices, forKey: "invoices") } } } ================================================ FILE: Print2PDF/PreviewViewController.swift ================================================ // // PreviewViewController.swift // Print2PDF // // Created by Gabriel Theodoropoulos on 14/06/16. // Copyright © 2016 Appcoda. All rights reserved. // import UIKit import MessageUI class PreviewViewController: UIViewController { @IBOutlet weak var webPreview: UIWebView! var invoiceInfo: [String: AnyObject]! var invoiceComposer: InvoiceComposer! var HTMLContent: String! override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) createInvoiceAsHTML() } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } /* // MARK: - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { // Get the new view controller using segue.destinationViewController. // Pass the selected object to the new view controller. } */ // MARK: IBAction Methods @IBAction func exportToPDF(_ sender: AnyObject) { invoiceComposer.exportHTMLContentToPDF(HTMLContent: HTMLContent) showOptionsAlert() } // MARK: Custom Methods func createInvoiceAsHTML() { invoiceComposer = InvoiceComposer() if let invoiceHTML = invoiceComposer.renderInvoice(invoiceNumber: invoiceInfo["invoiceNumber"] as! String, invoiceDate: invoiceInfo["invoiceDate"] as! String, recipientInfo: invoiceInfo["recipientInfo"] as! String, items: invoiceInfo["items"] as! [[String: String]], totalAmount: invoiceInfo["totalAmount"] as! String) { webPreview.loadHTMLString(invoiceHTML, baseURL: NSURL(string: invoiceComposer.pathToInvoiceHTMLTemplate!)! as URL) HTMLContent = invoiceHTML } } func showOptionsAlert() { let alertController = UIAlertController(title: "Yeah!", message: "Your invoice has been successfully printed to a PDF file.\n\nWhat do you want to do now?", preferredStyle: UIAlertControllerStyle.alert) let actionPreview = UIAlertAction(title: "Preview it", style: UIAlertActionStyle.default) { (action) in if let filename = self.invoiceComposer.pdfFilename, let url = URL(string: filename) { let request = URLRequest(url: url) self.webPreview.loadRequest(request) } } let actionEmail = UIAlertAction(title: "Send by Email", style: UIAlertActionStyle.default) { (action) in DispatchQueue.main.async { self.sendEmail() } } let actionNothing = UIAlertAction(title: "Nothing", style: UIAlertActionStyle.default) { (action) in } alertController.addAction(actionPreview) alertController.addAction(actionEmail) alertController.addAction(actionNothing) present(alertController, animated: true, completion: nil) } func sendEmail() { if MFMailComposeViewController.canSendMail() { let mailComposeViewController = MFMailComposeViewController() mailComposeViewController.setSubject("Invoice") mailComposeViewController.addAttachmentData(NSData(contentsOfFile: invoiceComposer.pdfFilename)! as Data, mimeType: "application/pdf", fileName: "Invoice") present(mailComposeViewController, animated: true, completion: nil) } } } ================================================ FILE: Print2PDF.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ C04F22BD1D1C32280090D56B /* InvoiceComposer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04F22BC1D1C32280090D56B /* InvoiceComposer.swift */; }; C091D2511D1D40CA008DBE68 /* CustomPrintPageRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C091D2501D1D40CA008DBE68 /* CustomPrintPageRenderer.swift */; }; C0C922211D0FFDE5000D4607 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C922201D0FFDE5000D4607 /* AppDelegate.swift */; }; C0C922261D0FFDE5000D4607 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C0C922241D0FFDE5000D4607 /* Main.storyboard */; }; C0C922281D0FFDE6000D4607 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C0C922271D0FFDE6000D4607 /* Assets.xcassets */; }; C0C9222B1D0FFDE6000D4607 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C0C922291D0FFDE6000D4607 /* LaunchScreen.storyboard */; }; C0C922331D0FFE1B000D4607 /* InvoiceListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C922321D0FFE1B000D4607 /* InvoiceListViewController.swift */; }; C0C922351D0FFE67000D4607 /* CreatorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C922341D0FFE67000D4607 /* CreatorViewController.swift */; }; C0C922371D1002C4000D4607 /* AddItemViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C922361D1002C4000D4607 /* AddItemViewController.swift */; }; C0C9223C1D10585C000D4607 /* invoice.html in Resources */ = {isa = PBXBuildFile; fileRef = C0C922391D10585C000D4607 /* invoice.html */; }; C0C9223D1D10585C000D4607 /* last_item.html in Resources */ = {isa = PBXBuildFile; fileRef = C0C9223A1D10585C000D4607 /* last_item.html */; }; C0C9223E1D10585C000D4607 /* single_item.html in Resources */ = {isa = PBXBuildFile; fileRef = C0C9223B1D10585C000D4607 /* single_item.html */; }; C0C922401D105886000D4607 /* PreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C9223F1D105886000D4607 /* PreviewViewController.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ C04F22BC1D1C32280090D56B /* InvoiceComposer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InvoiceComposer.swift; sourceTree = ""; }; C091D2501D1D40CA008DBE68 /* CustomPrintPageRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomPrintPageRenderer.swift; sourceTree = ""; }; C0C9221D1D0FFDE5000D4607 /* Print2PDF.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Print2PDF.app; sourceTree = BUILT_PRODUCTS_DIR; }; C0C922201D0FFDE5000D4607 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; C0C922251D0FFDE5000D4607 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; C0C922271D0FFDE6000D4607 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; C0C9222A1D0FFDE6000D4607 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; C0C9222C1D0FFDE6000D4607 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; C0C922321D0FFE1B000D4607 /* InvoiceListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InvoiceListViewController.swift; sourceTree = ""; }; C0C922341D0FFE67000D4607 /* CreatorViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreatorViewController.swift; sourceTree = ""; }; C0C922361D1002C4000D4607 /* AddItemViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddItemViewController.swift; sourceTree = ""; }; C0C922391D10585C000D4607 /* invoice.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = invoice.html; sourceTree = ""; }; C0C9223A1D10585C000D4607 /* last_item.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = last_item.html; sourceTree = ""; }; C0C9223B1D10585C000D4607 /* single_item.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = single_item.html; sourceTree = ""; }; C0C9223F1D105886000D4607 /* PreviewViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreviewViewController.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ C0C9221A1D0FFDE5000D4607 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ C0C922141D0FFDE5000D4607 = { isa = PBXGroup; children = ( C0C922381D105847000D4607 /* HTML Templates */, C0C9221F1D0FFDE5000D4607 /* Print2PDF */, C0C9221E1D0FFDE5000D4607 /* Products */, ); sourceTree = ""; }; C0C9221E1D0FFDE5000D4607 /* Products */ = { isa = PBXGroup; children = ( C0C9221D1D0FFDE5000D4607 /* Print2PDF.app */, ); name = Products; sourceTree = ""; }; C0C9221F1D0FFDE5000D4607 /* Print2PDF */ = { isa = PBXGroup; children = ( C0C922201D0FFDE5000D4607 /* AppDelegate.swift */, C0C922321D0FFE1B000D4607 /* InvoiceListViewController.swift */, C0C922341D0FFE67000D4607 /* CreatorViewController.swift */, C0C922361D1002C4000D4607 /* AddItemViewController.swift */, C0C9223F1D105886000D4607 /* PreviewViewController.swift */, C04F22BC1D1C32280090D56B /* InvoiceComposer.swift */, C091D2501D1D40CA008DBE68 /* CustomPrintPageRenderer.swift */, C0C922241D0FFDE5000D4607 /* Main.storyboard */, C0C922271D0FFDE6000D4607 /* Assets.xcassets */, C0C922291D0FFDE6000D4607 /* LaunchScreen.storyboard */, C0C9222C1D0FFDE6000D4607 /* Info.plist */, ); path = Print2PDF; sourceTree = ""; }; C0C922381D105847000D4607 /* HTML Templates */ = { isa = PBXGroup; children = ( C0C922391D10585C000D4607 /* invoice.html */, C0C9223A1D10585C000D4607 /* last_item.html */, C0C9223B1D10585C000D4607 /* single_item.html */, ); name = "HTML Templates"; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ C0C9221C1D0FFDE5000D4607 /* Print2PDF */ = { isa = PBXNativeTarget; buildConfigurationList = C0C9222F1D0FFDE6000D4607 /* Build configuration list for PBXNativeTarget "Print2PDF" */; buildPhases = ( C0C922191D0FFDE5000D4607 /* Sources */, C0C9221A1D0FFDE5000D4607 /* Frameworks */, C0C9221B1D0FFDE5000D4607 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = Print2PDF; productName = Print2PDF; productReference = C0C9221D1D0FFDE5000D4607 /* Print2PDF.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ C0C922151D0FFDE5000D4607 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0730; LastUpgradeCheck = 0730; ORGANIZATIONNAME = Appcoda; TargetAttributes = { C0C9221C1D0FFDE5000D4607 = { CreatedOnToolsVersion = 7.3; LastSwiftMigration = 0800; }; }; }; buildConfigurationList = C0C922181D0FFDE5000D4607 /* Build configuration list for PBXProject "Print2PDF" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = C0C922141D0FFDE5000D4607; productRefGroup = C0C9221E1D0FFDE5000D4607 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( C0C9221C1D0FFDE5000D4607 /* Print2PDF */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ C0C9221B1D0FFDE5000D4607 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( C0C9223C1D10585C000D4607 /* invoice.html in Resources */, C0C9223D1D10585C000D4607 /* last_item.html in Resources */, C0C9222B1D0FFDE6000D4607 /* LaunchScreen.storyboard in Resources */, C0C922281D0FFDE6000D4607 /* Assets.xcassets in Resources */, C0C922261D0FFDE5000D4607 /* Main.storyboard in Resources */, C0C9223E1D10585C000D4607 /* single_item.html in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ C0C922191D0FFDE5000D4607 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( C0C922371D1002C4000D4607 /* AddItemViewController.swift in Sources */, C0C922351D0FFE67000D4607 /* CreatorViewController.swift in Sources */, C091D2511D1D40CA008DBE68 /* CustomPrintPageRenderer.swift in Sources */, C0C922211D0FFDE5000D4607 /* AppDelegate.swift in Sources */, C0C922401D105886000D4607 /* PreviewViewController.swift in Sources */, C0C922331D0FFE1B000D4607 /* InvoiceListViewController.swift in Sources */, C04F22BD1D1C32280090D56B /* InvoiceComposer.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ C0C922241D0FFDE5000D4607 /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( C0C922251D0FFDE5000D4607 /* Base */, ); name = Main.storyboard; sourceTree = ""; }; C0C922291D0FFDE6000D4607 /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( C0C9222A1D0FFDE6000D4607 /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ C0C9222D1D0FFDE6000D4607 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.3; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; C0C9222E1D0FFDE6000D4607 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.3; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; C0C922301D0FFDE6000D4607 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = Print2PDF/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.appcoda.Print2PDF; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 3.0; }; name = Debug; }; C0C922311D0FFDE6000D4607 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = Print2PDF/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.appcoda.Print2PDF; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 3.0; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ C0C922181D0FFDE5000D4607 /* Build configuration list for PBXProject "Print2PDF" */ = { isa = XCConfigurationList; buildConfigurations = ( C0C9222D1D0FFDE6000D4607 /* Debug */, C0C9222E1D0FFDE6000D4607 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; C0C9222F1D0FFDE6000D4607 /* Build configuration list for PBXNativeTarget "Print2PDF" */ = { isa = XCConfigurationList; buildConfigurations = ( C0C922301D0FFDE6000D4607 /* Debug */, C0C922311D0FFDE6000D4607 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = C0C922151D0FFDE5000D4607 /* Project object */; } ================================================ FILE: README.md ================================================ # Print2PDF in iOS It is an invoice demo showing you how to create PDF documents using HTML templates in iOS. For full tutorial, please refer to the link below: http://www.appcoda.com/pdf-generation-ios/ ================================================ FILE: invoice.html ================================================ A simple, clean, and responsive HTML invoice template
#ITEMS#
Invoice #: #INVOICE_NUMBER#
#INVOICE_DATE#
#DUE_DATE#
#SENDER_INFO# #RECIPIENT_INFO#
Payment Method
#PAYMENT_METHOD#
Item Price

Total: #TOTAL_AMOUNT#
================================================ FILE: last_item.html ================================================ #ITEM_DESC# #PRICE# ================================================ FILE: single_item.html ================================================ #ITEM_DESC# #PRICE#