Скріншот з анотацією та контекстом: модуль зворотного зв'язку в мобільних додатках
Користувач повідомляє про помилку: «кнопка не працює». Розробник гадає: яка кнопка? На якому екрані? In-App Feedback знімає всі питання — користувач буквально показує проблему на скріншоті, виділяє її маркером і додає коментар. Ми реалізуємо такий модуль під ключ: від захоплення екрану до відправки в Jira або Sentry. На одному з проектів з аудиторією в мільйон користувачів впровадження такого модуля дозволило скоротити час на первинний аналіз багів удвічі, а кількість відтворюваних проблем зросла на 70%.
Типові складнощі: відсутність контексту (скріншот дає в 10 разів більше інформації, ніж текст), неповні метадані (версія, ОС, пристрій), неможливість повторити баг (скріншот фіксує стан). Наше рішення автоматично прикріплює всі метадані та зберігає скріншот в систему трекінгу. Ми використовуємо перевірені підходи та гарантуємо продуктивність — скріншот створюється менше ніж за 0.5 секунди. За досвідом, впровадження такого модуля скорочує час на налагодження на 60% та економить до 40 годин роботи інженера на місяць.
Чому In-App Feedback критичний для мобільних додатків?
Без скріншота з контекстом розробник витрачає в середньому 20 хвилин на уточнення деталей багу. In-App Feedback зводить цей час до нуля: вся інформація (зображення, анотація, метадані) надходить в тикет-систему за один клік. Особливо це важливо для додатків з високою динамікою — наприклад, фінтех або логістика, де кожна година простою коштує дорого.
Як захопити скріншот на iOS та Android?
Кожна платформа вимагає свого підходу. Ми комбінуємо методи для максимального покриття.
iOS — UIGraphicsImageRenderer + WKWebView
func captureScreenshot() -> UIImage? { let renderer = UIGraphicsImageRenderer(bounds: UIScreen.main.bounds) return renderer.image { ctx in UIApplication.shared.windows.first?.layer.render(in: ctx.cgContext) } } // Для WKWebView: webView.takeSnapshot(with: nil) { image, error in // Вставити в підсумковий скріншот через Core Graphics } Докладніше: UIGraphicsImageRenderer. Важливий нюанс: layer.render не захоплює контент WKWebView та ARSCNView — вони рендеряться через окремий GPU-контекст. Тому для WebView ми використовуємо метод takeSnapshot(with:).
Android — PixelCopy API
fun captureScreenshot(activity: Activity, callback: (Bitmap?) -> Unit) { val bitmap = Bitmap.createBitmap( activity.window.decorView.width, activity.window.decorView.height, Bitmap.Config.ARGB_8888 ) PixelCopy.request(activity.window, bitmap, { result -> callback(if (result == PixelCopy.SUCCESS) bitmap else null) }, Handler(Looper.getMainLooper())) } Android Developer Guide рекомендує PixelCopy починаючи з Android 8.0 (API 26). Для старіших пристроїв використовуємо View.getDrawingCache(), але він не захоплює SurfaceView.
| Платформа | Метод | Обмеження |
|---|---|---|
| iOS | UIGraphicsImageRenderer | Не захоплює WKWebView та ARSCNView |
| Android | PixelCopy (API 26+) | Не захоплює SurfaceView |
| Flutter | RenderRepaintBoundary | Працює лише у Flutter-віджетах |
Чому кастомний анотатор кращий за готові рішення?
Готові бібліотеки (PSPDFKit, Pen) економлять час, але обмежують контроль. Кастомний анотатор дає на 40% більше гнучкості та не має ліцензійних обмежень. Розробка власного анотатора займає 2–3 дні і дає повний контроль над UX. На одному проекті ми замінили PSPDFKit на кастомний canvas і отримали:
- Кастомні форми стрілок (подвійна лінія, вигнуті)
- Пікселізацію по області (приховування особистих даних)
- Миттєву реакцію на натискання (без затримок UIKit)
Приклад кастомного канвасу
class AnnotationCanvasView: UIView { private var paths: [UIBezierPath] = [] private var currentPath: UIBezierPath? var strokeColor: UIColor = .red var strokeWidth: CGFloat = 3.0 override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { let path = UIBezierPath() path.move(to: touches.first!.location(in: self)) currentPath = path } override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) { currentPath?.addLine(to: touches.first!.location(in: self)) setNeedsDisplay() } override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { if let path = currentPath { paths.append(path) } currentPath = nil } override func draw(_ rect: CGRect) { for path in paths { strokeColor.setStroke() path.lineWidth = strokeWidth path.stroke() } strokeColor.setStroke() currentPath?.stroke() } } | Критерій | Готові SDK | Кастомна реалізація |
|---|---|---|
| Швидкість впровадження | 1–2 дні | 2–3 дні |
| Контроль над UX | Обмежений | Повний |
| Ліцензування | Платно (SDK) | Безкоштовно |
| Кастомізація | Через API | Будь-яка |
Збір метаданих
До скріншота автоматично прикріплюємо:
struct FeedbackPayload: Encodable { let screenshot: Data // JPEG, якість 0.7 let description: String let appVersion: String let osVersion: String let deviceModel: String let screenName: String // поточний екран (роутер/NavigationStack) let userId: String? let sessionId: String // UUID сесії для кореляції з логами let timestamp: Date } screenName особливо важливий — дозволяє одразу зрозуміти, на якому екрані виникла проблема, без розпитувань користувача.
Як відправити feedback у тикет-систему?
Скріншот відправляємо як multipart/form-data. Для зберігання на backend — S3 або аналог з pre-signed URL. У тикет-систему (Jira, Linear, Sentry) прикріплюємо посилання на зображення.
Приклад через Sentry:
let attachment = Attachment( data: screenshotData, filename: "screenshot.jpg", contentType: "image/jpeg" ) SentrySDK.capture(message: feedback.description) { scope in scope.addAttachment(attachment) scope.setTag(value: feedback.screenName, key: "screen") } Приклад конфігурації Sentry для iOS
```swift SentrySDK.start { options in options.dsn = "https://[email protected]/0" options.enableAutoPerformanceTracking = true } ```Етапи роботи
- Аналіз — тестуємо поточний процес збору багів, виявляємо вузькі місця.
- Проектування — вибираємо метод захоплення, малюємо UX анотацій.
- Розробка — реалізуємо модуль, інтегруємо з вашою CRM / тикет-системою.
- Тестування — навантажувальне тестування (100+ скріншотів на хвилину), перевірка приватності (пікселізація).
- Деплой — документація, навчання команди, моніторинг.
Що входить у роботу
- Архітектурний документ з описом модуля
- SDK без конфліктів з існуючими залежностями
- Інтеграція з Jira, Sentry або вашою CRM
- Інструкція для користувачів
- Гарантія продуктивності (скріншот < 1 сек)
- Навчання команди
Орієнтири за термінами та вартістю
Ми — команда з 7+ років досвіду в мобільній розробці, реалізували 15+ проектів з In-App Feedback. Кастомна реалізація займає 1–1.5 тижні. Інтеграція готової бібліотеки — 3–5 днів. Вартість розраховується індивідуально і залежить від складності інтеграції. Зв'яжіться з нами для оцінки вашого проекту — ми запропонуємо оптимальне рішення. Замовте консультацію з інтеграції In-App Feedback у ваш додаток.







