March 9, 2024
57. Применяем маску с номером телефона
В этой статье покажу как можно применить маску с российским номером телефона к тексту внутри TextField без использования UIKit 🙂
Код для добавления маски
extension String {
func makeMaskedNumber(_ mask: String) -> String {
let cleanNumber = components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
var result = ""
var startIndex = cleanNumber.startIndex
for char in mask where startIndex < cleanNumber.endIndex {
if char == "X" {
result.append(cleanNumber[startIndex])
startIndex = cleanNumber.index(after: startIndex)
} else {
result.append(char)
}
}
if let plusIndex = result.firstIndex(of: "+") {
let nextIndex = result.index(after: plusIndex)
let nextNumber = result[nextIndex]
if nextNumber != "7" {
var array = Array(result)
array[1] = "7"
array.insert(nextNumber, at: 2)
result = array.map { String($0) }.joined()
}
}
return result
}
}
Код для вьюхи с текстфилдом
struct MaskedFieldExample: View {
@State private var phoneNumber: String = ""
let mask: String
var body: some View {
TextField("+7", text: $phoneNumber)
.keyboardType(.phonePad)
.textFieldStyle(.roundedBorder)
.onReceive(Just(phoneNumber)) { newValue in
if !newValue.isEmpty {
phoneNumber = newValue.makeMaskedNumber(mask)
}
}
}
}
#Preview
#Preview {
VStack(spacing: 20) {
MaskedFieldExample(mask: "(XXX) XXX-XX-XX")
MaskedFieldExample(mask: "+X (XXX) XXX-XX-XX")
MaskedFieldExample(mask: "+X XXX XXX-XX-XX")
}
.padding(.horizontal)
}
Нюансы
- Если использовать
onChange, то в консоли выводится вот такой лог: onChange(of: String) action tried to update multiple times per frame, а с использованиeмonReceiveтакого нет - В симуляторе и на девайсе маска применяется корректно и с
onChange, и сonReceive, а в unit-тестах пробел после первой семерки не ставится, что приводит к падению теста (т.е. получается не+7 123, а+71 23). Судя по всему, SwiftUI-модификаторы делают дополнительную работу, которая в логике применения маски отсутствует - Первая маска в превью подходит бОльшему количеству стран, чем остальные - они подходят только для телефонов России и Казахстана
Заключение
Мне понравилось, что можно без UITextFieldDelegate сделать нормальную маску в SwiftUI, и в целом она работает как ожидается (должна работать даже на iOS 13).
Код для этой статьи можно посмотреть тут, а другие статьи - тут.