Apple-platform port of Google's libphonenumber metadata and behavior, with a stable Objective-C core and Swift-first facades for modern Swift apps.
Use the Objective-C API when you need source-compatible legacy integration. Use the Swift facade modules when you want value-oriented parsing, formatting, validation, geocoding, short-number support, or a SwiftUI phone input.
- Objective-C core remains the source of truth for parsing, formatting, validation, geocoding, and short-number behavior.
- Swift facade modules provide smaller, task-focused imports for new Swift code.
- SwiftUI phone input is available as an opt-in module instead of being bundled into the core parser.
- Swift Package Manager and CocoaPods are both supported.
- Metadata updates are tracked against Google libphonenumber with parity checks and review artifacts.
For most Swift apps, start with the core Swift facade:
.product(name: "libPhoneNumberSwiftCore", package: "libPhoneNumber")Add optional modules only when needed:
.product(name: "libPhoneNumberSwiftGeocoding", package: "libPhoneNumber")
.product(name: "libPhoneNumberSwiftShortNumber", package: "libPhoneNumber")
.product(name: "libPhoneNumberSwiftCarrier", package: "libPhoneNumber")
.product(name: "libPhoneNumberSwiftTimeZones", package: "libPhoneNumber")
.product(name: "libPhoneNumberSwiftUI", package: "libPhoneNumber")
.product(name: "libPhoneNumberSwiftUIEnrichment", package: "libPhoneNumber")Use the umbrella product when you want one non-UI import for core, geocoding, and short-number facades:
.product(name: "libPhoneNumberIOSSwift", package: "libPhoneNumber")For CocoaPods Swift apps:
pod 'libPhoneNumber-iOS-SwiftCore', '~> 1.7'For the CocoaPods umbrella facade:
pod 'libPhoneNumber-iOS-Swift', '~> 1.7'Then import the umbrella module as:
import libPhoneNumberIOSSwift| Product | Package manager | Use when |
|---|---|---|
libPhoneNumberSwiftCore |
SPM, CocoaPods | You need Swift parsing, formatting, validation, and as-you-type formatting. |
libPhoneNumberSwiftGeocoding |
SPM, CocoaPods | You need Swift offline geocoding on top of the core facade. |
libPhoneNumberSwiftShortNumber |
SPM, CocoaPods | You need Swift emergency-number and short-code support. |
libPhoneNumberSwiftCarrier |
SPM, CocoaPods | You need Swift carrier prefix lookup. |
libPhoneNumberSwiftTimeZones |
SPM, CocoaPods | You need Swift timezone prefix lookup. |
libPhoneNumberSwiftUI |
SPM, CocoaPods | You need a SwiftUI phone-number input component. |
libPhoneNumberSwiftUIEnrichment |
SPM, CocoaPods | You want carrier/timezone metadata connected to SwiftUI field state. |
libPhoneNumberIOSSwift |
SPM, CocoaPods umbrella module | You want one non-UI Swift import for core, geocoding, and short-number facades. |
libPhoneNumber |
SPM, CocoaPods, Carthage, manual | You need the stable Objective-C core API. |
libPhoneNumberGeocoding |
SPM, CocoaPods | You need Objective-C offline geocoding APIs. |
libPhoneNumberShortNumber |
SPM, CocoaPods | You need Objective-C emergency-number and short-code APIs. |
libPhoneNumberCarrier |
SPM, CocoaPods | You need Objective-C carrier prefix lookup APIs. |
libPhoneNumberTimeZones |
SPM, CocoaPods | You need Objective-C timezone prefix lookup APIs. |
The SwiftUI module is intentionally separate from the umbrella module because it is UI-specific and requires SwiftUI runtime availability.
Add this repository as a package dependency:
https://github.com/iziz/libPhoneNumber-iOS
Select only the products your app needs:
.product(name: "libPhoneNumberSwiftCore", package: "libPhoneNumber")
.product(name: "libPhoneNumberSwiftGeocoding", package: "libPhoneNumber")
.product(name: "libPhoneNumberSwiftShortNumber", package: "libPhoneNumber")
.product(name: "libPhoneNumberSwiftCarrier", package: "libPhoneNumber")
.product(name: "libPhoneNumberSwiftTimeZones", package: "libPhoneNumber")
.product(name: "libPhoneNumberSwiftUI", package: "libPhoneNumber")
.product(name: "libPhoneNumberSwiftUIEnrichment", package: "libPhoneNumber")
.product(name: "libPhoneNumberIOSSwift", package: "libPhoneNumber")Objective-C-compatible products are also available:
.product(name: "libPhoneNumber", package: "libPhoneNumber")
.product(name: "libPhoneNumberGeocoding", package: "libPhoneNumber")
.product(name: "libPhoneNumberShortNumber", package: "libPhoneNumber")
.product(name: "libPhoneNumberCarrier", package: "libPhoneNumber")
.product(name: "libPhoneNumberTimeZones", package: "libPhoneNumber")Core Objective-C API:
pod 'libPhoneNumber-iOS', '~> 1.7'Swift facade modules:
pod 'libPhoneNumber-iOS-SwiftCore', '~> 1.7'
pod 'libPhoneNumber-iOS-SwiftGeocoding', '~> 1.7'
pod 'libPhoneNumber-iOS-SwiftShortNumber', '~> 1.7'
pod 'libPhoneNumber-iOS-SwiftCarrier', '~> 1.7'
pod 'libPhoneNumber-iOS-SwiftTimeZones', '~> 1.7'
pod 'libPhoneNumber-iOS-SwiftUI', '~> 1.7'
pod 'libPhoneNumber-iOS-SwiftUIEnrichment', '~> 1.7'Swift umbrella facade:
pod 'libPhoneNumber-iOS-Swift', '~> 1.7'Objective-C optional modules:
pod 'libPhoneNumberGeocoding', '~> 1.7'
pod 'libPhoneNumberShortNumber', '~> 1.7'
pod 'libPhoneNumberCarrier', '~> 1.7'
pod 'libPhoneNumberTimeZones', '~> 1.7'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 libPhoneNumberSwiftCore
import libPhoneNumberSwiftCarrier
let phoneUtil = PhoneNumberUtility.shared
let mapper = PhoneNumberCarrierMapper.shared
let number = try phoneUtil.parse("+244917654321", defaultRegion: "AO")
let carrier = mapper.safeDisplayName(for: number, localeCode: "en")import libPhoneNumberSwiftCore
import libPhoneNumberSwiftTimeZones
let phoneUtil = PhoneNumberUtility.shared
let mapper = PhoneNumberTimeZonesMapper.shared
let number = try phoneUtil.parse("16502530000", defaultRegion: "US")
let timeZones = mapper.timeZones(for: number)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)
}
)
}
}import SwiftUI
import libPhoneNumberSwiftUI
import libPhoneNumberSwiftUIEnrichment
struct EnrichedPhoneForm: View {
@State private var phoneNumber = ""
@State private var carrierName: String?
@State private var timeZones: [String] = []
private let formatter = PhoneNumberFieldFormatter(
enricher: CarrierTimeZonesPhoneNumberEnricher(localeCode: "en")
)
var body: some View {
PhoneNumberTextField(
"Phone number",
text: $phoneNumber,
defaultRegion: "AO",
formatter: formatter,
onStateChange: { state in
carrierName = state.enrichment?.carrierName
timeZones = state.enrichment?.timeZones ?? []
}
)
}
}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. Metadata or upstream behavior changes should be reviewed in a normal PR with:
- The Google libphonenumber version or commit used.
- Main metadata changes.
- Geocoding metadata changes, if applicable.
- Upstream test parity results.
- Upstream API parity results.
- Local validation results.
Generate a current-vs-upstream freshness report:
swift scripts/checkMetadataFreshness.swift --output .build/metadata-freshnessThe script writes review artifacts only. It does not modify checked-in metadata.
For user-reported numbering-plan gaps that are not yet in upstream metadata, follow the metadata patch policy. Local metadata overrides must be evidence-backed, tested, and removed once upstream includes the change.
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.
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 or metadata-only updates.
- Minor for additive functionality or public API additions.
- 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.