Communitygithub.com

PDFKit

PDFKit을 사용하여 PDF 문서를 표시하고 조작합니다. PDFView를 임베드하여 PDF 파일을 보여주거나, PDFDocument 인스턴스를 생성/수정하고, 주석(하이라이트, 노트, 서명)을 추가하고, PDFSelection으로 텍스트를 추출하고, 페이지를 탐색하고, 썸네일을 생성하고, PDF 양식을 작성하거나, PDFView를 SwiftUI에 래핑할 때 사용하세요.

PDFKit란 무엇인가요?

이 스킬은 Apple의 PDFKit 프레임워크를 사용하여 iOS, iPadOS, macOS, visionOS 등 다양한 플랫폼에서 PDF 문서를 완벽하게 제어할 수 있는 방법을 제공합니다. PDFView, PDFDocument, PDFPage, PDFAnnotation, PDFSelection과 같은 핵심 클래스를 사용하여 PDF 파일을 표시하고, 탐색하고, 검색하고, 주석을 추가하고, 텍스트를 추출하는 방법을 상세히 다룹니다. 또한 SwiftUI에서 PDFView를 사용할 수 있도록 UIViewRepresentable을 활용한 통합 방법을 제시하며, PDFThumbnailView를 통한 썸네일 생성, 페이지 오버레이, 비동기 검색, 양식 처리 등 고급 기능도 포함합니다. 이 스킬은 주석 추가 시 PDF 좌표계(하단-왼쪽 원점)를 준수하고 모든 PDFKit 변경 작업을 메인 스레드에서 수행해야 하는 등 일반적인 실수를 피하는 방법을 강조하여 안정적이고 효율적인 PDF 처리를 보장합니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/JordanCoin/ios-skills-collection/tree/HEAD/skills/dpearson2699--pdfkit

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

PDFKit은(는) 무엇을 하나요?

PDFView, PDFDocument, PDFPage, PDFAnnotation, PDFSelection을 사용하여 PDF 문서를 표시, 탐색, 검색, 주석 및 조작합니다. Swift 6.3 / iOS 26+를 대상으로 합니다.

목차

설정

PDFKit은 entitlements나 Info.plist 항목이 필요하지 않습니다.

import PDFKit

플랫폼 지원: iOS 11+, iPadOS 11+, Mac Catalyst 13.1+, macOS 10.4+, tvOS 11+, visionOS 1.0+.

PDF 표시하기

PDFView는 UIView의 하위 클래스로 PDF 콘텐츠를 렌더링하며, 줌, 스크롤, 텍스트 선택 및 페이지 탐색을 기본적으로 지원합니다.

import PDFKit
import UIKit

class PDFViewController: UIViewController {
    let pdfView = PDFView()

    override func viewDidLoad() {
        super.viewDidLoad()
        pdfView.frame = view.bounds
        pdfView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.addSubview(pdfView)

        pdfView.autoScales = true
        pdfView.displayMode = .singlePageContinuous
        pdfView.displayDirection = .vertical

        if let url = Bundle.main.url(forResource: "sample", withExtension: "pdf") {
            pdfView.document = PDFDocument(url: url)
        }
    }
}

표시 모드

모드동작
.singlePage한 번에 한 페이지
.singlePageContinuous페이지가 세로로 쌓여 스크롤 가능
.twoUp두 페이지를 나란히 표시
.twoUpContinuous연속 스크롤이 가능한 두 페이지 표시

스케일링 및 외관

pdfView.autoScales = true
pdfView.minScaleFactor = pdfView.scaleFactorForSizeToFit
pdfView.maxScaleFactor = 4.0

pdfView.displaysPageBreaks = true
pdfView.pageShadowsEnabled = true
pdfView.interpolationQuality = .high

문서 불러오기

PDFDocument는 URL, Data로부터 불러오거나 빈 상태로 생성할 수 있습니다.

let fileDoc = PDFDocument(url: fileURL)
let dataDoc = PDFDocument(data: pdfData)
let emptyDoc = PDFDocument()

암호로 보호된 PDF

guard let document = PDFDocument(url: url) else { return }
if document.isLocked {
    if !document.unlock(withPassword: userPassword) {
        // 암호 입력 프롬프트 표시
    }
}

저장 및 페이지 조작

document.write(to: outputURL)
document.write(to: outputURL, withOptions: [
    .ownerPasswordOption: "ownerPass", .userPasswordOption: "userPass"
])
let data = document.dataRepresentation()

// 페이지 (0-based)
let count = document.pageCount
document.insert(PDFPage(), at: count)
document.removePage(at: 2)
document.exchangePage(at: 0, withPageAt: 3)

페이지 탐색

PDFView는 히스토리 추적 기능이 있는 내장 탐색을 제공합니다.

