Create Phone Number View for Sign Up SwiftUI iOS16

Andrii
4 min readFeb 6, 2023

--

iPhone 14 pro with open PhoneNumber View

Hey there) I recently created a Phone Number Input View. To create a List of countries, I used json file.

Okay first of all create file CountryNumbers.json, full file you can find here

[{
"id": "0232",
"name": "Ukraine",
"flag": "πŸ‡ΊπŸ‡¦",
"code": "UA",
"dial_code": "+380",
"pattern": "## ### ## ##",
"limit": 17
}, {
"id": "0234",
"name": "United Kingdom",
"flag": "πŸ‡¬πŸ‡§",
"code": "GB",
"dial_code": "+44",
"pattern": "## #### ####",
"limit": 17
}, {
"id": "0235",
"name": "United States",
"flag": "πŸ‡ΊπŸ‡Έ",
"code": "US",
"dial_code": "+1",
"pattern": "### ### ####",
"limit": 17
}]

Next we need to define a CountryNumbers structure that can be loaded from JSON file. Create a new Swift file called CPData.swift, then give it this code:

import Foundation

struct CPData: Codable, Identifiable {
let id: String
let name: String
let flag: String
let code: String
let dial_code: String
let pattern: String
let limit: Int

static let allCountry: [CPData] = Bundle.main.decode("CountryNumbers.json")
static let example = allCountry[0]
}

For more understanding visit hackingwithswift.com

Now we need to load an array of codes and flags from JSON stored in app bundle, for that create new Swift file called Decoder.swift, then give it this code:

import Foundation
extension Bundle {
func decode<T: Decodable>(_ file: String) -> T {
guard let url = self.url(forResource: file, withExtension: nil) else {
fatalError("Failed to locate \(file) in bundle.")
}

guard let data = try? Data(contentsOf: url) else {
fatalError("Failed to load \(file) from bundle.")
}

let decoder = JSONDecoder()

guard let loaded = try? decoder.decode(T.self, from: data) else {
fatalError("Failed to decode \(file) from bundle.")
}

return loaded
}
}

And we are ready to make View. To do this, create a new Swift file named PhoneNumberView.swift and generate the code:

import SwiftUI
import Combine

struct PhoneNumberView: View {
@State var presentSheet = false
@State var countryCode : String = "+1"
@State var countryFlag : String = "πŸ‡ΊπŸ‡Έ"
@State var countryPattern : String = "### ### ####"
@State var countryLimit : Int = 17
@State var mobPhoneNumber = ""
@State private var searchCountry: String = ""
@Environment(\.colorScheme) var colorScheme
@FocusState private var keyIsFocused: Bool

let counrties: [CPData] = Bundle.main.decode("CountryNumbers.json")

var body: some View {
GeometryReader { geo in
let hasHomeIndicator = geo.safeAreaInsets.bottom > 0
NavigationStack {
VStack {

Text("Confirm country code and enter phone number")
.multilineTextAlignment(.center)
.font(.title).bold()
.padding(.top, hasHomeIndicator ? 70 : 20)

HStack {
Button {
presentSheet = true
keyIsFocused = false
} label: {
Text("\(countryFlag) \(countryCode)")
.padding(10)
.frame(minWidth: 80, minHeight: 47)
.background(backgroundColor, in: RoundedRectangle(cornerRadius: 10, style: .continuous))
.foregroundColor(foregroundColor)
}

TextField("", text: $mobPhoneNumber)
.placeholder(when: mobPhoneNumber.isEmpty) {
Text("Phone number")
.foregroundColor(.secondary)
}
.focused($keyIsFocused)
.keyboardType(.numbersAndPunctuation)
.onReceive(Just(mobPhoneNumber)) { _ in
applyPatternOnNumbers(&mobPhoneNumber, pattern: countryPattern, replacementCharacter: "#")
}
.padding(10)
.frame(minWidth: 80, minHeight: 47)
.background(backgroundColor, in: RoundedRectangle(cornerRadius: 10, style: .continuous))
}
.padding(.top, 20)
.padding(.bottom, 15)

Button {
// Move to the next step
} label: {
Text("Next")
}
.disableWithOpacity(mobPhoneNumber.count < 1)
.buttonStyle(OnboardingButtonStyle())
}
.animation(.easeInOut(duration: 0.6), value: keyIsFocused)
.padding(.horizontal)

Spacer()

}
.onTapGesture {
hideKeyboard()
}
.sheet(isPresented: $presentSheet) {
NavigationView {
List(filteredResorts) { country in
HStack {
Text(country.flag)
Text(country.name)
.font(.headline)
Spacer()
Text(country.dial_code)
.foregroundColor(.secondary)
}
.onTapGesture {
self.countryFlag = country.flag
self.countryCode = country.dial_code
self.countryPattern = country.pattern
self.countryLimit = country.limit
presentSheet = false
searchCountry = ""
}
}
.listStyle(.plain)
.searchable(text: $searchCountry, prompt: "Your country")
}
.presentationDetents([.medium, .large])
}
.presentationDetents([.medium, .large])
}
.ignoresSafeArea(.keyboard)
}

var filteredResorts: [CPData] {
if searchCountry.isEmpty {
return counrties
} else {
return counrties.filter { $0.name.contains(searchCountry) }
}
}

var foregroundColor: Color {
if colorScheme == .dark {
return Color(.white)
} else {
return Color(.black)
}
}

var backgroundColor: Color {
if colorScheme == .dark {
return Color(.systemGray5)
} else {
return Color(.systemGray6)
}
}

func applyPatternOnNumbers(_ stringvar: inout String, pattern: String, replacementCharacter: Character) {
var pureNumber = stringvar.replacingOccurrences( of: "[^0-9]", with: "", options: .regularExpression)
for index in 0 ..< pattern.count {
guard index < pureNumber.count else {
stringvar = pureNumber
return
}
let stringIndex = String.Index(utf16Offset: index, in: pattern)
let patternCharacter = pattern[stringIndex]
guard patternCharacter != replacementCharacter else { continue }
pureNumber.insert(patternCharacter, at: stringIndex)
}
stringvar = pureNumber
}
}

extension View {
func placeholder<Content: View>(
when shouldShow: Bool,
alignment: Alignment = .leading,
@ViewBuilder placeholder: () -> Content) -> some View {

ZStack(alignment: alignment) {
placeholder().opacity(shouldShow ? 1 : 0)
self
}
}
}
extension View {
func hideKeyboard() {
let resign = #selector(UIResponder.resignFirstResponder)
UIApplication.shared.sendAction(resign, to: nil, from: nil, for: nil)
}
}
extension View {
func disableWithOpacity(_ condition: Bool) -> some View {
self
.disabled(condition)
.opacity(condition ? 0.6 : 1)
}
}
struct OnboardingButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {

ZStack {
RoundedRectangle(cornerRadius: 10, style: .continuous )
.frame(height: 49)
.foregroundColor(Color(.systemBlue))

configuration.label
.fontWeight(.semibold)
.foregroundColor(Color(.white))
}
}
}

struct PhoneNumberView_Previews: PreviewProvider {
static var previews: some View {
PhoneNumberView(countryCode: "+1", countryFlag: "πŸ‡ΊπŸ‡Έ", countryPattern: "### ### ####", countryLimit: 17)
}
}

Hope it’s was useful. Thanks for reading.

--

--

Andrii
Andrii

Written by Andrii

Debunker ❌ | iOS developer πŸ‘¨πŸ»β€πŸ’»| Apple fun 🍏

Responses (4)