読者です 読者をやめる 読者になる 読者になる

IwamotoBlog

俺に付いて来い

SwiftでPDFをDL、保存して良さ気な見た目で表示しよう

色々あって個人の漫画アプリを作る案件を請け負わせて頂きました。
その実装の際に、サーバーからPDFをDL、保存したり、WebViewでなく横スワイプでページをめくれるようにしたり…みたいなことをしようとして、とても(2週間ほど)躓いたので、知見を共有しようかと思います。

環境

Mac OS X Yosemite 10.10.4
XCode 6.4
CocoaPods 0.38.2
M13PDFKit 1.0.2

プロジェクトの立ち上げ

XCodeを起動してCreate a new Xcode projectを選び、iOS Application -> Single View Application を選択。 名前を適当に設定してLanguageはSwiftに。出来たら保存!

f:id:PEPOipod007:20150813003406p:plain

PDFをDL、保存する仕組みを作る

とりあえずこれが出来ないと表示もへったくれもないので先に実装しちゃいましょう。
トップ画面のViewControllerを別に作って編集していきます。メニューバーからFile -> New -> Fileを選択してください。
次にCocoa Touch Classを選択して

f:id:PEPOipod007:20150813003955p:plain

Sub Class をUIViewControllerに設定。名前は 雑にTopViewControllerとかでいいでしょう。

f:id:PEPOipod007:20150813004002p:plain

出来たらプロジェクトのルートフォルダ内にある、プロジェクト名と同じフォルダの中に保存してください。AppDelegate.swiftとかが入ってるところです。

続いてStoryBoardを開いてViewControllerの割り当て、ボタンの配置等を進めていきましょう。

UIButtonを2つ、ProgressBar1つをこんな感じに配置してください。

f:id:PEPOipod007:20150813004655p:plain

画像ではMainViewControllerとか書かれてますが、気にしないでください。

上の〜〜ViewControllerと書かれているところをクリックするとViewControllerをユーティリティエリア(右側のやつ)から弄れるようになります。
ユーテリティエリアで下の画像で選択されている、左から3つ目のタブをクリックしClassからTopViewControllerを選択してあげてください。

f:id:PEPOipod007:20150813005425p:plain

その後、上のバーの右の方にある、丸が2つ重なったようなボタンをクリックして下さい。コードが右に表示されるはずです。
その状態でCtrlを押したままボタンをクリックしてそのままコードにドラッグしてください。ボタンが変数として登録されます。
以下のように接続してください。

Downloadと書かれたボタン
Connection: Outlet
Name: dl_button

Downloadと書かれたボタン
Connection: Action
Name: tapDlButton

ProgressBar
Connection: Outlet
Name: progress_bar

出来たら以下のようにコードを書いてください。

import UIKit

class TopViewController: UIViewController, NSURLSessionDownloadDelegate {

    @IBOutlet weak var dl_button: UIButton!
    @IBOutlet weak var progress_bar: UIProgressView!
    
    let path = NSSearchPathForDirectoriesInDomains(
        .DocumentDirectory,
        .UserDomainMask, true)
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        //self.show_button.hidden = true;
        progress_bar.progress = 0
    }

    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.
    }
    */
    
    @IBAction func tapDlButton(sender: AnyObject) {
        self.downloadWithFile()
    }
    
    func downloadWithFile() {
        let url = NSURL(string: "http://cdn.oreillystatic.com/oreilly/booksamplers/9781491908693_sampler.pdf")
        let config = NSURLSessionConfiguration.defaultSessionConfiguration()

        let session = NSURLSession(configuration: config,
            delegate: self,
            delegateQueue: NSOperationQueue.mainQueue())
        
        let task = session.downloadTaskWithURL(url!)
        
        task.resume()
    }
    
    func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
        progress_bar.progress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
    }
    
    func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) {
        
        progress_bar.progress = 1.0
        
        let data = NSData(contentsOfURL: location)!
        
        if data.length == 0 {
            NSLog("Error")
        } else {
            NSLog("Download Success")
            self.show_button.hidden = false
            
            let _path = path[0].stringByAppendingPathComponent("test.pdf")
            
            let success = data.writeToFile(_path, atomically: true)
            
            if success {
                println("Save Success")
            }
        }
    }
    
    func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
        
        if error != nil{
            session.invalidateAndCancel()
        }else{
            session.finishTasksAndInvalidate()
        }
        NSLog("OK")
    }
}

