Avatar ·

Как правильно проверить email на валидность в Swift?

Всем привет! Я новичок в Swift и разрабатываю приложение для iOS, где пользователь вводит свой email при регистрации. Мне нужно проверить, что введённая строка действительно является корректным email-адресом. Пробовал написать свою простую проверку, но она не работает для сложных случаев. Вот мой текущий код: ```swift func isValidEmail(_ email: String) -> Bool { return email.contains("@") && email.contains(".") } ``` Проблема в том, что эта проверка пропускает явно неверные адреса, например: "test@", "@gmail.com", "test@@gmail.com" или "test@.com". Я понимаю, что нужно использовать регулярные выражения (NSRegularExpression), но не знаю, как правильно их составить и применить в Swift. Подскажите, пожалуйста, как надёжно валидировать email? Есть ли какие-то подводные камни? И как правильно обрабатывать ошибки, если email невалидный? Заранее спасибо за помощь! <<>>

Проверка email с помощью регулярного выражения в Swift

Для валидации email в Swift лучше всего использовать NSRegularExpression и NSPredicate. Вот проверенный и надёжный метод, который покрывает большинство реальных случаев.

Основное решение

Создадим расширение для String с методом проверки email:

extension String {
    func isValidEmail() -> Bool {
        let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
        let emailPredicate = NSPredicate(format: "SELF MATCHES %@", emailRegex)
        return emailPredicate.evaluate(with: self)
    }
}

// Пример использования:
let testEmail = "user@example.com"
if testEmail.isValidEmail() {
    print("Email корректен")
} else {
    print("Email невалиден")
}

Разбор регулярного выражения

  • [A-Z0-9a-z._%+-]+ — локальная часть (до @). Допускаются буквы, цифры, точки, подчёркивания, проценты, плюсы и минусы. Один или более символов.
  • @ — обязательный символ "собаки".
  • [A-Za-z0-9.-]+ — доменное имя (после @). Буквы, цифры, точки и дефисы.
  • \\. — экранированная точка перед доменом верхнего уровня.
  • [A-Za-z]{2,64} — домен верхнего уровня (TLD). Только буквы, от 2 до 64 символов (например, .com, .ru, .online).

Более строгая версия (рекомендуется)

Для продакшена лучше использовать более точное выражение, которое отсекает большинство нестандартных случаев:

func isValidEmailStrict() -> Bool {
    let emailRegex = "(?:[\\p{L}0-9!#$%\\&'*+/=?\\^_`{|}~-]+(?:\\.[\\p{L}0-9!#$%\\&'*+/=?\\^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[\\p{L}0-9](?:[a-z0-9-]*[\\p{L}0-9])?\\.)+[\\p{L}0-9](?:[\\p{L}0-9-]*[\\p{L}0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[\\p{L}0-9-]*[\\p{L}0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])"
    let emailPredicate = NSPredicate(format: "SELF MATCHES %@", emailRegex)
    return emailPredicate.evaluate(with: self)
}

Важные замечания

  • Не пытайтесь валидировать email на 100% — RFC 5322 допускает очень странные форматы (например, "user@[192.168.1.1]" или "user+tag@example.com"). Регулярное выражение, покрывающее всё, будет огромным и медленным.
  • Лучший способ проверки — отправить письмо с кодом подтверждения. Валидация на стороне клиента нужна только для отсеивания очевидных ошибок (пропущен @, нет домена).
  • Юникодные символы — современные email могут содержать символы национальных алфавитов (например, "пользователь@пример.рф"). Используйте \\p{L} для поддержки любых букв.

Обработка ошибок в UI

Пример использования с текстовым полем:

func validateEmailField(_ textField: UITextField) {
    guard let email = textField.text, !email.isEmpty else {
        showError("Email не может быть пустым")
        return
    }
    
    if email.isValidEmail() {
        hideError()
    } else {
        showError("Введите корректный email (например, name@domain.com)")
    }
}

func showError(_ message: String) {
    // Показываем UILabel с ошибкой
    errorLabel.text = message
    errorLabel.isHidden = false
}

func hideError() {
    errorLabel.isHidden = true
}

Этот подход даёт хороший баланс между точностью и производительностью для большинства iOS-приложений.

<<>>

Альтернативный подход: проверка без регулярных выражений

Если вы не хотите использовать сложные регулярные выражения, можно написать более понятную, хотя и менее строгую проверку. Этот метод подойдёт для простых случаев или для обучения:

func isValidEmailSimple(_ email: String) -> Bool {
    // 1. Убираем пробелы по краям
    let trimmed = email.trimmingCharacters(in: .whitespacesAndNewlines)
    
    // 2. Проверяем наличие @
    guard let atIndex = trimmed.firstIndex(of: "@") else { return false }
    
    // 3. Локальная часть (до @) не должна быть пустой
    let localPart = trimmed[.. 0 else { return false }
    
    // 4. Доменная часть (после @)
    let domainPart = trimmed[trimmed.index(after: atIndex)...]
    guard domainPart.count > 0 else { return false }
    
    // 5. В домене должна быть хотя бы одна точка
    guard domainPart.contains(".") else { return false }
    
    // 6. Домен не должен начинаться или заканчиваться точкой
    guard domainPart.first != ".", domainPart.last != "." else { return false }
    
    // 7. Проверка на двойные точки
    guard !localPart.contains(".."), !domainPart.contains("..") else { return false }
    
    return true
}

Плюсы и минусы

  • Плюсы: Код легко читается и модифицируется. Не нужно разбираться в регулярных выражениях.
  • Минусы: Пропускает некоторые невалидные адреса (например, "a@b.c" без домена верхнего уровня). Не поддерживает сложные форматы вроде кавычек или IP-адресов.

Этот вариант хорош для прототипов или когда вам нужна базовая проверка без установки дополнительных зависимостей. Для продакшена рекомендую использовать первый вариант с NSPredicate.