Генерация PDF-отчётов на Swift для iOS и macOS с PDFKit и Core Graphics

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Генерация PDF-отчётов на Swift для iOS и macOS

Создание PDF-документов — одна из частых задач в мобильных и десктопных приложениях. На платформах Apple (iOS, macOS) для этого доступны мощные встроенные инструменты: PDFKit и Core Graphics. В этой статье мы разберём, как сформировать многостраничный отчёт с таблицами, текстом и графикой, используя только нативный Swift.

Почему стоит использовать встроенные инструменты?

  • Не требует сторонних зависимостей — всё уже включено в SDK.
  • Полный контроль над рендерингом — работа с координатами, шрифтами, цветами.
  • Кроссплатформенность — код работает и на iOS, и на macOS.
  • Высокая производительность — Core Graphics использует аппаратное ускорение.

Основные подходы

Существует два основных способа генерации PDF на Swift:

  1. PDFKit — высокоуровневый API для работы с PDF (чтение, редактирование, создание).
  2. Core Graphics — низкоуровневый рендеринг через контекст PDF.

Мы рассмотрим второй подход, так как он даёт максимальную гибкость при создании отчётов «с нуля».

Создание PDF-контекста

Первый шаг — создать PDF-контекст, в который будем рисовать элементы. Для этого используется функция UIGraphicsBeginPDFContextToFile (iOS) или её аналог для macOS.

import UIKit

func generatePDF() -> URL? {
    let fileName = "Report.pdf"
    let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
    guard let documentsURL = paths.first else { return nil }
    let pdfURL = documentsURL.appendingPathComponent(fileName)
    
    let pageSize = CGRect(x: 0, y: 0, width: 612, height: 792) // A4
    UIGraphicsBeginPDFContextToFile(pdfURL.path, pageSize, nil)
    
    // Здесь будем рисовать страницы
    UIGraphicsEndPDFContext()
    return pdfURL
}

Добавление страниц и контента

Каждая страница начинается с вызова UIGraphicsBeginPDFPage(). После этого можно использовать стандартные методы UIKit для рисования.

func drawReport() {
    guard let pdfURL = generatePDF() else { return }
    
    UIGraphicsBeginPDFContextToFile(pdfURL.path, CGRect(x: 0, y: 0, width: 612, height: 792), nil)
    
    // Первая страница
    UIGraphicsBeginPDFPage()
    drawHeader(title: "Ежемесячный отчёт")
    drawTable(data: sampleData)
    
    // Вторая страница (если нужно)
    UIGraphicsBeginPDFPage()
    drawChart()
    
    UIGraphicsEndPDFContext()
}

Рисование текста и заголовков

Для отрисовки текста используем NSString с атрибутами.

func drawHeader(title: String) {
    let titleAttributes: [NSAttributedString.Key: Any] = [
        .font: UIFont.boldSystemFont(ofSize: 24),
        .foregroundColor: UIColor.darkGray
    ]
    title.draw(at: CGPoint(x: 50, y: 40), withAttributes: titleAttributes)
}

Создание простой таблицы

Таблицу можно нарисовать с помощью линий и текста. Ниже пример генерации таблицы с двумя колонками.

func drawTable(data: [(String, String)]) {
    let startX: CGFloat = 50
    var startY: CGFloat = 100
    let colWidth: CGFloat = 250
    let rowHeight: CGFloat = 30
    
    // Рисуем заголовки
    let headerAttributes: [NSAttributedString.Key: Any] = [
        .font: UIFont.boldSystemFont(ofSize: 14),
        .foregroundColor: UIColor.black
    ]
    "Название".draw(at: CGPoint(x: startX, y: startY), withAttributes: headerAttributes)
    "Значение".draw(at: CGPoint(x: startX + colWidth, y: startY), withAttributes: headerAttributes)
    
    startY += rowHeight
    
    // Рисуем строки
    for (key, value) in data {
        let rowAttributes: [NSAttributedString.Key: Any] = [
            .font: UIFont.systemFont(ofSize: 12),
            .foregroundColor: UIColor.darkGray
        ]
        key.draw(at: CGPoint(x: startX, y: startY), withAttributes: rowAttributes)
        value.draw(at: CGPoint(x: startX + colWidth, y: startY), withAttributes: rowAttributes)
        
        // Рисуем разделительную линию
        let context = UIGraphicsGetCurrentContext()
        context?.setStrokeColor(UIColor.lightGray.cgColor)
        context?.setLineWidth(0.5)
        context?.move(to: CGPoint(x: startX, y: startY + rowHeight))
        context?.addLine(to                    

Рекомендаций