PDFを表示するビュワーアプリを作った

# TL;DR
* PDFKitのサンプルをSwiftUIで作成しました
* ページをめくるたびに今表示されているPDFのページ番号を取得します
* で、NotificatonCenterにaddObserveしてPDFViewからページ番号は取得します
* で、その場合ViewController時代ならViewDidLoadなどで登録したりできますが、どうしたらいいんだろう、、、

と言うあたりを解説します。

# デモ

[![Image from Gyazo](https://i.gyazo.com/496eac4f1e9a1cc88bc0a148d5941ca3.gif)](https://gyazo.com/496eac4f1e9a1cc88bc0a148d5941ca3)

ソースはこちら＞https://github.com/dropcontrol/PDFViewer

# Viewの解説から

```
struct ContentView: View {
    @ObservedObject var pdfInfo: PDFInfo = PDFInfo()
    
    var body: some View {
        VStack {
            ShowPDFView(pdfInfo: pdfInfo)
            PdfInfoView(pdfInfo: pdfInfo)
            .padding()
        }.onAppear(){
            pdfInfo.addObserver()
        }
        
    }
}
```

一番親になるViewでは二つのViewを呼んでいます。一つはPDFのビュワー部分である`ShowPDFView()`で、もう一つはその下でページ番号とTOPボタンを表示してる`PdfInfoView()`です。で、そこで`pdfInfo: pdfInfo`を渡していますが、それはViewの上の

```
@ObservedObject var pdfInfo: PDFInfo = PDFInfo()
```

の部分で`@ObservedObject`で`ObservableObject`である`PDFInfo()`のインスタンスを作成しています。で、その`PDFInfo()`は、、、

```
class PDFInfo: ObservableObject {
    @Published var pageNo: Int = 1
    @Published var pdfView: PDFView = PDFView()
    @Published var stateTopButton: Bool = false
    
    func addObserver() {
        NotificationCenter.default.addObserver(self, selector: #selector(self.pageChanged(_:)), name: Notification.Name.PDFViewPageChanged, object: nil)
    }
    
    @objc func pageChanged(_ notification: Notification) {
        pageNo = pdfView.currentPage!.pageRef!.pageNumber
        stateTopButton = pdfView.canGoToFirstPage
        print(self.pageNo)
        print("page is changed")
    }
}
```

こんな感じになってます。`@Publised`では`pageNo`, `pdfView`, `stateTopButton`といった変数を作っています。その下ではNotificasionCenterへの登録をする`func addObserver(){}`を、notificationを受け取ったら実行される`@objc func pageChanged(_ notification: Notification) {}`の関数を定義しています。その中ではページ番号と、PDFのトップページかどうかを判断する`canGoToFirstPage`です。

で、このなかでPDFの表示のために必要な、

```
@Published var pdfView: PDFView = PDFView()
```

を作っています。なので、先の`@ObservedObject`で初期化しています。と言うのも`pdfInfo`を他の`View`でも使い回したいので、`pdfInfo`を読み込んでいる`View`に渡しています。

# その他のViewについて

`PdfInfoView`はUIのところなのでソース読めばわかると思うので割愛して、`ShowPDFView`を解説します。ここでは以下のような感じです。

```
struct ShowPDFView: View {
    @ObservedObject var pdfInfo: PDFInfo
    
    var body: some View {
        PDFViewer(pdfInfo: pdfInfo)
    }
}
```

ここで用意してる`@ObservedObject var pdfInfo: PDFInfo`に親から`pdfInfo`を渡しています。`@ObservedObject`でインスタンスを使い回すケースを調べても親からインスタンスを渡す、と言うサンプルはあまり見当たらないのですが、これでやらないと先の`ObservableObject`で設定した変数を参照できません。

で、ここでは`PDFViewer(pdfInfo: pdfInfo)`で、実際のPDFの表示を行なっています。

# PDFViewer()について

PDFの設定についてはUIViewControllerなどでの書き方はほとんど一緒ですが、

```
struct PDFViewer: UIViewRepresentable {
    @ObservedObject var pdfInfo: PDFInfo
    
    let url: URL = Bundle.main.url(forResource: "Oz was Wizard - Original Soundtrack", withExtension: "pdf")!
    
    func makeUIView(context: UIViewRepresentableContext<PDFViewer>) -> PDFViewer.UIViewType {
    ＜ここでPDFの設定＞
    }
    
    func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<PDFViewer>) {
    }
    
}
```

と言う形で`UIViewRepresentable`を使って実装しています。
参照：
* https://developer.apple.com/documentation/swiftui/uiviewrepresentable
* https://qiita.com/k_awoki/items/448fd0bd6f51500d13b1

UIKitを使うにあたってはこれと同じような書き方をすれば良いようです。
PDFの設定周りはソースや他のリソースを参考にして貰えば。

# このサンプルで出来なかったこと

PDFを拡大／縮小する機能を無効にしたかったんですが、これはPDFKitでは機能的には実装されていないので、まだ調べていますがやり方が分かる方教えてもらえたら嬉しいです。