Привет! Меня зовут Александр, я 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.