- Swift版本:2.1.1(在Terminal輸入$ xcrun swift -version指令可查看版本)
- iOS版本:9.2
- 模擬器環境
前不久工作需要APP有產生PDF報表檔、讀取及印出的功能,研究出來後在此做個記錄。
製作這些功能主要有以下幾個步驟:
產生PDF檔
- 使用UIGraphicsBeginPDFContextToFile產生一PDF內容
- 使用UIGraphicsBeginPDFPageWithInfo產生新的一頁
- 使用UIKit和Core Graphics繪製PDF內容
- 完成內容繪製後,使用UIGraphicsEndPDFContext結束
讀取PDF檔
- 建立UIWebView
- 取得檔案的URL後,使用loadRequest載入
印出、郵寄PDF檔
- 使用Quick Look Framework
產生PDF檔
1. 使用UIGraphicsBeginPDFContextToFile產生一PDF內容let pathArr:NSArray = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let path = pathArr[0]
let fileName = path.stringByAppendingPathComponent("mypdf.pdf")
為了安全性,iOS使用沙盒(Sand Box)機制去限制每個APP只能讀取為該APP建立的檔案系統。在iOS的檔案系統底下有個Documents資料夾用來存放使用者產生的檔案,而這個資料夾的路徑可用NSSearchPathForDirectoriesInDomains取得。該函數會回傳一個String型別的陣列存放路徑值,在給予的參數條件下,此陣列的長度為1。取陣列的第一個元素得到路徑值後,使用stringByAppendingPathComponent建立檔案名稱。接著使用UIGraphicsBeginPDFContextToFile在該檔案中建立PDF內容。
UIGraphicsBeginPDFContextToFile(fileName, CGRectZero, nil)
2. 使用UIGraphicsBeginPDFPageWithInfo產生新的一頁
UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, 792, 612), nil)
在開始繪製內容前,須呼叫UIGraphicsBeginPDFPageWithInfo函數來標記為新的一頁。CGRectMake(0, 0, 792, 612)分別為該頁內容的起點x,y及寬、高。792x612這個尺寸是US Letter尺寸,如要換為A4尺寸則是842x595像素(橫向)。
3. 使用UIKit和Core Graphics繪製PDF內容
iOS的2D繪圖基於UIKit及Core Graphics兩個框架來處理,首先以文字為例。
let myText:NSString = "Nothing in all the world is more dangerous than sincere ignorance and conscientious stupidity."
let textStyle = NSMutableParagraphStyle.defaultParagraphStyle().mutableCopy() as! NSMutableParagraphStyle
textStyle.alignment = NSTextAlignment.Center
let textFontAttributes = [
NSFontAttributeName: UIFont.boldSystemFontOfSize(12),
NSForegroundColorAttributeName: UIColor.blueColor(),
NSParagraphStyleAttributeName: textStyle
]
myText.drawInRect(CGRectMake(0, 0, 792, 612), withAttributes: textFontAttributes)
第一行宣告欲繪製的文字變數myText(型別須為NSString)後,接下來設定文字樣式屬性。建立一個NSMutableParagraphStyle,設定alignment為center(文字置中對齊),並建立一Dictionary放置文字大小、顏色及對齊的樣式。NSFontAttributeName為設定文字字體,NSForegroundColorAttributeName設定文字顏色(NSString所包含的樣式屬性列表)。最後就是呼叫drawInRect函數,帶入繪圖區域及樣式設定的參數,進行NSString的繪製。
除了文字以外,可能還有繪製線條的需求。由於線條的繪製很常用到,所以建立一個drawLine函數供使用,帶有兩個參數值,分別是線條的起始y位置(y),以及線條寬度(lineWidth)。在這個函數中,首先使用UIGraphicsBeginImageContextWithOptions函數建立一個圖形的上下文(context),並建立一個變數context儲存由UIGraphicsGetCurrentContext回傳的目前上下文。接下來建立一個img變數,儲存UIGraphicsGetImageFromCurrentImageContext函數回傳由現在圖形上下文內容所轉成的圖片(UIImage型別),並用UIGraphicsEndImageContext函數從堆疊頂端移出目前的圖形上下文。最後使用drawInRect將這個圖片繪製上去。
func drawLine(y:CGFloat,lineWidth:CGFloat) -> Bool{
UIGraphicsBeginImageContextWithOptions(CGSize(width: 792, height: 612), false, 0)
let context = UIGraphicsGetCurrentContext()
CGContextMoveToPoint(context, 0, 0)//移至繪製點(0,0)
CGContextAddLineToPoint(context, 792, 0)//新增一條線至座標(792,0)
CGContextSetLineWidth(context, lineWidth)//設定線條寬度
CGContextSetStrokeColorWithColor(context, UIColor.blackColor().CGColor)//設定線條顏色
CGContextStrokePath(context)//開始繪製線條
let img=UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
img.drawInRect(CGRectMake(0, y, 792, 612))
return true
}
每要繪製線條時,就呼叫drawLine(y:CGFloat,lineWidth:CGFloat)。由於欲在上面那串文字的下方再呈現一條線,因而需要取得文字的高度。這邊可使用NSString的sizeWithAttributes函數取得(上面文字的大小設定為16)。
let textSize:CGSize = myText.sizeWithAttributes([NSFontAttributeName: UIFont.systemFontOfSize(16)]) let textHeight = textSize.height + 10 //文字與線條間留點空隙 drawLine(y:textHeight,lineWidth: 1)
如要換新一頁,記得都要呼叫UIGraphicsBeginPDFPageWithInfo函數。確定完成所有PDF的內容繪製後,使用UIGraphicsEndPDFContext函數結束。
UIGraphicsEndPDFContext()
建立完成的檔案可從該APP的Documents資料夾找到。
該APP資料夾的路徑可透過下列方式查到:
- 設定Breakpoint
- 使用Xcode的除錯器LLDB。當執行到該行Console底下會出現(lldb),打入指令po NSHomeDirectory()即會出現路徑。
讀取PDF檔
1. 建立UIWebView透過Storyboard、XIB或程式撰寫建立一個UIWebView,並由這個UIWebView顯示PDF檔案內容。我將這個UIWebView命名為showWebView。
2. 取得檔案的URL後,使用loadRequest載入
func showfile(){
let fileName:String = "mypdf.pdf"
let pathArr:NSArray = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let path = pathArr[0]
let fileNamePath = path.stringByAppendingPathComponent(fileName)
let url:NSURL = NSURL.fileURLWithPath(fileNamePath)
let request:NSURLRequest = NSURLRequest(URL: url)
showWebView.loadRequest(request)
}
印出、郵寄PDF檔
1. 使用Quick Look Framework除了用UIWebView顯示PDF檔案內容外。如果有預覽、列印或轉寄PDF的需求,這時可採用Quick Look。使用此Framework須實作QLPreviewControllerDataSource協定內被委託的函數。裡面的函數分別是numberOfPreviewItemsInPreviewController及previewController。
記得一開始須連結Framework。
import QuickLook
接下來實作第一個numberOfPreviewItemsInPreviewController函數,此函數需回傳資料的筆數(也就是檔案的筆數)。contentsOfDirectoryAtPath函數可以回傳特定資料夾內的所有項目路徑,之後使用filteredArrayUsingPredicate函數搭配NSPredicate使用,過濾副檔名為pdf的項目,最後再回傳該陣列的元素數量即可。
關於NSPredicate,如需用到正規表達式去驗證資料時可拿來應用。在下面的程式例子中,檔名須符合"self ENDSWITH '.pdf'"這個格式。self代表字串本身,ENDSWITH代表該字串是否以指定的字串'.pdf'結尾。詳細的語法可見官網(Predicate Format String Syntax)。
var fileList:NSArray = []//用來儲存Documents資料夾內的所有檔名
let pathArr:NSArray = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
func numberOfPreviewItemsInPreviewController(controller: QLPreviewController) -> Int {
let path = pathArr[0]
do{
fileList = try NSFileManager.defaultManager().contentsOfDirectoryAtPath(path as! String)
let filter:NSPredicate = NSPredicate(format: "self ENDSWITH '.pdf'")
fileList = fileList.filteredArrayUsingPredicate(filter)
}catch let error as NSError {
print(error.debugDescription)
}
return fileList.count
}
最後實作previewController。當需預覽該索引(previewItemAtIndex)項目時即會調用此函數,回傳該項目的NSURL(檔案的所在路徑)。
func previewController(controller: QLPreviewController, previewItemAtIndex index: Int) -> QLPreviewItem {
let fileName:String = fileList[index] as! String
let path = pathArr[0]
let fileNamePath = path.stringByAppendingPathComponent(fileName)
let url:NSURL = NSURL.fileURLWithPath(fileNamePath)
return url
}
完成後以Quick Look預覽PDF的畫面長這樣。
功能的實作大致上就是這樣了。不過有個問題也需記錄一下,在郵寄PDF的這個功能上,使用模擬器操作都會跳出這個錯誤(MailCompositionService quit unexpectedly)。拜了Google大神也不少人遇到這個問題,後來發現這個錯誤只會發生在模擬器,如果用實機操作就不會有這個問題。
以上。呼!終於打完了啊(T_T;)
目前放code的工具是使用SyntaxHighlighter,挺不錯的工具。我使用的是第2版。
參考資源:




沒有留言:
張貼留言