Реализация генерации PDF в мобильном приложении
Клиенты часто просят выгрузить счёт или акт из приложения. Мы столкнулись с этой задачей в проекте для финансового сервиса: нужно было за 2 недели сделать генерацию invoice.pdf с логотипом, таблицей позиций и кириллицей. Подход выбрали разный для iOS и Android, но результат один. Мы занимаемся мобильной разработкой более 5 лет и реализовали генерацию PDF для 30+ приложений. Сейчас расскажем, как решаем такие задачи под ключ.
Каждый проект уникален по сложности вёрстки и требованиям к шрифтам, но общие принципы остаются неизменными. Выбор подхода упирается в сложность документа — простой текст плюс таблица решается Canvas API, а сложная вёрстка с изображениями и нестандартными шрифтами требует HTML → PDF. Сравним в таблице.
| Подход | iOS | Android | Сложность вёрстки | Рекомендация |
|---|---|---|---|---|
| Canvas API | UIGraphicsPDFRenderer | PdfDocument | Низкая-средняя | Простые счета, акты |
| HTML → PDF | WKWebView.createPDF | WebView + PrintManager | Высокая | Сложные отчёты, брошюры |
| Библиотеки (iText, OpenPDF) | – | iText 7 / OpenPDF | Любая | Коммерческие шаблоны |
Для документов с нестандартными шрифтами или сложной сеткой HTML-подход даёт больше гибкости, но требует осторожности с разрывами страниц. Canvas API, напротив, даёт полный контроль над пикселями, но каждый элемент приходится позиционировать вручную. Получите консультацию по вашему проекту — мы поможем подобрать оптимальный подход.
Как выбрать подход к генерации PDF?
Выбор упирается в сложность документа. Если нужен простой счёт с парой полей и таблицей — используйте Canvas API. Для многостраничных отчётов с логотипами, колонтитулами и произвольными шрифтами — HTML→PDF. У нас был случай, когда клиент хотел PDF с динамическими графиками; мы выбрали HTML+svg и конвертировали через WKWebView. Всё влезло на 3 страницы с автоматическими разрывами.
iOS: PDFKit и UIGraphicsPDFRenderer
На iOS два основных подхода.
UIGraphicsPDFRenderer — рисуем PDF как Canvas. Полный контроль над расположением, но каждый элемент надо размещать вручную. Вот пример генерации счёта:
func generateInvoicePDF(invoice: Invoice) -> Data {
let pageRect = CGRect(x: 0, y: 0, width: 595.2, height: 841.8) // A4 в pt
let renderer = UIGraphicsPDFRenderer(bounds: pageRect)
return renderer.pdfData { context in
context.beginPage()
let ctx = context.cgContext
// Заголовок
let titleAttrs: [NSAttributedString.Key: Any] = [
.font: UIFont.systemFont(ofSize: 18, weight: .bold),
.foregroundColor: UIColor.black
]
"СЧЁТ №\(invoice.number)".draw(at: CGPoint(x: 40, y: 40), withAttributes: titleAttrs)
// Таблица позиций
drawInvoiceTable(invoice.items, in: ctx, startY: 120, pageWidth: 595.2)
// Если контент не умещается — beginPage() для следующей страницы
}
}Рендеринг HTML → PDF. Сложную вёрстку (таблицы, колонки, изображения) проще описать HTML/CSS и сконвертировать через WKWebView. Пример конвертера:
class HTMLToPDFConverter: NSObject, WKNavigationDelegate {
private var webView: WKWebView!
func convert(html: String, completion: @escaping (Data?) -> Void) {
webView = WKWebView(frame: CGRect(x: 0, y: 0, width: 595, height: 842))
webView.navigationDelegate = self
webView.loadHTMLString(html, baseURL: nil)
self.completion = completion
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
let config = WKPDFConfiguration()
config.rect = CGRect(origin: .zero, size: CGSize(width: 595, height: 842))
webView.createPDF(configuration: config) { result in
self.completion?(try? result.get())
}
}
}WKWebView.createPDF (iOS 14+) — самый простой способ для сложной вёрстки. CSS @page правила управляют разрывами страниц (page-break-before: always), шрифтами, отступами.
PDFKit — для манипуляций с существующими PDF: добавить страницу, вставить аннотацию, слить несколько документов в один. PDFDocument, PDFPage, PDFAnnotation — простой API. Подробнее в PDFKit.
Android: PdfDocument и WebView
android.graphics.pdf.PdfDocument — нативная генерация через Canvas API, аналогично UIGraphicsPDFRenderer. Пример:
fun generatePdf(invoice: Invoice): ByteArray {
val document = PdfDocument()
val pageInfo = PdfDocument.PageInfo.Builder(595, 842, 1).create() // A4
val page = document.startPage(pageInfo)
val canvas = page.canvas
val paint = Paint().apply {
textSize = 18f
isFakeBoldText = true
}
canvas.drawText("СЧЁТ №${invoice.number}", 40f, 60f, paint)
drawInvoiceTable(canvas, invoice.items, startY = 120f)
document.finishPage(page)
val output = ByteArrayOutputStream()
document.writeTo(output)
document.close()
return output.toByteArray()
}WebView → PDF. WebView.createPrintDocumentAdapter + PrintManager — стандартный путь на Android. Но это открывает диалог печати, а не возвращает ByteArray. Для программной генерации — WebView с PrintDocumentAdapter через reflection или сторонние библиотеки.
Альтернатива — iText 7 (AGPL, платная для коммерции) или Apache PDFBox (Apache 2.0). PdfBox на Android — порт с некоторыми ограничениями, но для большинства задач достаточен. OpenPDF (форк iText 2.x, LGPL) — хороший баланс функциональности и лицензии. Подробнее см. OpenPDF на GitHub.
Почему важно встраивать шрифты?
Стандартные системные шрифты в PDF могут не включаться корректно при открытии на другом устройстве. Правильный подход — embed шрифт в PDF. На iOS: UIFont(name:size:) с bundled TTF-файлом. На Android с iText/OpenPDF: PdfFont.createFont("assets/fonts/Roboto-Regular.ttf", PdfEncodings.IDENTITY_H, true) — true означает embed в документ.
Без embed кириллица часто отображается как квадратики — до 40% пользователей могут столкнуться с этой проблемой, если шрифт не встроен. Решение: всегда встраивайте шрифты.
Шаринг PDF из приложения
После генерации — UIActivityViewController (iOS) или FileProvider + ACTION_SEND Intent (Android). Для сохранения в Files/Downloads: UIDocumentPickerViewController (iOS) или MediaStore.Downloads URI (Android 10+). Просмотр без внешнего приложения: PDFView из PDFKit (iOS), PdfRenderer (Android) — постраничный рендеринг из файла в Bitmap.
Типичные проблемы и их решения
| Проблема | Решение |
|---|---|
| Кириллица отображается квадратиками | Встроить шрифт с помощью embed=true |
| Разрыв страницы посередине таблицы | Использовать CSS page-break-inside: avoid или Canvas с ручным контролем |
| PDF не открывается на старых устройствах | Генерировать в формате PDF 1.4 или ниже |
Что входит в работу?
- Анализ макета и требований к PDF-документу.
- Выбор оптимального подхода (Canvas / HTML / библиотека).
- Реализация генерации с учётом вёрстки, шрифтов, кириллицы.
- Интеграция сохранения и шаринга.
- Тестирование на реальных устройствах (5+ моделей).
- Передача исходного кода и документации.
Сроки и стоимость
2–3 рабочих дня для стандартных документов (счёт, акт, отчёт). Сложная вёрстка с изображениями, таблицами и многостраничным flow — до 5 дней. Стоимость рассчитывается индивидуально. Получите консультацию по оптимальному подходу для вашей задачи. Свяжитесь с нами — оценим ваш проект за 2 дня.







