משתמשים מצפים לכפתור 'הורד PDF', אבל מפתחים מתמודדים עם מגבלות זיכרון של המכשיר, יצירה אסינכרונית ופריסה מורכבת של עמודים מרובים. לאחרונה, לקוח פנה אלינו עם דוח של 5,000 שורות — באנדרואיד זה גרם ל-OutOfMemoryError, ומיהרנו לבנות מחדש את הארכיטקטורה ליצירה בצד השרת. לצוות שלנו יש ניסיון של 5+ שנים ביישום מערכות יצירת דוחות לאפליקציות מובייל — אנו מבטיחים פעולה יציבה גם עם נפחי נתונים גדולים. נבחן את הפרויקט שלך ונציע פתרון חסכוני.
ללא גישה נכונה, דוחות עלולים לצרוך יותר מדי זיכרון, לגרום לשגיאות בפתיחה, או להציג מלבנים ריקים במקום אותיות עבריות. דרכנו על המגרפות האלה בעשרות פרויקטים ואנחנו יודעים איך להימנע מהמלכודות האופייניות. כפי שאפל ממליצה: השתמשו ב-UIActivityViewController כדי לאפשר למשתמשים לייצא קבצים.
כיצד ליישם יצירת דוחות באפליקציית מובייל
יישום מוצלח דורש בחירת גישה, עיצוב נתונים, רינדור וייצוא לפורמט הנדרש. בואו נבחן שלוש דרכים עיקריות ליצירת PDF.
שלוש גישות ליצירת PDF
רינדור מקורי באמצעות Canvas
ב-iOS — UIGraphicsPDFRenderer, האפשרות הכי ניתנת לשליטה. ציירו כל אלמנט ידנית באמצעות Core Graphics:
func generateReport(data: ReportData) -> Data {
let pageRect = CGRect(x: 0, y: 0, width: 595, height: 842) // A4 в points
let renderer = UIGraphicsPDFRenderer(bounds: pageRect)
return renderer.pdfData { context in
context.beginPage()
let ctx = context.cgContext
// Заголовок
let titleFont = UIFont.boldSystemFont(ofSize: 18)
let title = data.title as NSString
title.draw(at: CGPoint(x: 40, y: 40), withAttributes: [
.font: titleFont,
.foregroundColor: UIColor.black
])
// Таблица данных
drawTable(ctx, rows: data.rows, startY: 80, pageWidth: pageRect.width)
// Если данные не помещаются — новая страница
if needsNewPage {
context.beginPage()
// продолжаем...
}
}
}עמל רב, אבל התוצאה היא שליטה מדויקת בפלט הפיקסלים. מתאים לתבנית דוח קבועה.
באנדרואיד באמצעות func generateReport(data: ReportData) -> Data { let pageRect = CGRect(x: 0, y: 0, width: 595, height: 842) // A4 в points let renderer = UIGraphicsPDFRenderer(bounds: pageRect) return renderer.pdfData { context in context.beginPage() let ctx = context.cgContext // Заголовок let titleFont = UIFont.boldSystemFont(ofSize: 18) let title = data.title as NSString title.draw(at: CGPoint(x: 40, y: 40), withAttributes: [ .font: titleFont, .foregroundColor: UIColor.black ]) // Таблица данных drawTable(ctx, rows: data.rows, startY: 80, pageWidth: pageRect.width) // Если данные не помещаются — новая страница if needsNewPage { context.beginPage() // продолжаем... } } } :
val document = PdfDocument()
val pageInfo = PdfDocument.PageInfo.Builder(595, 842, 1).create()
val page = document.startPage(pageInfo)
val canvas = page.canvas
val paint = Paint().apply {
textSize = 18f
isFakeBoldText = true
}
canvas.drawText(data.title, 40f, 60f, paint)
document.finishPage(page)
val stream = ByteArrayOutputStream()
document.writeTo(stream)
document.close() HTML → PDF באמצעות WebView
צרו תבנית HTML והמירו אותה ל-PDF. קל יותר לפריסות מורכבות, טבלאות ובלוקי טקסט — HTML/CSS גמיש יותר מ-Canvas. ב-Flutter השתמשו בחבילת PdfDocument + val document = PdfDocument() val pageInfo = PdfDocument.PageInfo.Builder(595, 842, 1).create() val page = document.startPage(pageInfo) val canvas = page.canvas val paint = Paint().apply { textSize = 18f; isFakeBoldText = true } canvas.drawText(data.title, 40f, 60f, paint) document.finishPage(page) val stream = ByteArrayOutputStream() document.writeTo(stream) document.close() :
import 'package:pdf/widgets.dart' as pw;
import 'package:printing/printing.dart';
Future<Uint8List> buildPdf(ReportData data) async {
final doc = pw.Document();
final font = await PdfGoogleFonts.notoSansRegular();
final boldFont = await PdfGoogleFonts.notoSansBold();
doc.addPage(
pw.MultiPage(
pageFormat: PdfPageFormat.a4,
build: (context) => [
pw.Header(
text: data.title,
textStyle: pw.TextStyle(font: boldFont, fontSize: 18),
),
pw.SizedBox(height: 16),
pw.TableHelper.fromTextArray(
headers: data.headers,
data: data.rows,
cellStyle: pw.TextStyle(font: font, fontSize: 10),
),
],
),
);
return doc.save();
}printing מתחלק אוטומטית לעמודים — ופותר את הבעיה המרכזית של דוחות ארוכים.
יצירה בצד השרת (מומלץ לדוחות מורכבים)
דוחות עם נפחי נתונים גדולים, תרשימים מורכבים או מיתוג תאגידי עדיף ליצור בשרת. Puppeteer (Node.js) מרנדר HTML + תרשימים ל-PDF עם דיוק של מנוע דפדפן. הלקוח מקבל קישור או קובץ דרך WebSocket לאחר השלמת היצירה.
זו הדרך הנכונה היחידה אם הדוח דורש תרשימים אינטראקטיביים (Echarts, Highcharts) — אי אפשר לרנדר אותם במכשיר ללא WebView.
| פרמטר | Canvas מקורי | HTML → PDF WebView | יצירה בצד השרת |
|---|---|---|---|
| מורכבות היישום | גבוהה | בינונית | נמוכה (לקוח) |
| שליטה בפריסה | מלאה | תלויה ב-CSS | תלויה ב-HTML/CSS |
| נפח נתונים | עד 50 עמודים | עד 100 עמודים | ללא הגבלה |
| תרשימים אינטראקטיביים | לא | דרך WebView | כן (Puppeteer) |
| זיכרון מכשיר | גבוה | בינוני | נמוך |
כדי לבחור את הפתרון האופטימלי, צרו קשר — ננתח את הנתונים שלכם ונציע גישה.
באיזה פורמט דוח לבחור?
מלבד PDF, לעתים קרובות נדרשים CSV ו-Excel. CSV הוא הקל ביותר: הנתונים נכתבים בשורות המופרדות בתווים. הניואנס המרכזי הוא קידוד: Excel פותח CSV ב-UTF-8 רק עם BOM (pdf). אחרת, עברית הופכת לגיבריש. Excel (xlsx) מורכב יותר: צריך ספרייה שתומכת בנוסחאות, סגנונות ומספר גיליונות. לטבלאות פשוטות, CSV מספיק. בפועל, CSV עם 10,000 שורות נוצר במכשיר תוך פחות משנייה, בעוד PDF עם אותו מספר שורות דורש חלוקה לעמודים.
| פורמט | מורכבות היישום | תמיכה בסגנונות | גודל קובץ | אוניברסליות |
|---|---|---|---|---|
| גבוהה | כן (פריסה קבועה) | בינוני | גבוהה (נפתח בכל מקום) | |
| CSV | נמוכה | לא | קטן | בינונית (Excel, Numbers) |
| Excel | בינונית | כן (נוסחאות, צבעים) | גדול | גבוהה (Excel, Google Sheets) |
מה לבחור: רינדור מקורי או HTML ל-PDF?
הבחירה תלויה במורכבות הפריסה ונפח הנתונים. לתבנית קבועה עם מספר קטן של עמודים, רינדור מקורי מתאים. לטבלאות ותרשימים מורכבים — HTML ל-PDF או שרת. הניסיון שלנו מראה ש-80% מהפרויקטים משתמשים בגישה היברידית: דוחות פשוטים במכשיר, מורכבים בשרת. נעזור לכם לבחור את הפתרון האופטימלי.
ייצוא CSV
פשוט יותר מ-PDF, אבל יש ניואנסים של קידוד. Excel מצפה ל-UTF-8 עם BOM — אחרת עברית מוצגת כגיבריש. ב-Flutter:
String buildCsv(List<List<dynamic>> rows) {
final buffer = StringBuffer();
buffer.write('\uFEFF'); // UTF-8 BOM для корректного открытия в Excel
for (final row in rows) {
buffer.writeln(row.map((cell) {
final str = cell.toString();
// Экранируем ячейки с запятыми и кавычками
return str.contains(',') || str.contains('"') ? '"${str.replaceAll('"', '""')}"' : str;
}).join(','));
}
return buffer.toString();
} שיתוף הקובץ שנוצר
import 'package:pdf/widgets.dart' as pw; import 'package:printing/printing.dart'; Future<Uint8List> buildPdf(ReportData data) async { final doc = pw.Document(); final font = await PdfGoogleFonts.notoSansRegular(); final boldFont = await PdfGoogleFonts.notoSansBold(); doc.addPage( pw.MultiPage( pageFormat: PdfPageFormat.a4, build: (context) => [ pw.Header(text: data.title, textStyle: pw.TextStyle(font: boldFont, fontSize: 18)), pw.SizedBox(height: 16), pw.TableHelper.fromTextArray( headers: data.headers, data: data.rows, cellStyle: pw.TextStyle(font: font, fontSize: 10), ), ], ), ); return doc.save(); } ב-Flutter, pw.MultiPage ב-iOS, \uFEFF + String buildCsv(List<List<dynamic>> rows) { final buffer = StringBuffer(); buffer.write('\uFEFF'); // UTF-8 BOM для корректного открытия в Excel for (final row in rows) { buffer.writeln(row.map((cell) { final str = cell.toString(); // Экранируем ячейки с запятыми и кавычками return str.contains(',') || str.contains('"') ? '"${str.replaceAll('"', '""')}"' : str; }).join(',')); } return buffer.toString(); } באנדרואיד. שמירה לגלריה / קבצים — דרך share_plus + UIActivityViewController.
ב-iOS צריך FileProvider ב-Info.plist לשמירה לתמונות, Intent.ACTION_SEND לגישה לקבצים דרך Files.app.
אילו בעיות אופייניות מתרחשות במהלך יצירת דוחות?
יצירת PDF עם טבלה של 10,000 שורות במכשיר — path_provider באנדרואיד או אזהרת זיכרון ב-iOS. פתרון: חלוקת נתונים לעמודים, יצירה עמוד אחר עמוד, לנפחים גדולים — שרת.
עברית ב-PDF ללא גופן מוטמע — מלבנים לבנים במקום אותיות. הקפידו להטמיע גופן מותאם עם תמיכה ב-Unicode דרך open_filex.
בעיות עם Excel: אם משתמשים בספרייה כמו NSPhotoLibraryAddUsageDescription (Dart), אל תשכחו סגנונות שורות — אחרת הקובץ לא ייפתח בגרסאות ישנות של Excel. אנו ממליצים תמיד לבדוק על גרסאות אמיתיות של חבילות משרדיות.
שלבים ליישום יצירת דוחות
- ניתוח נתונים ופריסות: קביעת הפורמט, הנפח וסוג הדוחות.
- בחירת גישה: מקורי, HTML→PDF או צד שרת.
- פיתוח תבניות עם פריסה והטמעת גופנים.
- שילוב ייצוא ושיתוף.
- בדיקה עם נתונים אמיתיים (ריקים, גדולים, עם עברית).
- אופטימיזציה של זיכרון וביצועים.
מה כלול בעבודה
- בחירת גישה לפי מאפייני הנתונים והפריסה (מקורי / HTML→PDF / שרת)
- יישום תבניות דוחות (PDF/CSV/Excel)
- שילוב שיתוף ושמירת קבצים
- טיפול במקרי קצה: נתונים ריקים, נפחים גדולים, מעברי עמודים
לוחות זמנים
תבנית דוח PDF אחת עם עיצוב בסיסי: 2–3 ימים. מספר סוגי דוחות עם תרשימים ויצירה בצד השרת: 1–2 שבועות. העלות מחושבת באופן אישי. קבלו ייעוץ לפרויקט שלכם — ניישם מערכת דוחות מלאה. הזמינו פיתוח, ואנו נציע את הפתרון האופטימלי בהתחשב בנתונים ובתקציב שלכם.
מידע נוסף על ספריות Excel
עבור Flutter, השתמשו בחבילת UIFileSharingEnabled (גרסה 4.x) — היא תומכת בסגנונות, מיזוג תאים ונוסחאות. עבור אנדרואיד — Apache POI (דרך JNI או שרת). עבור iOS — SwiftExcel או xlsxwriter. הבחירה תלויה במורכבות הדוח ובזמן הפיתוח.