// 특정 페이지로 이동
if let page = pdfView.document?.page(at: 5) {
    pdfView.go(to: page)
}

// 순차 탐색
pdfView.goToNextPage(nil)
pdfView.goToPreviousPage(nil)
pdfView.goToFirstPage(nil)
pdfView.goToLastPage(nil)

// 탐색 상태 확인
if pdfView.canGoToNextPage { /* ... */ }

// 히스토리 탐색
if pdfView.canGoBack { pdfView.goBack(nil) }

// 페이지의 특정 지점으로 이동
let destination = PDFDestination(page: page, at: CGPoint(x: 0, y: 500))
pdfView.go(to: destination)

페이지 변경 관찰

NotificationCenter.default.addObserver(
    self, selector: #selector(pageChanged),
    name: .PDFViewPageChanged, object: pdfView
)

@objc func pageChanged(_ notification: Notification) {
    guard let page = pdfView.currentPage,
          let doc = pdfView.document else { return }
    let index = doc.index(for: page)
    pageLabel.text = "\(index + 1) / \(doc.pageCount) 페이지"
}

텍스트 검색 및 선택

동기 검색

let results: [PDFSelection] = document.findString(
    "search term", withOptions: [.caseInsensitive]
)

비동기 검색

방대한 문서의 백그라운드 검색에는 PDFDocumentDelegate를 사용하세요. 각 일치 항목을 수신하려면 didMatchString(_:)를 구현하고, 완료를 처리하려면 documentDidEndDocumentFind(_:)를 구현합니다.

증분 검색 및 찾기 상호 작용

// 현재 선택 항목에서 다음 일치 항목 찾기
let next = document.findString("term", fromSelection: current, withOptions: [.caseInsensitive])

// 시스템 찾기 바 (iOS 16+)
pdfView.isFindInteractionEnabled = true

텍스트 추출

let fullText = document.string                          // 전체 문서
let pageText = document.page(at: 0)?.string             // 단일 페이지
let attributed = document.page(at: 0)?.attributedString  // 서식 포함

// 영역 기반 추출
if let page = document.page(at: 0) {
    let selection = page.selection(for: CGRect(x: 50, y: 50, width: 400, height: 200))
    let text = selection?.string
}

검색 결과 하이라이트

let results = document.findString("important", withOptions: [.caseInsensitive])
for selection in results { selection.color = .yellow }
pdfView.highlightedSelections = results

if let first = results.first {
    pdfView.setCurrentSelection(first, animate: true)
    pdfView.go(to: first)
}

주석

주석은 PDFAnnotation(bounds:forType:withProperties:)으로 생성하고 PDFPage에 추가합니다.

하이라이트 주석

func addHighlight(to page: PDFPage, selection: PDFSelection) {
    let highlight = PDFAnnotation(
        bounds: selection.bounds(for: page),
        forType: .highlight, withProperties: nil
    )
    highlight.color = UIColor.yellow.withAlphaComponent(0.5)
    page.addAnnotation(highlight)
}

텍스트 노트 주석

let note = PDFAnnotation(
    bounds: CGRect(x: 100, y: 700, width: 30, height: 30),
    forType: .text, withProperties: nil
)
note.contents = "이것은 스티커 메모입니다."
note.color = .systemYellow
note.iconType = .comment
page.addAnnotation(note)

자유 텍스트 주석

let freeText = PDFAnnotation(
    bounds: CGRect(x: 50, y: 600, width: 300, height: 40),
    forType: .freeText, withProperties: nil
)
freeText.contents = "추가 코멘트"
freeText.font = UIFont.systemFont(ofSize: 14)
freeText.fontColor = .darkGray
page.addAnnotation(freeText)

링크 주석

let link = PDFAnnotation(
    bounds: CGRect(x: 50, y: 500, width: 200, height: 20),
    forType: .link, withProperties: nil
)
link.url = URL(string: "https://example.com")
page.addAnnotation(link)

// 내부 페이지 링크
link.destination = PDFDestination(page: targetPage, at: .zero)

주석 제거

for annotation in page.annotations {
    page.removeAnnotation(annotation)
}

주석 하위 유형 참조

하위 유형상수용도
하이라이트.highlight텍스트 마크업 (노란색 하이라이트)
밑줄.underline텍스트 마크업 (밑줄)
취소선.strikeOut텍스트 마크업 (취소선)
텍스트.text스티커 메모 아이콘
자유 텍스트.freeText인라인 텍스트 블록
잉크.ink자유형 그리기 경로
링크.linkURL 또는 페이지 대상
선.line끝점이 있는 직선
사각형.square직사각형 모양
원.circle타원형 모양
스탬프.stamp고무 도장 (승인됨 등)
위젯.widget양식 요소 (텍스트 필드, 체크박스)

썸네일

PDFThumbnailView

