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