ダウンロードにはNSURLSessionというクラスを用います。classの行にNSURLSessionDelegateをつけるのを忘れないで下さい。
progress_bar.progress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)によってダウンロードの進み具合を可視化しています。
保存にあたっては、Document配下に保存するためまずNSSearchPathForDirectoriesInDomainsでDocumentのパスを取得し、let _path = path[0].stringByAppendingPathComponent("test.pdf")によって細かく保存先を指定しています。
その後、data.writeToFile(_path, atomically: true)でファイルを保存しました。

これで保存までは完了しましたね。次は表示しましょう。

CocoaPodsでライブラリをダウンロードしよう

次はPDFをよしなに表示してくれるライブラリを導入しましょう。今回用いるライブラリはこちらです。

github.com

では早速導入しましょう。CocoaPodsの導入方法は省きます。
ターミナルからプロジェクトのルートフォルダにアクセスし、

$ pod init

を実行してください。
実行して出てきたPodfileは以下のように記述してください。

# Uncomment this line to define a global platform for your project
# platform :ios, '6.0'
source 'https://github.com/CocoaPods/Specs.git'
use_frameworks!

pod 'M13PDFKit', '1.0.2'

書けたら

$ pod install

を実行して下さい。

無事に成功したのを確認したら、今開いてるXCodeのプロジェクトを閉じて、新しく生成されたプロジェクト名.xcworkspaceを開いてください。
これでライブラリのダウンロードは完了です。

ライブラリを用いてPDFを表示しよう。

ではこのライブラリを用いて、PDFを表示します。
まずは表示するためのViewControllerを準備しましょう。StoryBoardを開いてください。 新しくViewControllerを設置し、TopViewControllerのShow PDFと書かれたボタンをCtrlを押したままクリックし、新しいViewControllerへドラッグしてください。
すると黒いメニューが表示されるので、その中にあるshowを押してあげて下さい。

f:id:PEPOipod007:20150813014034p:plain

次に、TopViewControllerを設定した要領で、この新しいViewControllerにもクラスを割り当ててあげましょう。割り当てるクラスはPDFKBasicPDFViewerです。

f:id:PEPOipod007:20150813014141p:plain

ここまで出来たら、またTopViewControllerを編集していきます。
まず一番上のインポートを以下のように変更してください。

import UIKit
import M13PDFKit

これでM13PDFKitライブラリがこのファイル内で扱えるようになりました。
続いて一番下に以下のコードを書き足してください。

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        var viewer: PDFKBasicPDFViewer = segue.destinationViewController as! PDFKBasicPDFViewer
        
        let _path = path[0].stringByAppendingPathComponent("test.pdf")
        
        var document = PDFKDocument(contentsOfFile: _path, password: nil)
        viewer.loadDocument(document)
    }

_pathに先ほど指定したPDFの保存先を格納し、 var document = PDFKDocument(contentsOfFile: _path, password: nil)PDFKDocumetに変換しています。

これでPDFViewerは完成です。実行して確認してみましょう。

f:id:PEPOipod007:20150813014835p:plain

おわりに

Swift(というかiOSアプリ開発)分からないことが多すぎます…。Objective-Cのライブラリと連携させる所とかとても詰まりました。
最終的にはSwiftに明るい方に助けて頂いて何とかなりました。本当にありがとうございます。個人の漫画アプリを作ろうとしている方々、是非この知見を活かしてください。

漫画アプリ案件、何とかなってくれという気持ちで一杯だ…頑張っていこう。