PDFThumbnailView는 PDFView에 연결된 페이지 썸네일 스트립을 표시합니다.

let thumbnailView = PDFThumbnailView()
thumbnailView.pdfView = pdfView
thumbnailView.thumbnailSize = CGSize(width: 60, height: 80)
thumbnailView.layoutMode = .vertical
thumbnailView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(thumbnailView)

프로그래매틱 방식으로 썸네일 생성

let thumbnail = page.thumbnail(of: CGSize(width: 120, height: 160), for: .mediaBox)

// 모든 페이지
let thumbnails = (0..<document.pageCount).compactMap {
    document.page(at: $0)?.thumbnail(of: CGSize(width: 120, height: 160), for: .mediaBox)
}

SwiftUI 통합

SwiftUI에서 사용하기 위해 PDFView를 UIViewRepresentable로 래핑합니다.

import SwiftUI
import PDFKit

struct PDFKitView: UIViewRepresentable {
    let document: PDFDocument

    func makeUIView(context: Context) -> PDFView {
        let pdfView = PDFView()
        pdfView.autoScales = true
        pdfView.displayMode = .singlePageContinuous
        pdfView.document = document
        return pdfView
    }

    func updateUIView(_ pdfView: PDFView, context: Context) {
        if pdfView.document !== document {
            pdfView.document = document
        }
    }
}

사용 예

struct DocumentScreen: View {
    let url: URL

    var body: some View {
        if let document = PDFDocument(url: url) {
            PDFKitView(document: document)
                .ignoresSafeArea()
        } else {
            ContentUnavailableView("PDF를 불러올 수 없습니다", systemImage: "doc.questionmark")
        }
    }
}

페이지 추적, 주석 히트 감지 및 코디네이터 패턴을 사용한 대화형 래퍼는 references/pdfkit-patterns.md를 참조하세요.

페이지 오버레이 (iOS 16+)

PDFPageOverlayViewProvider는 개별 페이지 위에 UIKit 뷰를 배치하여 표준 주석 이상의 대화형 컨트롤 또는 사용자 지정 렌더링을 가능하게 합니다.

class OverlayProvider: NSObject, PDFPageOverlayViewProvider {
    func pdfView(_ view: PDFView, overlayViewFor page: PDFPage) -> UIView? {
        let overlay = UIView()
        // 사용자 지정 하위 뷰 추가
        return overlay
    }
}

pdfView.pageOverlayViewProvider = overlayProvider

일반적인 실수

하지 말 것: PDFDocument 초기화를 강제 언래핑

PDFDocument(url:) 및 PDFDocument(data:)는 실패 가능한 초기화 메서드입니다.

// 잘못된 방법
let document = PDFDocument(url: url)!

// 올바른 방법
guard let document = PDFDocument(url: url) else { return }

하지 말 것: PDFView에서 autoScales를 잊음

autoScales가 없으면 PDF가 기본 해상도로 렌더링됩니다.

// 잘못된 방법
pdfView.document = document

// 올바른 방법
pdfView.autoScales = true
pdfView.document = document

하지 말 것: 주석에서 PDF 좌표계를 무시

PDF 페이지 좌표는 왼쪽 하단을 원점으로 Y축이 위쪽으로 증가합니다. UIKit과 반대입니다.

// 잘못된 방법: UIKit 좌표
let bounds = CGRect(x: 50, y: 50, width: 200, height: 30)

// 올바른 방법: PDF 좌표 (원점 왼쪽 하단)
let pageBounds = page.bounds(for: .mediaBox)
let pdfY = pageBounds.height - 50 - 30
let bounds = CGRect(x: 50, y: pdfY, width: 200, height: 30)

하지 말 것: 백그라운드 스레드에서 주석 수정

PDFKit 클래스는 스레드 안전하지 않습니다.

// 잘못된 방법
DispatchQueue.global().async { page.addAnnotation(annotation) }

// 올바른 방법
DispatchQueue.main.async { page.addAnnotation(annotation) }

하지 말 것: UIViewRepresentable에서 PDFDocument를 ==로 비교

PDFDocument는 참조 타입입니다. !==(identity)를 사용하세요.

// 잘못된 방법: 항상 문서를 교체
func updateUIView(_ pdfView: PDFView, context: Context) {
    pdfView.document = document
}

// 올바른 방법
func updateUIView(_ pdfView: PDFView, context: Context) {
    if pdfView.document !== document {
        pdfView.document = document
    }
}

