Привет! Меня зовут Александр, я iOS-разработчик в IT-компании SimbirSoft. В этой статье я расскажу, как интегрировать Yandex SmartCaptcha в iOS-приложение — от подготовки до решения возможных трудностей.
Настройка Yandex SmartCaptcha на первый взгляд может показаться простой задачей, но на практике она требует внимательности, точной настройки и понимания архитектуры приложения. Я поделюсь личным опытом внедрения этого инструмента, объясню, какие нюансы стоит учесть, а также как избежать ошибок.
Материал предназначен для iOS-разработчиков, у которых уже есть базовые знания мобильной разработки, и кто хочет быстро и корректно внедрить капчу от Яндекса в свое iOS-приложение.

В первой части мы с вами рассмотрим, как настроить визуализацию капчи на web-стороне, а во второй — как интегрировать данный инструмент в ваше iOS-приложение.
Содержание
Настройка визуализации капчи на web-стороне
Чтобы работать с капчей, для этого нужно в личном кабинете получить ключ. Он выглядит примерно так: ysc1_ADJdjadjkandanjkdakjDHkakdahkdkda
Также нужно создать капчу, подробное описание есть в документации, останавливаться не буду.
Yandex SmartCaptcha состоит из двух блоков:

Механика требует нажатия на галочку для проверки, также есть на выбор другой блок в виде слайдера.
Первый срабатывает в 90% случаев, в 10% открывается второй блок:

Второй блок работает как bottom sheet. Отключить второй блок нельзя, только можно уменьшить сложность, чтобы он реже появлялся у пользователей. Также можно выбрать другой вид, например, слайдер-мозаику.
Можно настроить цвет и вид блоков:

Интеграция в iOS приложение
1. Настройка JS части
Создадим константу и заполним body во viewModel (можно сделать отдельным JSON-файлом):
let body = """ <html> <head> <meta charset=«UTF-8"> // Настраиваем внешний вид капчи для мобильных устройств. Если этого не сделать, капча будет растянута, так как по умолчанию она оптимизирована для веб-версии. <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <script> // Подписываемся на готовность капчи к представлению function onSmartCaptchaReady(widgetId) { if (!window.smartCaptcha) { throw new Error("SmartCaptcha is not present"); } // Подписываемся на показ капчи window.smartCaptcha.subscribe( widgetId, "challenge-visible", handleChallengeVisible ); // Подписываемся на скрытие капчу window.smartCaptcha.subscribe( widgetId, "challenge-hidden", handleChallengeHidden ); // Подписываемся на успешный event показа капчи window.smartCaptcha.subscribe(widgetId, "success", handleSuccess); } // У капчи есть жизненный цикл: // captchaDidFinish — когда капча успешно завершилась function handleSuccess(token) { sendIos("captchaDidFinish", token); } // challengeDidAppear — когда пользователь смахнул вниз второй блок(bottom sheet) function handleChallengeVisible() { sendIos("challengeDidAppear"); } // challengeDidDisappear — капча находиться в не пройденном состоянии function handleChallengeHidden() { sendIos("challengeDidDisappear"); } // Метод для взаимодействия с IOS-частью function sendIos(...args) { if (args.length == 0) { return; } const message = { method: args[0], data: args[1] !== undefined ? args[1] : "" }; if (window.webkit) { window.webkit.messageHandlers.NativeClient.postMessage(message); } } // Загрузка body капчи </script> <script src="https://smartcaptcha.yandexcloud.net/captcha.js?render=onload&onload=onloadFunction" defer></script> </head> <body> <div id="captcha-container" class="smart-captcha" data-sitekey=«Ваш ключ" data-hl="ru" data-callback="callback"> <script> function onloadFunction() { if (window.smartCaptcha) { const container = document.getElementById('captcha-container'); const widgetId = window.smartCaptcha.render(container, { sitekey: «Ваш ключ", hl: "ru", }); onSmartCaptchaReady(widgetId) } } </script> </div> </body> </html> """ }
2. Настройка нативной части
1. Создаем:
private lazy var webView: WKWebView = { let configuration = WKWebViewConfiguration() // Регистрируем конфигурацию configuration.userContentController.add(self, name: "NativeClient") let webView = WKWebView(frame: .zero, configuration: configuration) return webView }()
2. Расставляем констрейты под наши нужды
3. Cоздаем enum с жизненным циклом капчи:
enum CaptchaState: String { case captchaDidFinish case challengeDidAppear case challengeDidDisappear }
4. Загружаем loadCaptchaBody во ViewDidLoad:
// Создаем url во viewModel var url = Foundation.URL(string: “Ваша ссылка”) private func loadCaptchaBody() { if let baseURL = viewModel.url { // Загружаем наш JS код webView.loadHTMLString(viewModel.body, baseURL: baseURL) } else { print("Invalid urlAuth") } }
5. Подписываемся под делегат WKScriptMessageHandler и используем метод userContentController:
extension AuthorizationViewController: WKScriptMessageHandler { func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { guard let jsData = message.body as? [String: String], let methodName = jsData["method"] else { return } if let captchaState = CaptchaState(rawValue: methodName) { switch captchaState { // Когда капча завершилась успешно case .captchaDidFinish: // Здесь отправляем токен viewModel.validateToken(jsData["data"] ?? «") // К примеру со взаимодействием UI, можно свернуть клавиатуру, кнопка далее не скрыта case .challengeDidAppear: // Срабатывает, когда сворачивается bottom sheet, капча не закончена и пользователь вернулся к первому шагу case .challengeDidDisappear: // Срабатывает в первоначальном кейсе, когда капча не пройдена } } } }
6. Нужно отправить токен во viewModel. В моем случае я использую RxSwift, наблюдаю за наличием токена, и если он есть, делаю активной кнопку «Далее» (наличие токена, одна из проверок (к примеру, обычно еще есть проверка «маски» номера телефона или email). Метод validateToken вызывается в пункте 5.
func validateToken(_ text: String) { validateTokenCaptchaPhoneRelay.accept(text) }
7. В метод, где происходит валидация телефона или email, вставляем проверку на наличие токена.
func validatePhone(_ text: String) { guard !validateTokenCaptchaPhoneRelay.value.isEmpty else { print(“Токен не найден”) return } }
8. Очищаем токен и перезапускаем капчу при каждом появлении экрана в viewWillAppear.
private func resetCaptcha() { webView.evaluateJavaScript("window.smartCaptcha.reset()") viewModel.clearCaptchaToken() } // Очищаем токен во ViewModel func clearCaptchaToken() { validateTokenCaptchaPhoneRelay.accept("") }
9. Если у вас будет такой же кейс, как у меня — нужно сделать неактивной вторую часть экрана. Придется делать градиентом, потому что подложка у WebView черная и изменить ее нельзя.
Пример реализации

Заключение
В этой статье я описал процесс интеграции Yandex SmartCaptcha в iOS-приложение, показал основные шаги, указал на возможные сложности и ключевые моменты, на которые стоит обратить внимание. Следуя описанным рекомендациям, вы сможете легко внедрить SmartCaptcha и улучшить безопасность вашего приложения.
Спасибо за внимание! Больше авторских материалов для Mobile-разработчиков от моих коллег читайте в соцсетях SimbirSoft – ВКонтакте и Telegram.
