In the world of mobile development, displaying PDF documents within an app is a common requirement. For iOS development using Swift, SwiftUI offers a declarative way to create user interfaces, including a PDF viewer. In this comprehensive guide, we will explore how to create a SwiftUI PDF viewer app, leveraging the power of PDFKit and UIKit integration.
What is SwiftUI?
SwiftUI is Apple’s modern UI framework for building user interfaces across all Apple platforms. It provides a declarative syntax that makes code easier to read and write. SwiftUI works seamlessly with existing UIKit code, allowing developers to integrate newer SwiftUI components into older UIKit-based apps, and vice versa.
Why Use SwiftUI for PDF Viewer?
- Declarative Syntax: Easier to reason about and maintain UI code.
- Live Preview: Instant feedback during development.
- Cross-Platform Compatibility: Build apps for iOS, macOS, watchOS, and tvOS with shared code.
- Integration with UIKit: Reuse existing PDFKit functionalities within SwiftUI.
Setting up a SwiftUI PDF Viewer App
To start, create a new Xcode project with the SwiftUI interface.
Step 1: Create a New Xcode Project
- Open Xcode and select “Create a new Xcode project.”
- Choose “iOS” and then “App.”
- Give your project a name, e.g., “SwiftUI PDF Viewer,” and select “SwiftUI” as the interface.
- Choose a location to save your project and click “Create.”
Step 2: Import Necessary Frameworks
Import PDFKit into your SwiftUI view. PDFKit is a framework that provides features to display, create, and manipulate PDF documents.
import SwiftUI
import PDFKit
Step 3: Create a PDFKitView Representable
Since PDFKit is part of UIKit, we need to create a UIViewRepresentable for SwiftUI to work with UIKit views. The PDFKitView will handle the display of the PDF document.
import SwiftUI
import PDFKit
struct PDFKitView: UIViewRepresentable {
let url: URL
func makeUIView(context: Context) -> PDFView {
let pdfView = PDFView()
pdfView.document = PDFDocument(url: url)
pdfView.autoScales = true // Enable auto scaling for better viewing
return pdfView
}
func updateUIView(_ uiView: PDFView, context: Context) {
uiView.document = PDFDocument(url: url)
}
}
In this code:
PDFKitViewconforms toUIViewRepresentable, making it compatible with SwiftUI.makeUIView(context:)creates and configures thePDFViewwith the PDF document loaded from the specified URL.updateUIView(_:context:)updates thePDFViewif the URL changes.autoScales = trueenables auto scaling, ensuring that the PDF fits nicely within the view.
Step 4: Embed PDFKitView in SwiftUI View
Now, create a SwiftUI view that uses the PDFKitView to display the PDF document.
struct ContentView: View {
let pdfURL = Bundle.main.url(forResource: "sample", withExtension: "pdf")! // Replace "sample.pdf" with your PDF file name
var body: some View {
PDFKitView(url: pdfURL)
.navigationTitle("PDF Viewer")
}
}
In this code:
ContentViewis a SwiftUI view that holds thePDFKitView.pdfURLloads a sample PDF from the app’s bundle. You should replace"sample.pdf"with the name of your PDF file.PDFKitViewis initialized with the URL of the PDF document.navigationTitlesets the title of the navigation bar.
Step 5: Add PDF File to Project
Add a PDF file to your Xcode project.
- Drag and drop your PDF file (e.g.,
sample.pdf) into the Xcode project navigator. - Make sure “Copy items if needed” is checked.
- Ensure the PDF is included in your app’s target.
Step 6: Enable Navigation in App File
Update the App file to embed the ContentView in a NavigationView, which will display the title.
@main
struct PDF_ViewerApp: App {
var body: some Scene {
WindowGroup {
NavigationView {
ContentView()
}
}
}
}
Customizing the PDF Viewer
You can add more features to enhance the PDF viewer app, such as zooming, scrolling, and searching.
Adding Zoom Functionality
PDFKit’s PDFView supports zooming. However, to control the zoom level via SwiftUI, you might use a @State variable and a slider.
import SwiftUI
import PDFKit
struct PDFKitView: UIViewRepresentable {
let url: URL
@Binding var zoomScale: CGFloat // Add zoomScale binding
func makeUIView(context: Context) -> PDFView {
let pdfView = PDFView()
pdfView.document = PDFDocument(url: url)
pdfView.autoScales = false // Disable auto scaling to manage zoom manually
return pdfView
}
func updateUIView(_ uiView: PDFView, context: Context) {
uiView.document = PDFDocument(url: url)
uiView.scale(to: zoomScale, anchor: CGPoint(x: 0, y: 0)) // Set the zoom scale
}
}
struct ContentView: View {
let pdfURL = Bundle.main.url(forResource: "sample", withExtension: "pdf")!
@State private var zoomScale: CGFloat = 1.0 // Initial zoom scale
var body: some View {
VStack {
PDFKitView(url: pdfURL, zoomScale: $zoomScale)
.navigationTitle("PDF Viewer")
Slider(value: $zoomScale, in: 0.5...3.0, step: 0.1) // Zoom slider
.padding()
}
}
}
Key improvements and explanations:
@Binding var zoomScale: CGFloatinPDFKitViewallows you to control the zoom level from the parent view (ContentView).autoScales = falseis set inmakeUIViewbecause you will be managing the scaling manually.uiView.scale(to: zoomScale, anchor: CGPoint(x: 0, y: 0))applies the zoom level to thePDFView.@State private var zoomScale: CGFloat = 1.0inContentViewinitializes and holds the zoom level.- A
Slideris added to theContentViewto control the zoom level.
Adding Page Navigation
Add buttons to navigate between pages in the PDF.
import SwiftUI
import PDFKit
struct PDFKitView: UIViewRepresentable {
let url: URL
@Binding var currentPage: Int
@Binding var document: PDFDocument?
func makeUIView(context: Context) -> PDFView {
let pdfView = PDFView()
if let document = PDFDocument(url: url) {
pdfView.document = document
self.document = document
}
pdfView.autoScales = true
return pdfView
}
func updateUIView(_ uiView: PDFView, context: Context) {
guard let document = document else { return }
if currentPage >= 1 && currentPage <= document.pageCount {
if let page = document.page(at: currentPage - 1) {
uiView.goTo(page)
}
}
}
}
struct ContentView: View {
let pdfURL = Bundle.main.url(forResource: "sample", withExtension: "pdf")!
@State private var currentPage: Int = 1
@State private var document: PDFDocument? = nil
var body: some View {
VStack {
HStack {
Button(action: {
if currentPage > 1 {
currentPage -= 1
}
}) {
Text("Previous")
}
Text("Page (currentPage)")
Button(action: {
if let document = document, currentPage < document.pageCount {
currentPage += 1
}
}) {
Text("Next")
}
}
.padding()
PDFKitView(url: pdfURL, currentPage: $currentPage, document: $document)
.navigationTitle("PDF Viewer")
}
}
}
Improvements and explanations:
@Binding var currentPage: Intand@Binding var document: PDFDocument?are added toPDFKitViewto manage and update the current page and document.- In
updateUIView, the current page is updated, ensuring it is within the valid page range. - Previous and Next buttons are added in
ContentViewto navigate between pages. - The
currentPagestate variable holds the current page number, and the document is stored so you can get total page count.
Handling PDF Loading Errors
Implement error handling to gracefully handle PDF loading failures.
import SwiftUI
import PDFKit
struct PDFKitView: UIViewRepresentable {
let url: URL
@Binding var pdfLoadError: Bool
func makeUIView(context: Context) -> PDFView {
let pdfView = PDFView()
if let document = PDFDocument(url: url) {
pdfView.document = document
pdfView.autoScales = true
pdfLoadError = false // Reset error if loaded successfully
} else {
pdfLoadError = true // Set error if PDFDocument fails
}
return pdfView
}
func updateUIView(_ uiView: PDFView, context: Context) {
uiView.document = PDFDocument(url: url)
}
}
struct ContentView: View {
let pdfURL = Bundle.main.url(forResource: "sample", withExtension: "pdf")!
@State private var pdfLoadError: Bool = false
var body: some View {
VStack {
if pdfLoadError {
Text("Failed to load PDF. Please try again.")
.foregroundColor(.red)
.padding()
} else {
PDFKitView(url: pdfURL, pdfLoadError: $pdfLoadError)
.navigationTitle("PDF Viewer")
}
}
}
}
Key changes:
@Binding var pdfLoadError: BoolinPDFKitViewis used to report if there's a PDF loading error.- The
makeUIViewfunction now setspdfLoadErrorbased on whether the PDF loads successfully. - In
ContentView, the app checkspdfLoadErrorand displays an error message if needed.
Conclusion
Creating a SwiftUI PDF viewer app involves integrating PDFKit through UIViewRepresentable. With SwiftUI's declarative syntax and PDFKit’s robust functionalities, you can build a feature-rich and efficient PDF viewer app. This comprehensive guide covered the basics of setting up a PDF viewer, adding zooming, implementing page navigation, and handling loading errors, giving you a solid foundation for more advanced features.