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.
Choose the smallest product that matches the app. Geocoding, carrier, and timezone modules are opt-in because they ship metadata bundles. Carrier names are original-assignment hints and may be misleading in mobile-number-portable regions; use the safe-display API for user-facing carrier labels. Timezone lookup returns possible CLDR timezone IDs from prefix metadata, not the user's current location.
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.
Use the all-in-one updater for normal metadata releases:
swift scripts/updateMetadata.swift <libphonenumber-version> --dry-run
swift scripts/updateMetadata.swift <libphonenumber-version>Use --only main,geocoding or --skip carrier,timezones when the release intentionally covers only specific metadata families. See the release runbook for the full release flow.
Use the individual generators only when debugging or intentionally updating one metadata family outside the normal release flow:
swift scripts/metadataGenerator.swift <libphonenumber-version> --pretty
swift scripts/updateGeocodingMetadata.swift <libphonenumber-version> --replace-bundle
swift scripts/updateCarrierMetadata.swift <libphonenumber-version> --replace-bundle
swift scripts/updateTimeZonesMetadata.swift <libphonenumber-version> --replace-bundleUse --output to inspect generated review artifacts before replacing checked-in bundles. Use --source with the individual scripts when you already have a local Google libphonenumber checkout or extracted resource 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:
swift scripts/publishPodspecs.swift --lintFor Xcode schemes:
swift scripts/testXcodeSchemes.swift- 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 podspecs:
swift scripts/publishPodspecs.swift --lint
- Open a pull request with upstream version, parity results, and test results.
- Create a GitHub release after merge.
- Push updated podspecs in dependency order:
swift scripts/publishPodspecs.swift swift scripts/publishPodspecs.swift --publish