[iOS] String NSDataDetector ValidEmail (이메일 형식 체크하기)



# [iOS] String ValidEmail (이메일 형식 체크하기)

안녕하세요 __물먹고하자__ 입니다 :)
메일쓰기화면에서 Email 규약이 맞는지 정규식으로 체크했었는데,
"xxx.ddd123@gmail.com" 이런식이 기존에 인식이 안되었던 버그가 있어서
이참에 업데이트 하고, 테스트 했던 결과 공유드립니다.

---
## 1. NSDataDetector Link 추출후 mailto 활용하기
> 💡 <a href="https://developer.apple.com/documentation/foundation/nsdatadetector" target="_blank">NSDataDetector</a> iOS 4.0 +
- 과거 네이버 블로그 <a href="https://blog.naver.com/xodhks_0113/220946461989?trackingCode=blog_bloghome_searchlist" target="_blank">https://blog.naver.com/xodhks_0113/220946461989?trackingCode=blog_bloghome_searchlist</a>
- 예전에 했었는데, 기억이 안났을줄이야...

``` swift
import Foundation

// MARK: - 이메일 검증 함수
func isValidEmail(_ email: String) -> Bool {
    guard !email.isEmpty else { return false }

    do {
        let detector = try NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
        let range = NSRange(email.startIndex..., in: email)
        let matches = detector.matches(in: email, options: [], range: range)
        
//        // 문자열에 메일형식이 맞는지 체크하는 부분
//        for match in matches {
//            if match.resultType == .link,
//               let url = match.url,
//               url.scheme == "mailto" {
//                return true
//            }
//        }
        // 전체 문자열이 완전히 이메일 패턴과 일치해야 함
        return matches.contains { match in
            match.resultType == .link &&
            match.url?.scheme == "mailto" &&
            NSEqualRanges(match.range, range)
        }
        
    } catch {
        print("DataDetector error: \(error)")
    }

    return false
}

// MARK: - 테스트용 이메일 목록
let testEmails = [
    // &#9989; 정상적인 주소들
    "john.doe@example.com",
    "john_doe@example.co.uk",
    "john-doe@sub.domain.com",
    "user+promo@gmail.com",
    "user@123.45.67.89",
    "firstname.lastname@company.travel",
    "me@my-domain.io",
    "test@exämple.com",                 // 국제문자 도메인
    "iveta.sevcakova@rb.cz",            // 체코 도메인
    "user_name@domain.org",
    "user.name@domain.tech",
    "user@domain.c",                    // 짧은 TLD

    // &#10060; 명백히 잘못된 이메일
    "plainaddress",
    "missingatsign.com",
    "@nouser.com",
    "user@.com",
    "user@domain..com",
    "invalid@@example.com",
    "john@doe@example.com",
    "user@localhost",
    "user@domain,com",
    "user@domain@domain.com",
    "user@domain..co",
    "user@-domain.com",                 // 도메인명 시작이 - 금지
    "user@domain-.com",                 // 도메인명 끝이 - 금지
    "user@.domain.com",                 // 도메인 시작이 . 금지
    "user@domain_.com",                 // 언더스코어 금지
    "user.@domain.com",                 // 점으로 끝나는 로컬파트 금지
    ".user@domain.com",                 // 점으로 시작 금지
    "user@domain",                      // TLD 없음
    "user@123",                         // 숫자 도메인 단독 금지
    "user@domain.toolongtlddddd",       // 너무 긴 TLD

    // &#9888;&#65039; 경계 / 애매한 케이스
    "  spaced@out.com  ",               // 공백 포함
    "<xodhks_0113@naver.com>",          // 꺾쇠 포함
    "(user)@example.com",               // 괄호 포함
    "\"user@name\"@example.com",        // 따옴표 포함
    "üñîçøðé@example.com",              // 유니코드 로컬파트
    "이메일@도메인.com",                 // 한글 포함
    "user@[192.168.1.1]",               // 대괄호 IP 형식
    "user@sub_domain.com",              // 언더스코어 포함 (비표준)
    "user@do..main.com",                // 연속 점
    "user@domain.corporate",            // 긴 TLD
    "customer-service@shop-name.co.jp", // 다단계 도메인
    "mailbox+filter@sub.service.io"     // 플러스 라벨링
]

// MARK: - 테스트 실행
for email in testEmails {
    let trimmed = email.trimmingCharacters(in: .whitespacesAndNewlines)
    var check = isValidEmail(trimmed) ? "&#9989; Valid" : "&#10060; Invalid"
    print("\(email) : \(check)")
}


```

<!-- 1. 결과 이미지 -->
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEga1p7Y9Jva9KmhcvcEwinsselAmBxRU6nDDDPiM22wxIFXM5_NxBsR3YpL9WS7T4ZrnWWF1_EiG_BLKU7OTR2GeWby_kW3LW6p82Rx1KOygAZgRSXIkd4rthzM6McKX53oBNAozidDc22OD0jiU6F5ThFfqfRQL7CtC3XueUHElqRDB-Bt7qIP3bueOjOJ/s2234/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202025-11-12%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%202.39.29.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" data-original-height="2234" data-original-width="922" height="600" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEga1p7Y9Jva9KmhcvcEwinsselAmBxRU6nDDDPiM22wxIFXM5_NxBsR3YpL9WS7T4ZrnWWF1_EiG_BLKU7OTR2GeWby_kW3LW6p82Rx1KOygAZgRSXIkd4rthzM6McKX53oBNAozidDc22OD0jiU6F5ThFfqfRQL7CtC3XueUHElqRDB-Bt7qIP3bueOjOJ/s600/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202025-11-12%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%202.39.29.png"/></a></div>

코드영역 __isValidEmail__ 함수에서 해당 문자열에 이메일이 포함되어있는경우도 true로 하고 싶은경우는 주석된 부분을 사용하면 되고,
현재 코드 기준으로는 정확하게 email형식과 입력된 String이 매칭이 되었을때만 true인 상황이다.

ex) <xodhks_0113@naver.com> 이메일이 포함되어서 true 이지만, xodhks_0113@naver.com 이 유효한 email형식임.

---
## 마무리
너무 오래되서 까먹고 있었는데, 왜인지 이 코드를 알고있고 과거의 블로그도 썼었는데 
이메일쓰기 부분을 정규식으로 해두었더군요.

그러다 보니 모든걸 허용되진 못했던 이슈가 있었고요.

다시 알고있던걸 정리했고, GPT통해서 샘플링과 허용/비허용 테스트코드도 작성해봤네요.

오늘은 이만~

즐거운 코딩 되게요.

끝.


댓글