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
================================================
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="8150" systemVersion="15A204g" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="8122"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/>
<viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<animations/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
</document>
================================================
FILE: Print2PDF/Base.lproj/Main.storyboard
================================================
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10116" systemVersion="15A284" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="orp-cW-drb">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
</dependencies>
<scenes>
<!--Invoice List View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController automaticallyAdjustsScrollViewInsets="NO" id="BYZ-38-t0r" customClass="InvoiceListViewController" customModule="Print2PDF" customModuleProvider="target" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="yo6-cb-KTQ">
<rect key="frame" x="0.0" y="64" width="600" height="536"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="invoiceCell" id="xTZ-Rv-Ddy">
<rect key="frame" x="0.0" y="28" width="600" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="xTZ-Rv-Ddy" id="PXz-uD-OPb">
<rect key="frame" x="0.0" y="0.0" width="567" height="43"/>
<autoresizingMask key="autoresizingMask"/>
</tableViewCellContentView>
</tableViewCell>
</prototypes>
</tableView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="wfy-db-euE" firstAttribute="top" secondItem="yo6-cb-KTQ" secondAttribute="bottom" id="Chv-4Y-uTT"/>
<constraint firstItem="yo6-cb-KTQ" firstAttribute="top" secondItem="y3c-jy-aDJ" secondAttribute="bottom" id="TWC-RL-ia1"/>
<constraint firstItem="yo6-cb-KTQ" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leadingMargin" constant="-20" id="eSB-Lj-bSC"/>
<constraint firstAttribute="trailingMargin" secondItem="yo6-cb-KTQ" secondAttribute="trailing" constant="-20" id="qdg-Ta-Ix6"/>
</constraints>
</view>
<navigationItem key="navigationItem" id="82B-eR-Fgg">
<barButtonItem key="rightBarButtonItem" systemItem="add" id="HlV-Wt-A6c">
<connections>
<action selector="createInvoice:" destination="BYZ-38-t0r" id="DIn-ql-M0R"/>
</connections>
</barButtonItem>
</navigationItem>
<connections>
<outlet property="tblInvoices" destination="yo6-cb-KTQ" id="VT1-iO-yUa"/>
<segue destination="WAP-F9-I3Z" kind="show" identifier="idSeguePresentCreator" id="xnN-U9-ldL"/>
<segue destination="N0b-RX-mJe" kind="show" identifier="idSeguePresentPreview" id="8Ur-XB-KzF"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="620" y="165"/>
</scene>
<!--Creator View Controller-->
<scene sceneID="IyW-Sz-TpM">
<objects>
<viewController storyboardIdentifier="idCreateInvoice" automaticallyAdjustsScrollViewInsets="NO" id="WAP-F9-I3Z" customClass="CreatorViewController" customModule="Print2PDF" customModuleProvider="target" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="mbN-b8-fyX"/>
<viewControllerLayoutGuide type="bottom" id="eoC-r6-fOw"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="0rY-IY-DVi">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Recipient Info:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="AED-lz-kBC">
<rect key="frame" x="20" y="72" width="189" height="21"/>
<constraints>
<constraint firstAttribute="width" constant="189" id="qkx-kb-WQK"/>
<constraint firstAttribute="height" constant="21" id="uuX-L5-4aD"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Invoice Items:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Yhh-ef-Rot">
<rect key="frame" x="20" y="234" width="189" height="21"/>
<constraints>
<constraint firstAttribute="height" constant="21" id="IEk-je-NhF"/>
<constraint firstAttribute="width" constant="189" id="zUn-5G-6VI"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<toolbar opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="NTc-VZ-xvo">
<rect key="frame" x="0.0" y="556" width="600" height="44"/>
<constraints>
<constraint firstAttribute="height" constant="44" id="pVd-Rv-hTz"/>
</constraints>
<items>
<barButtonItem title="Total:" id="mVA-Is-NsK"/>
<barButtonItem style="plain" systemItem="flexibleSpace" id="bk5-X4-3Bb"/>
<barButtonItem systemItem="add" id="gzY-DJ-XQ1">
<connections>
<action selector="addItem:" destination="WAP-F9-I3Z" id="v5Q-br-cSq"/>
</connections>
</barButtonItem>
</items>
</toolbar>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="Yd7-HA-YJG">
<rect key="frame" x="20" y="255" width="560" height="290"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="none" indentationWidth="10" reuseIdentifier="itemCell" textLabel="bkx-jD-jC3" detailTextLabel="JDk-6K-4TP" style="IBUITableViewCellStyleValue1" id="0Gm-5s-XdY">
<rect key="frame" x="0.0" y="28" width="560" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="0Gm-5s-XdY" id="AS7-KW-yDD">
<rect key="frame" x="0.0" y="0.0" width="560" height="43"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="bkx-jD-jC3">
<rect key="frame" x="15" y="12" width="32" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Detail" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="JDk-6K-4TP">
<rect key="frame" x="503" y="12" width="42" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<color key="textColor" red="0.55686274509803924" green="0.55686274509803924" blue="0.57647058823529407" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
</tableViewCell>
</prototypes>
</tableView>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="9su-63-jEX">
<rect key="frame" x="20" y="98" width="560" height="128"/>
<color key="backgroundColor" red="0.90196079019999997" green="0.90196079019999997" blue="0.90196079019999997" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="height" constant="128" id="4pq-Bv-hmX"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
</textView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="AED-lz-kBC" firstAttribute="top" secondItem="mbN-b8-fyX" secondAttribute="bottom" constant="8" id="EdG-mS-EpR"/>
<constraint firstItem="Yhh-ef-Rot" firstAttribute="top" secondItem="9su-63-jEX" secondAttribute="bottom" constant="8" id="G4D-xs-fEX"/>
<constraint firstItem="NTc-VZ-xvo" firstAttribute="leading" secondItem="0rY-IY-DVi" secondAttribute="leadingMargin" constant="-20" id="Gmt-KX-orE"/>
<constraint firstItem="NTc-VZ-xvo" firstAttribute="top" secondItem="Yd7-HA-YJG" secondAttribute="bottom" constant="11" id="LUb-vb-dHt"/>
<constraint firstItem="9su-63-jEX" firstAttribute="leading" secondItem="0rY-IY-DVi" secondAttribute="leadingMargin" id="OSO-kp-dze"/>
<constraint firstItem="AED-lz-kBC" firstAttribute="leading" secondItem="0rY-IY-DVi" secondAttribute="leadingMargin" id="TjL-y6-B5Z"/>
<constraint firstItem="Yd7-HA-YJG" firstAttribute="leading" secondItem="0rY-IY-DVi" secondAttribute="leadingMargin" id="Xbe-4B-Qsx"/>
<constraint firstItem="eoC-r6-fOw" firstAttribute="top" secondItem="NTc-VZ-xvo" secondAttribute="bottom" id="XoA-1k-g2T"/>
<constraint firstAttribute="trailingMargin" secondItem="NTc-VZ-xvo" secondAttribute="trailing" constant="-20" id="eqb-FJ-agb"/>
<constraint firstItem="Yhh-ef-Rot" firstAttribute="leading" secondItem="0rY-IY-DVi" secondAttribute="leadingMargin" id="inn-Qp-M5x"/>
<constraint firstItem="Yd7-HA-YJG" firstAttribute="trailing" secondItem="0rY-IY-DVi" secondAttribute="trailingMargin" id="jX8-2D-1IG"/>
<constraint firstAttribute="trailingMargin" secondItem="9su-63-jEX" secondAttribute="trailing" id="jpa-41-Frm"/>
<constraint firstItem="Yd7-HA-YJG" firstAttribute="top" secondItem="Yhh-ef-Rot" secondAttribute="bottom" id="q7N-hQ-R41"/>
<constraint firstItem="9su-63-jEX" firstAttribute="top" secondItem="mbN-b8-fyX" secondAttribute="bottom" constant="34" id="rtb-aJ-17o"/>
</constraints>
</view>
<navigationItem key="navigationItem" id="MmZ-zm-mew">
<barButtonItem key="rightBarButtonItem" systemItem="save" id="8M2-R1-ngz">
<connections>
<action selector="saveInvoice:" destination="WAP-F9-I3Z" id="q8j-YV-y1H"/>
</connections>
</barButtonItem>
</navigationItem>
<connections>
<outlet property="bbiTotal" destination="mVA-Is-NsK" id="1tc-u8-vOG"/>
<outlet property="tblInvoiceItems" destination="Yd7-HA-YJG" id="Yq2-vp-ipx"/>
<outlet property="tvRecipientInfo" destination="9su-63-jEX" id="7L7-OV-Y9x"/>
<segue destination="Kxm-nt-nUb" kind="show" identifier="idSeguePresentAddItem" id="IjY-w6-3a1"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="x7m-3a-TtX" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1439" y="-293"/>
</scene>
<!--Preview View Controller-->
<scene sceneID="MbO-WJ-6zz">
<objects>
<viewController id="N0b-RX-mJe" customClass="PreviewViewController" customModule="Print2PDF" customModuleProvider="target" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="XdU-Mo-vKd"/>
<viewControllerLayoutGuide type="bottom" id="LeA-95-WR8"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="1dj-n8-uBZ">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<webView contentMode="scaleToFill" scalesPageToFit="YES" translatesAutoresizingMaskIntoConstraints="NO" id="3Ca-ak-dqG">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<color key="backgroundColor" red="0.36078431370000003" green="0.38823529410000002" blue="0.4039215686" alpha="1" colorSpace="deviceRGB"/>
</webView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="LeA-95-WR8" firstAttribute="top" secondItem="3Ca-ak-dqG" secondAttribute="bottom" id="RQI-Qq-5cJ"/>
<constraint firstItem="3Ca-ak-dqG" firstAttribute="leading" secondItem="1dj-n8-uBZ" secondAttribute="leadingMargin" constant="-20" id="Soc-qi-4Dh"/>
<constraint firstItem="3Ca-ak-dqG" firstAttribute="top" secondItem="XdU-Mo-vKd" secondAttribute="bottom" constant="-64" id="fYh-CT-2PF"/>
<constraint firstAttribute="trailingMargin" secondItem="3Ca-ak-dqG" secondAttribute="trailing" constant="-20" id="lJf-33-QOg"/>
</constraints>
</view>
<navigationItem key="navigationItem" id="Iqt-u4-Sd2">
<barButtonItem key="rightBarButtonItem" title="PDF" id="UXX-tS-GQe">
<connections>
<action selector="exportToPDF:" destination="N0b-RX-mJe" id="2MI-xz-fXV"/>
</connections>
</barButtonItem>
</navigationItem>
<connections>
<outlet property="webPreview" destination="3Ca-ak-dqG" id="WbG-xe-HXB"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="fbz-k9-D9q" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1439" y="411"/>
</scene>
<!--Add Item View Controller-->
<scene sceneID="YxM-Hf-f0r">
<objects>
<viewController storyboardIdentifier="idAddItem" id="Kxm-nt-nUb" customClass="AddItemViewController" customModule="Print2PDF" customModuleProvider="target" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="G9n-Rd-WgX"/>
<viewControllerLayoutGuide type="bottom" id="Cab-7Y-OL1"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="dcj-PZ-K5I">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Price..." textAlignment="center" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="OIa-FX-7nh">
<rect key="frame" x="20" y="158" width="560" height="30"/>
<constraints>
<constraint firstAttribute="height" constant="30" id="it0-Vv-sop"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" keyboardType="numberPad" returnKeyType="done"/>
</textField>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Item or Service description..." textAlignment="center" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="kmr-Th-a0P">
<rect key="frame" x="20" y="98" width="560" height="30"/>
<constraints>
<constraint firstAttribute="height" constant="30" id="hXv-Vw-lAM"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" returnKeyType="next"/>
</textField>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="kmr-Th-a0P" firstAttribute="leading" secondItem="dcj-PZ-K5I" secondAttribute="leadingMargin" id="5kS-2j-EBW"/>
<constraint firstItem="OIa-FX-7nh" firstAttribute="trailing" secondItem="dcj-PZ-K5I" secondAttribute="trailingMargin" id="Hjk-b8-6tv"/>
<constraint firstItem="OIa-FX-7nh" firstAttribute="leading" secondItem="dcj-PZ-K5I" secondAttribute="leadingMargin" id="Ofs-Yv-sTx"/>
<constraint firstItem="OIa-FX-7nh" firstAttribute="top" secondItem="kmr-Th-a0P" secondAttribute="bottom" constant="30" id="QoG-Px-0JA"/>
<constraint firstItem="kmr-Th-a0P" firstAttribute="trailing" secondItem="dcj-PZ-K5I" secondAttribute="trailingMargin" id="WLO-1S-JuL"/>
<constraint firstItem="kmr-Th-a0P" firstAttribute="top" secondItem="G9n-Rd-WgX" secondAttribute="bottom" constant="34" id="oxN-SZ-0wF"/>
</constraints>
</view>
<navigationItem key="navigationItem" id="ydz-QL-08J">
<barButtonItem key="rightBarButtonItem" systemItem="save" id="6ea-Yj-Hz9">
<connections>
<action selector="saveItem:" destination="Kxm-nt-nUb" id="s18-0O-tEj"/>
</connections>
</barButtonItem>
</navigationItem>
<connections>
<outlet property="txtItemDescription" destination="kmr-Th-a0P" id="QYD-XO-Nke"/>
<outlet property="txtPrice" destination="OIa-FX-7nh" id="wV2-Yc-oIa"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="qNC-hl-ylg" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="2256" y="-293"/>
</scene>
<!--Navigation Controller-->
<scene sceneID="k84-tD-0vU">
<objects>
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="orp-cW-drb" sceneMemberID="viewController">
<toolbarItems/>
<navigationBar key="navigationBar" contentMode="scaleToFill" id="e8d-at-r7d">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
</navigationBar>
<nil name="viewControllers"/>
<connections>
<segue destination="BYZ-38-t0r" kind="relationship" relationship="rootViewController" id="lwo-sJ-nIw"/>
</connections>
</navigationController>
<placeholder placeholderIdentifier="IBFirstResponder" id="TJw-oV-eya" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-180" y="165"/>
</scene>
</scenes>
</document>
================================================
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
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
</dict>
</plist>
================================================
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<br>123 Somewhere Str.<br>10000 - MyCity<br>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: "<br>"))
// 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..<items.count {
var itemHTMLContent: String!
// Determine the proper template file.
if i != items.count - 1 {
itemHTMLContent = try String(contentsOfFile: pathToSingleItemHTMLTemplate!)
}
else {
itemHTMLContent = try String(contentsOfFile: pathToLastItemHTMLTemplate!)
}
// Replace the description and price placeholders with the actual values.
itemHTMLContent = itemHTMLContent.replacingOccurrences(of: "#ITEM_DESC#", with: items[i]["item"]!)
// Format each item's price as a currency value.
let formattedPrice = AppDelegate.getAppDelegate().getStringValueFormattedAsCurrency(value: items[i]["price"]!)
itemHTMLContent = itemHTMLContent.replacingOccurrences(of: "#PRICE#", with: formattedPrice)
// Add the item's HTML code to the general items string.
allItems += itemHTMLContent
}
// Set the items.
HTMLContent = HTMLContent.replacingOccurrences(of: "#ITEMS#", with: allItems)
// The HTML code is ready.
return HTMLContent
}
catch {
print("Unable to open and use HTML template files.")
}
return nil
}
func exportHTMLContentToPDF(HTMLContent: String) {
let printPageRenderer = CustomPrintPageRenderer()
let printFormatter = UIMarkupTextPrintFormatter(markupText: HTMLContent)
printPageRenderer.addPrintFormatter(printFormatter, startingAtPageAt: 0)
let pdfData = drawPDFUsingPrintPageRenderer(printPageRenderer: printPageRenderer)
pdfFilename = "\(AppDelegate.getAppDelegate().getDocDir())/Invoice\(invoiceNumber!).pdf"
pdfData?.write(toFile: pdfFilename, atomically: true)
print(pdfFilename)
}
func drawPDFUsingPrintPageRenderer(printPageRenderer: UIPrintPageRenderer) -> NSData! {
let data = NSMutableData()
UIGraphicsBeginPDFContextToData(data, CGRect.zero, nil)
for i in 0..<printPageRenderer.numberOfPages {
UIGraphicsBeginPDFPage()
printPageRenderer.drawPage(at: i, in: UIGraphicsGetPDFContextBounds())
}
UIGraphicsEndPDFContext()
return data
}
}
================================================
FILE: Print2PDF/InvoiceListViewController.swift
================================================
//
// InvoiceListViewController.swift
// Print2PDF
//
// Created by Gabriel Theodoropoulos on 14/06/16.
// Copyright © 2016 Appcoda. All rights reserved.
//
import UIKit
class InvoiceListViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
@IBOutlet weak var tblInvoices: UITableView!
var invoices: [[String: AnyObject]]!
var selectedInvoiceIndex: Int!
override func viewDidLoad() {
super.viewDidLoad()
tblInvoices.delegate = self
tblInvoices.dataSource = self
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let inv = UserDefaults.standard.object(forKey: "invoices") {
invoices = inv as? [[String: AnyObject]]
tblInvoices.reloadData()
}
}
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 prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let identifier = segue.identifier {
if identifier == "idSeguePresentPreview" {
let previewViewController = segue.destination as! PreviewViewController
previewViewController.invoiceInfo = invoices[selectedInvoiceIndex]
}
}
}
// MARK: IBAction Methods
@IBAction func createInvoice(_ sender: AnyObject) {
let creatorViewController = storyboard?.instantiateViewController(withIdentifier: "idCreateInvoice") as! CreatorViewController
creatorViewController.presentCreatorViewControllerInViewController(originalViewController: self) { (invoiceNumber, recipientInfo, totalAmount, items) in
DispatchQueue.main.async {
if self.invoices == nil {
self.invoices = [[String: AnyObject]]()
}
// Add the new invoice data to the invoices array.
self.invoices.append(["invoiceNumber": invoiceNumber as AnyObject, "invoiceDate": self.formatAndGetCurrentDate() as AnyObject, "recipientInfo": recipientInfo as AnyObject, "totalAmount": totalAmount as AnyObject, "items": items as AnyObject])
// Update the user defaults with the new invoice.
UserDefaults.standard.set(self.invoices, forKey: "invoices")
// Reload the tableview.
self.tblInvoices.reloadData()
}
}
}
// MARK: Custom Methods
func formatAndGetCurrentDate() -> 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 = "<group>"; };
C091D2501D1D40CA008DBE68 /* CustomPrintPageRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomPrintPageRenderer.swift; sourceTree = "<group>"; };
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 = "<group>"; };
C0C922251D0FFDE5000D4607 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
C0C922271D0FFDE6000D4607 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
C0C9222A1D0FFDE6000D4607 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
C0C9222C1D0FFDE6000D4607 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
C0C922321D0FFE1B000D4607 /* InvoiceListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InvoiceListViewController.swift; sourceTree = "<group>"; };
C0C922341D0FFE67000D4607 /* CreatorViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreatorViewController.swift; sourceTree = "<group>"; };
C0C922361D1002C4000D4607 /* AddItemViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddItemViewController.swift; sourceTree = "<group>"; };
C0C922391D10585C000D4607 /* invoice.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = invoice.html; sourceTree = "<group>"; };
C0C9223A1D10585C000D4607 /* last_item.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = last_item.html; sourceTree = "<group>"; };
C0C9223B1D10585C000D4607 /* single_item.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = single_item.html; sourceTree = "<group>"; };
C0C9223F1D105886000D4607 /* PreviewViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreviewViewController.swift; sourceTree = "<group>"; };
/* 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 = "<group>";
};
C0C9221E1D0FFDE5000D4607 /* Products */ = {
isa = PBXGroup;
children = (
C0C9221D1D0FFDE5000D4607 /* Print2PDF.app */,
);
name = Products;
sourceTree = "<group>";
};
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 = "<group>";
};
C0C922381D105847000D4607 /* HTML Templates */ = {
isa = PBXGroup;
children = (
C0C922391D10585C000D4607 /* invoice.html */,
C0C9223A1D10585C000D4607 /* last_item.html */,
C0C9223B1D10585C000D4607 /* single_item.html */,
);
name = "HTML Templates";
sourceTree = "<group>";
};
/* 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 = "<group>";
};
C0C922291D0FFDE6000D4607 /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
C0C9222A1D0FFDE6000D4607 /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* 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
================================================
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type">
<title>A simple, clean, and responsive HTML invoice template</title>
<style>
.invoice-box{
max-width:800px;
margin:auto;
padding:30px;
/*border:1px solid #eee;*/
/*box-shadow:0 0 10px rgba(0, 0, 0, .15); */
font-size:16px;
line-height:24px;
font-family:'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;
color:#555;
}
.invoice-box table{
width:100%;
line-height:inherit;
text-align:left;
}
.invoice-box table td{
padding:5px;
vertical-align:top;
}
.invoice-box table tr td:nth-child(2){
text-align:right;
}
.invoice-box table tr.top table td{
padding-bottom:20px;
}
.invoice-box table tr.top table td.title{
font-size:45px;
line-height:45px;
color:#333;
}
.invoice-box table tr.information table td{
padding-bottom:40px;
}
.invoice-box table tr.heading td{
background:#eee;
border-bottom:1px solid #ddd;
font-weight:bold;
}
.invoice-box table tr.details td{
padding-bottom:20px;
}
.invoice-box table tr.item td{
border-bottom:1px solid #eee;
}
.invoice-box table tr.item.last td{
border-bottom:none;
}
.invoice-box table tr.total td:nth-child(2){
border-top:2px solid #eee;
font-weight:bold;
}
@media only screen and (max-width: 600px) {
.invoice-box table tr.top table td{
width:100%;
display:block;
text-align:center;
}
.invoice-box table tr.information table td{
width:100%;
display:block;
text-align:center;
}
}
</style>
</head>
<body>
<div class="invoice-box">
<table cellpadding="0" cellspacing="0">
<tbody>
<tr class="top">
<td colspan="2">
<table>
<tbody>
<tr>
<td class="title"> <img src="#LOGO_IMAGE#" style="width:100%; max-width:300px; background-color: #cdcdcd">
</td>
<td> Invoice #: #INVOICE_NUMBER#<br>
#INVOICE_DATE#<br>
#DUE_DATE# </td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr class="information">
<td colspan="2">
<table>
<tbody>
<tr>
<td> #SENDER_INFO# </td>
<td> #RECIPIENT_INFO# </td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr class="heading">
<td> Payment Method </td>
<td> <br>
</td>
</tr>
<tr class="details">
<td> #PAYMENT_METHOD# </td>
<td> <br>
</td>
</tr>
<tr class="heading">
<td> Item </td>
<td> Price </td>
</tr>
#ITEMS#
<tr class="total">
<td><br>
</td>
<td> Total: #TOTAL_AMOUNT# </td>
</tr>
</tbody>
</table>
</div>
</body>
</html>
================================================
FILE: last_item.html
================================================
<tr class="item last">
<td>#ITEM_DESC#</td>
<td>#PRICE#</td>
</tr>
================================================
FILE: single_item.html
================================================
<tr class="item">
<td>#ITEM_DESC#</td>
<td>#PRICE#</td>
</tr>
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
Condensed preview — 17 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (86K chars).
[
{
"path": ".gitignore",
"chars": 112,
"preview": "build\nxcuserdata\n*.mode*\n*pbxuser\n*.perspectivev3\nproject.xcworkspace\n*.xcuserstate\n*tmproj\n~*\n.DS_Store\n*.orig\n"
},
{
"path": "Print2PDF/AddItemViewController.swift",
"chars": 3226,
"preview": "//\n// AddItemViewController.swift\n// Print2PDF\n//\n// Created by Gabriel Theodoropoulos on 14/06/16.\n// Copyright © 2"
},
{
"path": "Print2PDF/AppDelegate.swift",
"chars": 1300,
"preview": "//\n// AppDelegate.swift\n// Print2PDF\n//\n// Created by Gabriel Theodoropoulos on 14/06/16.\n// Copyright © 2016 Appcod"
},
{
"path": "Print2PDF/Assets.xcassets/AppIcon.appiconset/Contents.json",
"chars": 1077,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"iphone\",\n \"size\" : \"29x29\",\n \"scale\" : \"2x\"\n },\n {\n \"idiom\""
},
{
"path": "Print2PDF/Base.lproj/LaunchScreen.storyboard",
"chars": 1664,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard"
},
{
"path": "Print2PDF/Base.lproj/Main.storyboard",
"chars": 26369,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard"
},
{
"path": "Print2PDF/CreatorViewController.swift",
"chars": 6908,
"preview": "//\n// CreatorViewController.swift\n// Print2PDF\n//\n// Created by Gabriel Theodoropoulos on 14/06/16.\n// Copyright © 2"
},
{
"path": "Print2PDF/CustomPrintPageRenderer.swift",
"chars": 3911,
"preview": "//\n// CustomPrintPageRenderer.swift\n// Print2PDF\n//\n// Created by Gabriel Theodoropoulos on 24/06/16.\n// Copyright ©"
},
{
"path": "Print2PDF/Info.plist",
"chars": 1593,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Print2PDF/InvoiceComposer.swift",
"chars": 5518,
"preview": "//\n// InvoiceComposer.swift\n// Print2PDF\n//\n// Created by Gabriel Theodoropoulos on 23/06/16.\n// Copyright © 2016 Ap"
},
{
"path": "Print2PDF/InvoiceListViewController.swift",
"chars": 4674,
"preview": "//\n// InvoiceListViewController.swift\n// Print2PDF\n//\n// Created by Gabriel Theodoropoulos on 14/06/16.\n// Copyright"
},
{
"path": "Print2PDF/PreviewViewController.swift",
"chars": 4014,
"preview": "//\n// PreviewViewController.swift\n// Print2PDF\n//\n// Created by Gabriel Theodoropoulos on 14/06/16.\n// Copyright © 2"
},
{
"path": "Print2PDF.xcodeproj/project.pbxproj",
"chars": 15019,
"preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 46;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
},
{
"path": "README.md",
"chars": 206,
"preview": "# Print2PDF in iOS\n\nIt is an invoice demo showing you how to create PDF documents using HTML templates in iOS. For full "
},
{
"path": "invoice.html",
"chars": 4879,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <meta content=\"text/html; charset=utf-8\" http-equiv=\"content-type\">\n "
},
{
"path": "last_item.html",
"chars": 69,
"preview": "<tr class=\"item last\">\n\t<td>#ITEM_DESC#</td>\n\t<td>#PRICE#</td>\n</tr>\n"
},
{
"path": "single_item.html",
"chars": 64,
"preview": "<tr class=\"item\">\n\t<td>#ITEM_DESC#</td>\n\t<td>#PRICE#</td>\n</tr>\n"
}
]
About this extraction
This page contains the full source code of the appcoda/Print2PDF GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 17 files (78.7 KB), approximately 20.0k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.