Реалізація Coach Marks (указівників) для навчання користувача в мобільному додатку
Coach marks — мгновенні контекстні пояснення: затемнений екран, підсвічений елемент, стрілка та одна фраза «Натисніть тут, щоб додати завдання». Відрізняються від walk-through тим, що не вимагають послідовного виконання дій — просто указують та пояснюють. Технічно простіше walk-through, але свої проблеми є.
Реалізація на iOS
Найнадійніший підхід — UIView-оверлей поверх window з маскуванням через CAShapeLayer. Створюємо синглтон CoachMarkManager або вбудовуємо в UIViewController-наслідник.
Отримання frame цільового елемента:
let globalFrame = targetView.convert(targetView.bounds, to: UIApplication.shared.keyWindow)
keyWindow застарів з iOS 13 — правильно: UIApplication.shared.connectedScenes.first(where: { $0.activationState == .foregroundActive }) як UIWindowScene, потім .windows.first(where: { $0.isKeyWindow }).
Маска overlay: UIBezierPath(rect: overlayView.bounds).appending(UIBezierPath(ovalIn: globalFrame.insetBy(dx: -8, dy: -8))) з fillRule = .evenOdd. Анімуємо появу через CABasicAnimation на opacity.
Бібліотеки. Instructions (ephread/Instructions) — зріла iOS бібліотека з CoachMarkController, підтримкою кастомних bubble-view та accessibility. EasyTipView для простих tooltip без overlay. Якщо дизайн стандартний — бібліотека секономить день. Якщо кастомний overlay з нестандартними формами highlight — пишемо сам.
У SwiftUI — anchorPreference(key:value:) + overlayPreferenceValue дозволяють розмістити overlay відносно довільного view без знання його координат заранее. Це правильний SwiftUI-way, але вимагає погруження в preference system. Альтернатива — GeometryReader + .coordinateSpace(name:) для отримання глобальних координат.
Android та Compose
На Android — бібліотека TapTargetView (KeepSafe) для Material-styled coach marks. Працює з звичайними View. Для Compose — Spotlight (TakuSemba) або кастомна реалізація через Popup + Canvas.
У Compose кастомна реалізація:
@Composable
fun CoachMarkOverlay(targetRect: Rect, text: String) {
Canvas(modifier = Modifier.fillMaxSize()) {
drawRect(color = Color.Black.copy(alpha = 0.7f))
drawCircle(
color = Color.Transparent,
radius = targetRect.size.minDimension / 2 + 12f,
center = targetRect.center,
blendMode = BlendMode.Clear
)
}
// Tooltip позиціонуємо через offset від targetRect
}
BlendMode.Clear вимагає graphicsLayer { compositingStrategy = CompositingStrategy.Offscreen } на батьківському контейнері — без цього Clear не працює корректно.
Послідовність та черговість coach marks
Якщо coach marks кілька — показуємо по одному. Черга [CoachMarkConfig] у CoachMarkManager. Після dismiss кожного — невелика задержка 300 мс перед наступним (користувачу потрібна секунда усвідомити збачене).
Умови показу: після конкретної дії користувача, після N-го запуску додатку, після оновлення до версії X. Логіка у CoachMarkScheduler — окремий об'єкт з UserDefaults-персистентністю. Ніяких умов у контролерах.
Доступність
Coach mark повинен бути виден VoiceOver/TalkBack. Overlay view — accessibilityViewIsModal = true на iOS (всі елементи під overlay зникнуть з accessibility tree). Текст підсказки — accessibilityLabel на bubble view. Dismiss-кнопка — accessibilityLabel = "Закрити підсказку". При VoiceOver фокус встановлюємо на bubble автоматично через UIAccessibility.post(notification: .screenChanged, argument: bubbleView).
Терміни: 1–3 дні. Один coach mark з простим highlight — 1 день. Система з чергою, конфігурацією з JSON, кастомними формами highlight та повною accessibility — 3 дні.