리뷰 체크리스트

  • PDFDocument 초기화는 옵셔널 바인딩을 사용하고 강제 언래핑하지 않음
  • pdfView.autoScales = true가 적절한 초기 표시를 위해 설정됨
  • 페이지 인덱스가 접근 전에 pageCount와 비교 확인됨
  • displayMode 및 displayDirection이 디자인에 맞게 구성됨
  • 주석이 PDF 좌표 공간(원점 왼쪽 하단, Y 위쪽)을 사용함
  • 모든 PDFKit 변경 작업이 메인 스레드에서 수행됨
  • 암호로 보호된 PDF가 isLocked / unlock(withPassword:)로 처리됨
  • SwiftUI 래퍼가 updateUIView에서 !== identity 검사를 사용함
  • PDFViewPageChanged 알림이 페이지 추적을 위해 관찰됨
  • PDFThumbnailView.pdfView가 메인 PDFView에 연결됨
  • 대용량 문서 검색이 델리게이트와 함께 beginFindString을 사용함
  • 저장된 문서가 암호화가 필요할 때 write(to:withOptions:)를 사용함

참고 자료

Individual skills in this repo

This repo contains 11 individual skills — each has its own dedicated page.

JordanCoin/ios-skills-collection

Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries. Use when the user asks about GitHub issues, pull requests, workflows, or wants to interact with GitHub repositories from the command line — including tasks like check CI status, create PR, list issues, or query the GitHub API.

JordanCoin/ios-skills-collection

Create media playback experiences using AVKit. Use when adding video players with AVPlayerViewController, enabling Picture-in-Picture, routing media with AirPlay, using SwiftUI VideoPlayer views, configuring transport controls, displaying subtitles and closed captions, or integrating AVFoundation playback with system UI.

JordanCoin/ios-skills-collection

Integrate and optimize Core ML models in iOS apps for on-device machine learning inference. Covers model loading (.mlmodelc, .mlpackage), predictions with auto-generated classes and MLFeatureProvider, compute unit configuration (CPU, GPU, Neural Engine), MLTensor, VNCoreMLRequest, MLComputePlan, multi-model pipelines, and deployment strategies. Use when loading Core ML models, making predictions, configuring compute units, or profiling model performance.

JordanCoin/ios-skills-collection

Implement, review, or improve maps and location features in iOS/macOS apps using MapKit and CoreLocation. Use when working with Map views, annotations, markers, polylines, user location tracking, geocoding, reverse geocoding, search/autocomplete, directions and routes, geofencing, region monitoring, CLLocationUpdate async streams, or location authorization flows. Also use when working with maps, coordinates, addresses, places, directions, distance calculations, or location-based features in Swift apps.

JordanCoin/ios-skills-collection

Implement, review, or improve in-app tips and onboarding using Apple's TipKit framework. Use when adding feature discovery tooltips, onboarding flows, contextual tips, first-run experiences, coach marks, or working with Tip protocol, TipView, popoverTip, tip rules, tip events, or feature education UI.

JordanCoin/ios-skills-collection

When the user wants to optimize their Google Play Store listing — title, short description, full description, keywords, ratings, or Play Store-specific features. Use when the user mentions "Google Play", "Android", "Play Store", "Play Console", "short description", "full description indexed", "Google Play ASO", or wants Google Play-specific keyword, creative, or ratings strategy. For iOS App Store optimization, see aso-audit and metadata-optimization.

JordanCoin/ios-skills-collection

When the user wants to implement, optimize, or use App Clips for app discovery and conversion. Use when the user mentions "App Clip", "app clip code", "mini app", "instant app", "App Clip card", "App Clip link", "no download required", "instant experience", or wants to understand how App Clips appear in App Store search. For general App Store discoverability, see aso-audit. For marketing campaigns, see ua-campaign.

JordanCoin/ios-skills-collection

When the user wants to plan a launch strategy for a new app or major update. Also use when the user mentions "app launch", "launch plan", "launch checklist", "pre-launch", "launch day", or "how to launch my app". For ongoing ASO after launch, see aso-audit. For paid acquisition during launch, see ua-campaign.

JordanCoin/ios-skills-collection

When the user wants to analyze their own app's actual performance data from App Store Connect — real downloads, revenue, IAP, subscriptions, trials, or country breakdowns synced via Appeeky Connect. Use when the user asks about "my downloads", "my revenue", "how is my app performing", "ASC data", "sales and trends", "my subscription numbers", "App Store Connect metrics", or wants to compare periods or top markets. For third-party app estimates, see app-analytics. For subscription analytics depth, see monetization-strategy.

JordanCoin/ios-skills-collection

When the user wants a full ASO health audit, review their App Store listing quality, or diagnose why their app isn't ranking. Also use when the user mentions "ASO audit", "ASO score", "why am I not ranking", "listing review", or "optimize my app store page". For keyword-specific research, see keyword-research. For metadata writing, see metadata-optimization.

JordanCoin/ios-skills-collection

Always use when working on any iOS, macOS, Swift, SwiftUI, Xcode, or Apple platform project. Routes to the correct specialized skills based on development phase and task context. This collection contains 210 skills from 28 authors covering the full iOS lifecycle.

관련 스킬