Avatar ·

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

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

Лучший способ валидации email в Swift

Правильная валидация email — задача сложнее, чем кажется на первый взгляд. Простое наличие символов "@" и "." не гарантирует корректность адреса. Я покажу два подхода: с использованием NSDataDetector (рекомендуемый) и через регулярные выражения.

Метод 1: Использование NSDataDetector (рекомендуемый)

Начиная с iOS, Apple предоставляет мощный инструмент NSDataDetector, который умеет распознавать email адреса в тексте. Это самый надёжный способ, так как он использует встроенные механизмы системы:

extension String {
    func isValidEmail() -> Bool {
        let emailRegEx = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
        let emailPred = NSPredicate(format:"SELF MATCHES %@", emailRegEx)
        return emailPred.evaluate(with: self)
    }
    
    func isValidEmailUsingDetector() -> Bool {
        let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
        let matches = detector?.matches(in: self, options: [], range: NSRange(location: 0, length: self.utf16.count))
        
        guard let match = matches?.first else { return false }
        
        // Проверяем, что вся строка — это один email
        return match.url?.scheme == "mailto" && match.range.length == self.utf16.count
    }
}

// Пример использования:
let testEmails = ["user@example.com", "test@", "@test.com", "user@.com", "user@domain.co.uk"]
for email in testEmails {
    print("\(email): \(email.isValidEmailUsingDetector())")
}
// Вывод:
// user@example.com: true
// test@: false
// @test.com: false
// user@.com: false
// user@domain.co.uk: true

Метод 2: Классическое регулярное выражение

Если вам нужен более гибкий контроль, используйте NSPredicate с регулярным выражением:

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

// Пример с дополнительной обработкой
func validateEmail(_ email: String) -> Bool {
    let trimmedEmail = email.trimmingCharacters(in: .whitespacesAndNewlines)
    guard !trimmedEmail.isEmpty else { return false }
    return trimmedEmail.isValidEmailRegex()
}

Почему NSDataDetector лучше?

  • Актуальность: Apple обновляет детектор вместе с ОС, он учитывает новые домены верхнего уровня (например, .online, .tech)
  • Надёжность: Детектор проверяет не только формат, но и валидность схемы (mailto:)
  • Простота: Не нужно разбираться в сложных регулярных выражениях

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

Помните, что даже самая лучшая валидация формата не гарантирует, что email реально существует. Для полной проверки нужно отправлять письмо с подтверждением. Также не стоит делать валидацию слишком строгой — например, адреса вида "user+tag@example.com" или "user.name@domain.co.uk" абсолютно корректны по стандарту RFC 5322.

В большинстве случаев для iOS-приложения достаточно простой проверки через NSDataDetector или регулярное выражение, приведённое выше. Это отсечёт 99% опечаток и невалидных адресов. <<>>

Альтернативный подход: валидация компонентов вручную

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

func isValidEmailManual(_ email: String) -> Bool {
    let trimmed = email.trimmingCharacters(in: .whitespacesAndNewlines)
    
    // Проверяем наличие одного @
    let parts = trimmed.split(separator: "@", maxSplits: 1)
    guard parts.count == 2 else { return false }
    
    let localPart = String(parts[0])
    let domainPart = String(parts[1])
    
    // Локальная часть не должна быть пустой
    guard !localPart.isEmpty, localPart.count <= 64 else { return false }
    
    // Домен должен содержать точку
    guard domainPart.contains("."), 
          let lastDot = domainPart.lastIndex(of: ".") else { return false }
    
    // TLD (часть после последней точки) должна быть длиной от 2 до 64 символов
    let tld = domainPart[domainPart.index(after: lastDot)...]
    guard tld.count >= 2, tld.count <= 64 else { return false }
    
    return true
}

Этот метод проверяет базовые правила RFC 5321: локальная часть до 64 символов, домен должен содержать точку, TLD — от 2 символов. Он не такой всеобъемлющий, как регулярное выражение, но легко читается и модифицируется. Для большинства приложений этого более чем достаточно.

Если вам нужно поддерживать интернациональные домены (IDN), обязательно используйте метод .precomposedStringWithCanonicalMapping для нормализации строки перед проверкой.