Скріншот з анотацією та контекстом: модуль зворотного зв'язку в мобільних додатках
Користувач повідомляє про помилку: «кнопка не працює». Розробник гадає: яка кнопка? На якому екрані? 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 у ваш додаток.







