はじめに
こんにちは!株式会社ZOZOでiOSエンジニアをしている加藤です。私はショップスタッフの販売サポートツール「FAANS」の開発に従事しています。FAANSでは、この度、ショップスタッフのコーデ投稿を支援するPDFの表示機能が実装され、コーデ投稿に関する情報が掲載されたPDFをアプリ内で閲覧できるようになりました。
FAANSにおけるPDFの閲覧機能はPDFKitを用いて作成されており、PDFの左下に閲覧中のページ数を表示するデザインを採用しています。本記事では、PDFKitを用いて表示したPDFにおいて、閲覧中のページ数を取得し、表示する方法について書きます!
目次
- PDFKitを用いたPDF表示
- 縦長PDFのページ番号表示
- 横長PDFのページ番号表示
PDFKitを用いたPDF表示
PDFKitを用いたPDF表示は以下のようなプログラムで行えます。
var document: PDFDocument?
@MainActor
func setupPDFView() {
let pdfView = PDFView()
pdfView.displayMode = .singlePageContinuous // PDFを連続で表示する
pdfView.displayDirection = .vertical // 縦方向に表示する
pdfView.autoScales = true // 自動的にPDFのスケールを画面に合わせる
pdfView.document = document
return pdfView
}
func loadPdfDocument() async {
do {
let (data, _) = try await URLSession.shared.data(with: pdfUrl) // URLから非同期でPDFデータを取得
if let document = PDFDocument(data: data), document.pageCount > 0 {
self.document = document // 有効なPDFを設定
} else {
// 表示できるPDFがないケース
}
} catch {
// 表示できるPDFがないケース
}
}
extension URLSession {
func data(with url: URL) async throws -> (Data, URLResponse) {
try await data(from: url)
}
}
上記のように、URLを指定してPDFDocumentを生成して、PDFViewのdocumentに設定することでPDFが表示されます。また、.displayMode = .singlePageContinuous
、.displayDirection = .vertical
に設定することでPDFが縦方向に連続で表示されるようにしています。
縦長PDFのページ番号表示
先に提示したGifのように、縦長PDFを表示中に閲覧しているページ番号を取得する方法を紹介します。以下のコードは、ページが変更されるたびに通知を受け取り、現在のページ番号を取得する仕組みです。
let pdfView = PDFView()
NotificationCenter.default.addObserver(forName: .PDFViewPageChanged, object: pdfView, queue: .main) { _ in
Task { @MainActor in
// 現在のページ番号を取得
if let pageNumber = pdfView.currentPage?.pageRef?.pageNumber {
// pageNumberを画像上に表示
}
}
}
このコードでは、NotificationCenterを使用して.PDFViewPageChanged
通知を監視しています。この通知は、PDFViewでページをスクロールするなどして表示中のページが変更されるたびにトリガーされます。また、pdfView.currentPageで現在のページを取得でき、pageRef?.pageNumberから現在のページ番号(1始まり)を取得しています。
横長PDFのページ番号表示
横長のPDFを表示する場合、前節と同じようにページ番号を取得すると、初期状態で表示されるページ番号が2になってしまいます。初期のページ番号が2になっているGifを以下に示します。
初期のページ番号が2になってしまう原因は、横長PDFの場合、画面上に複数ページが同時に表示されることです。初期の画面では、1〜3ページが表示され、中央に最も近いページが2ページ目のため、取得されるページ数が2となります。FAANSでは、PDFの上端が表示されている場合は、ページ数の表示は1にしたいため、以下のように実装しました。
let pdfView = PDFView()
// ページ変更通知を監視して、現在のページ番号を更新
NotificationCenter.default.addObserver(forName: .PDFViewPageChanged, object: pdfView, queue: .main) { _ in
Task { @MainActor in
// 現在のページ番号を取得し、上端が表示されている場合は1を設定
if let pageNumber = pdfView.currentPage?.pageRef?.pageNumber {
currentPage = showPdfTop ? 1 : pageNumber
}
}
}
// scrollViewのcontentOffsetを監視してPDFのスクロール位置を検出
if let scrollView = pdfView.scrollView() {
cancellableStore.cancellable = scrollView.publisher(for: \.contentOffset)
.sink { [weak pdfView] contentOffset in
Task { @MainActor in
guard let pdfView = pdfView else { return }
if contentOffset.y <= 0.0 {
// PDFの最上部が表示されている場合
currentPage = 1
showPdfTop = true
} else if let pageNumber = pdfView.currentPage?.pageRef?.pageNumber, pageNumber != currentPage {
// ページが変更された場合に通知を送信
NotificationCenter.default.post(name: .PDFViewPageChanged, object: pdfView)
showPdfTop = false
}
}
}
}
fileprivate extension PDFView {
// PDFViewのサブビューからUIScrollViewを取得
func scrollView() -> UIScrollView? {
return self.subviews.compactMap { $0 as? UIScrollView }.first
}
}
このプログラムでは、PDFViewからスクロール位置を検出するために内部のUIScrollViewを取得し、そのcontentOffsetをCombineのPublisherを使用して監視しています。contentOffsetが0以下の場合、PDFの最上端が表示されていると判断してページ番号を1に設定します。これにより、横長PDFであっても意図したページ番号を正しく表示できます。
まとめ
PDFKitを用いてPDFを表示する方法と、縦長・横長のPDFで現在のページ番号を取得する方法を紹介しました。この記事が、今後PDFKitを用いてiOSアプリにPDF表示機能を実装する方の参考になれば幸いです。