Реализация Privacy Policy и Terms of Service экранов в мобильном приложении
Экраны Privacy Policy и Terms of Service — это не просто «открыть WebView с URL». App Store и Google Play имеют конкретные требования к их реализации, и приложения регулярно получают отказы в ревью из-за неправильной интеграции.
Требования App Store и Google Play
Apple требует ссылку на Privacy Policy в App Store Connect — без неё приложение с IAP или запросом чувствительных данных не пройдёт ревью (Guideline 5.1.1). Внутри приложения — при запросе персональных данных должна быть доступна ссылка на PP.
Google Play требует Privacy Policy URL в консоли разработчика + наличие ссылки внутри приложения, если оно запрашивает разрешения, использует данные о детях или финансовые данные.
Экран при первом запуске
Первый запуск — критическая точка. Нельзя показывать согласие как чекбокс «принять всё» без возможности ознакомиться с текстом. Нельзя делать кнопку «Принять» единственной активной, пока пользователь не проскроллил документ — это тёмный паттерн, который регуляторы всё чаще преследуют.
// Минимально корректная реализация
class ConsentScreen : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.btnAccept.isEnabled = false // недоступна до прочтения
binding.scrollView.setOnScrollChangeListener { _, _, scrollY, _, _ ->
val contentHeight = binding.scrollContent.height
val scrollHeight = binding.scrollView.height
// Активируем после достижения конца
if (scrollY >= contentHeight - scrollHeight - 50) {
binding.btnAccept.isEnabled = true
}
}
binding.linkPrivacyPolicy.setOnClickListener {
openDocument(DocumentType.PRIVACY_POLICY)
}
}
}
Хранить факт принятия нужно с версией документа и timestamp:
data class AcceptanceRecord(
val documentType: String, // "privacy_policy", "terms"
val version: String, // "2.1.0"
val acceptedAt: Long, // unix timestamp
val userId: String?
)
При обновлении документа — повторное принятие только если изменения существенные. Фиксируем document_version в таблице пользователя на сервере.
WebView vs нативный экран
Загрузка из URL через WebView — гибко (обновление без релиза), но создаёт зависимость от сети. При отсутствии интернета пользователь не может ознакомиться с документом и принять соглашение. Решение: кэшировать последнюю версию локально, показывать кешированную при отсутствии сети.
class PolicyDocumentLoader {
func loadPolicy(_ type: PolicyType) async -> PolicyDocument {
// Пробуем загрузить свежую версию
if let fresh = try? await fetchFromServer(type) {
cache.save(fresh, for: type)
return fresh
}
// Fallback на кеш
if let cached = cache.load(for: type) {
return cached
}
// Последний резерв — bundled версия из приложения
return loadBundled(type)
}
}
Bundled версия — всегда актуальная на момент релиза — шьётся в приложение. Она не может быть устаревшей при установке.
Deep link к конкретному разделу
Для compliance иногда нужно открыть конкретный раздел: например, из экрана запроса разрешений камеры — сразу к разделу «Фото и видео» в Privacy Policy. WKWebView поддерживает URL fragment (#camera-section), если документ это поддерживает.
Сроки
Реализация экранов с WebView, кешированием, логированием принятия: 1 день. Стоимость рассчитывается индивидуально.







