Objective-C implementation of Google's libphonenumber metadata and behavior for Apple platforms, with a Swift-first facade for new Swift integrations.
The project keeps the Objective-C core stable for existing apps while exposing a smaller Swift API for common parsing, formatting, validation, geocoding, and short-number workflows.
| Product | Use when |
|---|---|
libPhoneNumberSwiftCore |
You are writing Swift code and only need parsing, formatting, validation, and as-you-type formatting. |
libPhoneNumberSwiftGeocoding |
You need the Swift facade plus offline geocoding. |
libPhoneNumberSwiftShortNumber |
You need the Swift facade plus emergency and short-code support. |
libPhoneNumberSwiftUI |
You need a SwiftUI phone-number input component. |
libPhoneNumberSwift |
You want the backwards-compatible Swift umbrella facade with every Swift module. |
libPhoneNumber |
You need the stable Objective-C core API. |
libPhoneNumberGeocoding |
You need offline region descriptions for phone numbers. |
libPhoneNumberShortNumber |
You need emergency and short-code support. |
Add this repository as a package dependency and select the products you need.
For most Swift apps, choose the smallest Swift facade product:
.product(name: "libPhoneNumberSwiftCore", package: "libPhoneNumber")Add optional Swift facade products only when needed:
.product(name: "libPhoneNumberSwiftGeocoding", package: "libPhoneNumber")
.product(name: "libPhoneNumberSwiftShortNumber", package: "libPhoneNumber")
.product(name: "libPhoneNumberSwiftUI", package: "libPhoneNumber")Existing apps can keep using the umbrella product:
.product(name: "libPhoneNumberSwift", package: "libPhoneNumber")Use the Objective-C-compatible products directly if you need lower-level access:
.product(name: "libPhoneNumber", package: "libPhoneNumber")
.product(name: "libPhoneNumberGeocoding", package: "libPhoneNumber")
.product(name: "libPhoneNumberShortNumber", package: "libPhoneNumber")For Objective-C-compatible core APIs:
pod 'libPhoneNumber-iOS', '~> 1.6'For the Swift-first core facade:
pod 'libPhoneNumber-iOS-SwiftCore', '~> 1.6'Optional Swift facade modules:
pod 'libPhoneNumber-iOS-SwiftGeocoding', '~> 1.6'
pod 'libPhoneNumber-iOS-SwiftShortNumber', '~> 1.6'
pod 'libPhoneNumber-iOS-SwiftUI', '~> 1.6'For the backwards-compatible Swift umbrella facade:
pod 'libPhoneNumber-iOS-Swift', '~> 1.6'Optional modules:
pod 'libPhoneNumberGeocoding', '~> 1.6'
pod 'libPhoneNumberShortNumber', '~> 1.6'Add this to your Cartfile:
github "iziz/libPhoneNumber-iOS"
Add the source files from the modules you need and link Contacts.framework for the core library.
Prefer libPhoneNumberSwiftCore for new Swift code that only needs parsing, formatting, validation, and as-you-type formatting:
import libPhoneNumberSwiftCore
let phoneUtil = PhoneNumberUtility.shared
let phoneNumber = try phoneUtil.parse("01065431234", defaultRegion: "KR")
let e164 = try phoneUtil.format(phoneNumber, as: .e164)
let isValid = phoneUtil.isValidNumber(phoneNumber)
let numberType = phoneUtil.type(of: phoneNumber)The Swift facade delegates to the Objective-C implementation. Phone number parsing and validation logic should stay in the Objective-C core so upstream behavior remains centralized.
For storage, concurrency boundaries, or API responses, use the immutable value wrapper:
let value = try phoneUtil.value(from: "01065431234", defaultRegion: "KR").get()
value.e164
value.regionCode
value.nationalSignificantNumber
value.typeimport libPhoneNumberSwiftCore
let formatter = AsYouTypeFormatter(regionCode: "US")
formatter.inputDigit("6") // "6"
formatter.inputDigit("5") // "65"
formatter.inputDigit("0") // "650"
formatter.inputDigit("2") // "650-2"import libPhoneNumberSwiftCore
import libPhoneNumberSwiftShortNumber
let phoneUtil = PhoneNumberUtility.shared
let shortUtil = ShortNumberUtility.shared
let number = try phoneUtil.parse("911", defaultRegion: "US")
shortUtil.isValidShortNumber(number, forRegion: "US")
shortUtil.connectsToEmergencyNumber("911", forRegion: "US")
shortUtil.expectedCost(of: number, forRegion: "US")import libPhoneNumberSwiftCore
import libPhoneNumberSwiftGeocoding
let phoneUtil = PhoneNumberUtility.shared
let geocoder = PhoneNumberGeocoder.shared
let number = try phoneUtil.parse("16502530000", defaultRegion: "US")
let description = geocoder.description(for: number, languageCode: "en")import SwiftUI
import libPhoneNumberSwiftUI
struct PhoneForm: View {
@State private var phoneNumber = ""
@State private var e164: String?
var body: some View {
PhoneNumberTextField(
"Phone number",
text: $phoneNumber,
defaultRegion: "US",
onStateChange: { state in
e164 = state.e164
},
regionPicker: { region in
Text(region)
}
)
}
}Use NBPhoneNumberUtil when integrating from Objective-C or when you need direct access to the core API:
NBPhoneNumberUtil *phoneUtil = [NBPhoneNumberUtil sharedInstance];
NSError *error = nil;
NBPhoneNumber *phoneNumber = [phoneUtil parse:@"6766077303"
defaultRegion:@"AT"
error:&error];
if (phoneNumber != nil && error == nil) {
NSLog(@"isValidPhoneNumber ? %@", [phoneUtil isValidNumber:phoneNumber] ? @"YES" : @"NO");
NSLog(@"E164 : %@", [phoneUtil format:phoneNumber
numberFormat:NBEPhoneNumberFormatE164
error:&error]);
NSLog(@"INTERNATIONAL : %@", [phoneUtil format:phoneNumber
numberFormat:NBEPhoneNumberFormatINTERNATIONAL
error:&error]);
NSLog(@"NATIONAL : %@", [phoneUtil format:phoneNumber
numberFormat:NBEPhoneNumberFormatNATIONAL
error:&error]);
NSLog(@"RFC3966 : %@", [phoneUtil format:phoneNumber
numberFormat:NBEPhoneNumberFormatRFC3966
error:&error]);
} else {
NSLog(@"Error: %@", error.localizedDescription);
}NBAsYouTypeFormatter *formatter = [[NBAsYouTypeFormatter alloc] initWithRegionCode:@"US"];
NSLog(@"%@", [formatter inputDigit:@"6"]); // "6"
NSLog(@"%@", [formatter inputDigit:@"5"]); // "65"
NSLog(@"%@", [formatter inputDigit:@"0"]); // "650"
NSLog(@"%@", [formatter inputDigit:@"2"]); // "650-2"Existing Swift projects can continue to import Objective-C headers directly.
For manual integration:
#import "NBPhoneNumberUtil.h"
#import "NBPhoneNumber.h"For CocoaPods:
#import "libPhoneNumber_iOS/NBPhoneNumberUtil.h"
#import "libPhoneNumber_iOS/NBPhoneNumber.h"New Swift code should prefer libPhoneNumberSwiftCore unless it specifically needs geocoding, short-number support, or Objective-C API details.
Phone number behavior is driven by Google's libphonenumber metadata. When metadata or upstream behavior changes, update this repository in a normal PR and include:
- The Google libphonenumber version or commit used.
- Main metadata changes.
- Geocoding metadata changes, if applicable.
- Upstream test parity results.
- Upstream API parity results.
- Local test results.
Useful commands:
swift scripts/checkMetadataFreshness.swift
swift scripts/checkUpstreamTestParity.swift --upstream-ref <version-or-ref>
swift scripts/checkUpstreamAPIParity.swift --upstream-ref <version-or-ref>
swift test
LC_ALL=ko_KR.UTF-8 LANG=ko_KR.UTF-8 swift test
swift build -c releaseFor the full maintenance workflow, see:
Run the metadata generator from the scripts directory:
cd scripts
./metadataGenerator.swift <libphonenumber-version> --prettyThis downloads metadata from Google's libphonenumber repository, updates the generated Objective-C metadata files from compact JSON, and writes pretty-printed generatedJSON files for review.
Generate SQLite geocoding databases with the command-line updater:
swift scripts/updateGeocodingMetadata.swift <libphonenumber-version> --replace-bundleExamples:
swift scripts/updateGeocodingMetadata.swift 9.0.29 --replace-bundle
swift scripts/updateGeocodingMetadata.swift master --output /tmp/geocoding
swift scripts/updateGeocodingMetadata.swift --source /tmp/libphonenumber --output /tmp/geocodingUse --output to inspect generated databases before replacing the checked-in bundle. Use --source when you already have a local Google libphonenumber checkout or an extracted resources/geocoding directory.
Generate a current-vs-upstream metadata freshness summary and issue/PR text candidates:
swift scripts/checkMetadataFreshness.swift --output .build/metadata-freshnessThe script writes review artifacts only. It does not modify checked-in metadata.
Before merging behavior, metadata, packaging, or API changes, run the relevant checks from docs/TESTING.md.
Common local checks:
swift scripts/checkVersionConsistency.swift
swift test
LC_ALL=ko_KR.UTF-8 LANG=ko_KR.UTF-8 swift test
swift build -c release
git diff --checkFor Swift facade changes:
pod lib lint libPhoneNumber-iOS-SwiftCore.podspec --allow-warnings --include-podspecs='*.podspec'
pod lib lint libPhoneNumber-iOS-SwiftGeocoding.podspec --allow-warnings --include-podspecs='*.podspec'
pod lib lint libPhoneNumber-iOS-SwiftShortNumber.podspec --allow-warnings --include-podspecs='*.podspec'
pod lib lint libPhoneNumber-iOS-SwiftUI.podspec --allow-warnings --include-podspecs='*.podspec'
pod lib lint libPhoneNumber-iOS-Swift.podspec --allow-warnings --include-podspecs='*.podspec'For Xcode schemes:
xcodebuild test -scheme libPhoneNumber -destination 'platform=iOS Simulator,name=iPhone 16'
xcodebuild test -scheme libPhoneNumberGeocoding -destination 'platform=iOS Simulator,name=iPhone 16'
xcodebuild test -scheme libPhoneNumberShortNumber -destination 'platform=iOS Simulator,name=iPhone 16'- Decide the next version:
- Patch for bug fixes.
- Minor for metadata updates or additive functionality.
- Major for breaking changes.
- Update project versions:
swift scripts/updateProjectVersions.swift <new-version>
- Run the validation matrix.
- Lint the affected podspecs.
- Open a pull request with upstream version, parity results, and test results.
- Create a GitHub release after merge.
- Push updated podspecs.