Скриншот с аннотацией и контекстом: модуль обратной связи в мобильных приложениях
Пользователь сообщает об ошибке: «кнопка не работает». Разработчик гадает: какая кнопка? На каком экране? 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 в ваше приложение.







