How to Format Textfiled.text in Turkish Phone Format? - ios

I've a textfield which only takes phone number and I'm trying to format it into Turkish format number which looks like this (555) 555 5555. How can I make that in Swift?

You can do something like this on EiditingChange method, this will change first three entries to this () format while user inputs text, you can follow same approach to remove format if user is deleting entries
#IBAction func onEditingChanged(sender: UITextField!)
{
var string = sender.text as NSString
if string.length > 3
{
let range = string.rangeOfString("(")
if range.location == NSNotFound
{
var firstPart = string.substringToIndex(3)
var secondPart = string.substringFromIndex(3)
var string = "(\(firstPart))\(secondPart)"
sender.text = string
}
}
}

Well if we take the assumption that all Turkish numbers are 10 digits long, we can do it as follows.
First lets define a helper function to get the substrings:
func sub(str: String, start: Int, end: Int) -> String {
return str.substringWithRange(Range<String.Index>(start: advance(str.startIndex, start), end: advance(str.startIndex, end)))
}
Now we just apply the function to get the sections of the number:
// Lets say this is the number we get from the textfield
let number = "1234567890"
let start = sub(number, 0, 3) // "123"
let mid = sub(number, 3, 6) // "456"
let end = sub(number, 6, 10) // "7890"
And then we format this into a single string as desired.
let formatNumber = "(\(start)) \(mid) \(end)" // "(123) 456 7890"
Note that this would only work for numbers that have 10 digits (I doubt that all Turkish numbers are). You would need to modify this to format for numbers of different lengths, by specifying different substrings of the start mid and end above.
If you wanted to limit the user to only using 10 digit numbers, you should perform validation on textfield.

var shouldAttemptFormat: Bool = true
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
if textField == self.phoneNumberTextField{
let resultString: String = (textField.text as NSString).stringByReplacingCharactersInRange(range, withString:string)
let oldString: String = self.phoneNumberTextField.text
let oldCount = count(oldString)
let newCount = count(resultString)
shouldAttemptFormat = newCount > oldCount
return true//newCount < 15
}else{
return true
}
// otherwise we should just let them continue
}
// MARK: - phone number formatting
func formatPhoneNumber() {
// this value is determined when textField shouldChangeCharactersInRange is called on a phone
// number cell - if a user is deleting characters we don't want to try to format it, otherwise
// using the current logic below certain deletions will have no effect
if !shouldAttemptFormat {
return
}
// here we are leveraging some of the objective-c NSString functions to help parse and modify
// the phone number... first we strip anything that's not a number from the textfield, and then
// depending on the current value we append formatting characters to make it pretty
let currentValue: NSString = self.phoneNumberTextField.text
let strippedValue: NSString = currentValue.stringByReplacingOccurrencesOfString("[^0-9]", withString: "", options: .RegularExpressionSearch, range: NSMakeRange(0, currentValue.length))
var formattedString: NSString = ""
if strippedValue.length == 0 {
formattedString = "";
}
else if strippedValue.length < 3 {
formattedString = "(" + (strippedValue as String)
}
else if strippedValue.length == 3 {
formattedString = "(" + (strippedValue as String) + ") "
}
else if strippedValue.length < 6 {
formattedString = "(" + strippedValue.substringToIndex(3) + ") " + strippedValue.substringFromIndex(3)
}
else if strippedValue.length == 6 {
formattedString = "(" + strippedValue.substringToIndex(3) + ") " + strippedValue.substringFromIndex(3) + "-"
}
else if strippedValue.length <= 10 {
formattedString = "(" + strippedValue.substringToIndex(3) + ") " + strippedValue.substringWithRange(NSMakeRange(3, 3)) + "-" + strippedValue.substringFromIndex(6)
}
else if strippedValue.length >= 11 {
formattedString = "(" + strippedValue.substringToIndex(3) + ") " + strippedValue.substringWithRange(NSMakeRange(3, 3)) + "-" + strippedValue.substringWithRange(NSMakeRange(6, 4))
}
self.phoneNumberTextField.text = formattedString as String
}
I use this code as it looks above, It works. When user type any character, formatPhoneNumber function works, for each char.
self.phoneNumberTextField.addTarget(self, action: "formatPhoneNumber", forControlEvents: .EditingChanged)
You must to add this line in viewDidLoad.
Hopefully, it will work for you

Related

Autoformat UITextfield in phone number format XXX-XXX-XXXX in iOS

I'm trying to autoformat my textfield in the format XXX-XXX-XXXX. The rules are that it should be in the format as mentioned and the first number should be greater than zero and should be of max 10 digits, the regex for this is already added in my function. Below are the methods I'm using
#IBAction func validateAction(_ sender: Any) {
guard let phoneNumber = phoneNumber.text else {return }
if validatePhoneNumber(phoneNumber: phoneNumber) {
errorMessage.text = "Validation successful"
} else {
errorMessage.text = "Validation failed"
}
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let currentText = textField.text as NSString? else {return true}
let textString = currentText.replacingCharacters(in: range, with: string)
if textField == phoneNumber {
return textField.updatePhoneNumber(string, textString)
}else{
return true
}
}
func validatePhoneNumber(phoneNumber: String) -> Bool {
let phoneRegex: String = "^[2-9]\\d{2}-\\d{3}-\\d{4}$"
return NSPredicate(format: "SELF MATCHES %#", phoneRegex).evaluate(with: phoneNumber)
}
extension UITextField {
func updatePhoneNumber(_ replacementString: String?, _ textString: String?) -> Bool {
guard let textCount = textString?.count else {return true}
guard let currentString = self.text else {return true}
if replacementString == "" {
return true
} else if textCount == 4 {
self.text = currentString + "-"
} else if textCount == 8 {
self.text = currentString + "-"
} else if textCount > 12 || replacementString == " " {
return false
}
return true
}
}
This works to some extent, now the issue is, user can manually intervene and disrupt the format for eg: if I entered, 234-567-8990, user can place the cursor just before 5 and backspace and type in at the end or between like 567-89900000 or 234567-8990. By validating the regular expression it will give an error but I want to re-adjust the format as user types in. For eg: in the earlier scenario if the user is on cursor before 5 and backspaces it should not remove the dash (-) but just removes 4 and re-adjust format like 235-678-990. Is there any simple way to do this? Any help is appreciated
I use this extension for String. It's small and real helpful.
extension String {
func applyPatternOnNumbers(pattern: String, replacmentCharacter: Character) -> String {
var pureNumber = self.replacingOccurrences( of: "[^0-9]", with: "", options: .regularExpression)
for index in 0 ..< pattern.count {
guard index < pureNumber.count else { return pureNumber }
let stringIndex = String.Index(encodedOffset: index)
let patternCharacter = pattern[stringIndex]
guard patternCharacter != replacmentCharacter else { continue }
pureNumber.insert(patternCharacter, at: stringIndex)
}
return pureNumber
}
just set a needed mask
text.applyPatternOnNumbers(pattern: "+# (###) ###-##-##", replacmentCharacter: "#")
and that's all
#SonuP very good question. I believe you want to format the phone and also keep the cursor in correct position. If so, then this task is slightly more complex than just formatting. You need to reformat the code and update the cursor position.
Note that my solution follows the specific formatting and if it does not match yours, then tweak the code slightly:
Swift 5
public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
var text = textField.text ?? ""
text.replaceSubrange(range.toRange(string: text), with: string)
if let phone = (textField.text ?? "").replacePhoneSubrange(range, with: string) {
// update text in the field
textField.text = text
// update cursor position
if text.count == range.location + string.count || text.hasSuffix(")") && text.count == range.location + string.count + 1 { // end
if phone.hasSuffix(")") {
textField.setCursor(phone.count - 1)
}
else {
textField.setCursor(phone.count)
}
}
else {
textField.setCursor(min(range.location + string.count, phone.count-1))
}
}
return false
}
Also you will need the following extensions:
// MARK: - Helpful methods
extension NSRange {
/// Convert to Range for given string
///
/// - Parameter string: the string
/// - Returns: range
func toRange(string: String) -> Range<String.Index> {
let range = string.index(string.startIndex, offsetBy: self.lowerBound)..<string.index(string.startIndex, offsetBy: self.upperBound)
return range
}
static func fromRange(_ range: Range<String.Index>, inString string: String) -> NSRange {
let s = string.distance(from: string.startIndex, to: range.lowerBound)
let e = string.distance(from: string.startIndex, to: range.upperBound)
return NSMakeRange(s, e-s)
}
}
// MARK: - Helpful methods
extension String {
/// Raplace string in phone
public func replacePhoneSubrange(_ range: NSRange, with string: String) -> String? {
if let phone = self.phone { // +11-111-111-1111 (111)
var numberString = phone.phoneNumber // 111111111111111
let newRange = self.toPhoneRange(range: range)
numberString.replaceSubrange(newRange.toRange(string: phone), with: string)
return numberString.phone
}
return nil
}
/// Phone number string
public var phoneNumber: String {
let components = self.components(
separatedBy: CharacterSet.decimalDigits.inverted)
var decimalString = NSString(string: components.joined(separator: ""))
while decimalString.hasPrefix("0") {
decimalString = decimalString.substring(from: 1) as NSString
}
return String(decimalString)
}
/// Get phone range
public func toPhoneRange(range: NSRange) -> NSRange {
let start = range.location
let end = start + range.length
let s2 = self.convertPhoneLocation(location: start)
let e2 = self.convertPhoneLocation(location: end)
return NSRange(location: s2, length: e2-s2)
}
/// Get cursor location for phone
public func convertPhoneLocation(location: Int) -> Int {
let substring = self[self.startIndex..<self.index(self.startIndex, offsetBy: location)]
return String(substring).phoneNumber.count
}
}
// MARK: - Helpful methods
extension UITextField {
/// Set cursor
///
/// - Parameter position: the position to set
func setCursor(_ position: Int) {
if let startPosition = self.position(from: self.beginningOfDocument, offset: position) {
let endPosition = startPosition
self.selectedTextRange = self.textRange(from: startPosition, to: endPosition)
}
}
}
// MARK: - Helpful methods
extension String {
/// phone formatting
public var phone: String? {
let components = self.components(
separatedBy: CharacterSet.decimalDigits.inverted)
var decimalString = NSString(string: components.joined(separator: ""))
while decimalString.hasPrefix("0") {
decimalString = decimalString.substring(from: 1) as NSString
}
let length = decimalString.length
let hasLeadingOne = length > 0 && length == 11
let hasLeadingTwo = length > 11
if length > 15 {
return nil
}
var index = 0 as Int
let formattedString = NSMutableString()
if hasLeadingOne || hasLeadingTwo {
let len = hasLeadingTwo ? 2 : 1
let areaCode = decimalString.substring(with: NSMakeRange(index, len))
formattedString.appendFormat("+%#-", areaCode)
index += len
}
if (length - index) > 3 {
let areaCode = decimalString.substring(with: NSMakeRange(index, 3))
formattedString.appendFormat("%#-", areaCode)
index += 3
}
if length - index == 4 && length == 7 { // xxx-xxxx
let prefix = decimalString.substring(with: NSMakeRange(index, 4))
formattedString.append(prefix)
index += 4
}
else if length - index > 3 {// xxx-xxx-x...
let prefix = decimalString.substring(with: NSMakeRange(index, 3))
formattedString.appendFormat("%#-", prefix)
index += 3
}
if length - index == 4 { // xxx-xxx-xxxx
let prefix = decimalString.substring(with: NSMakeRange(index, 4))
formattedString.append(prefix)
index += 4
}
// format phone extenstion
if length - index > 4 {
let prefix = decimalString.substring(with: NSMakeRange(index, 4))
formattedString.appendFormat("%# ", prefix)
index += 4
}
let remainder = decimalString.substring(from: index)
if length > 12 {
formattedString.append("(\(remainder))")
}
else {
formattedString.append(remainder)
}
return (formattedString as String).trimmingCharacters(in: NSCharacterSet.whitespacesAndNewlines)
}
}
Use this in textfield delegate method :
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if range.length > 0 {
return true
}
if string == "" {
return false
}
if range.location > 11 {
return false
}
var originalText = textField.text
let replacementText = string.replacingOccurrences(of: " ", with: "")
if !CharacterSet.decimalDigits.isSuperset(of: CharacterSet(charactersIn: replacementText)) {
return false
}
if range.location == 3 || range.location == 7 {
originalText?.append("-")
textField.text = originalText
}
return true
}

Swift - unable custom cursor position from user in UITextField

I have a textfield and I do not want users to be able to set the cursor position by touching and holding at the textfield. Is there a way to always have the cursor to be at the very end of the textfield?
First of all, its not good to change this type of default behavior of textField.
As per your comment if you want to add credit card and you are facing some input complexity then I found code from my old project and might be it will work for you.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let replacementStringIsLegal = string.rangeOfCharacter(from: NSCharacterSet(charactersIn: "0123456789").inverted) == nil
if !replacementStringIsLegal {
return false
}
let newString = (textField.text! as NSString).replacingCharacters(in: range, with: string)
let components = newString.components(separatedBy: NSCharacterSet(charactersIn: "0123456789").inverted)
let decimalString = components.joined(separator: "") as NSString
let length = decimalString.length
let hasLeadingOne = length > 0 && decimalString.character(at: 0) == (1 as unichar)
if length == 0 || (length > 16 && !hasLeadingOne) || length > 19
{
let newLength = (textField.text! as NSString).length + (string as NSString).length - range.length as Int
return (newLength > 16) ? false : true
}
var index = 0 as Int
let formattedString = NSMutableString()
if hasLeadingOne
{
formattedString.append("1 ")
index += 1
}
if length - index > 4
{
let prefix = decimalString.substring(with: NSMakeRange(index, 4))
formattedString.appendFormat("%# ", prefix)
index += 4
}
if length - index > 4
{
let prefix = decimalString.substring(with: NSMakeRange(index, 4))
formattedString.appendFormat("%# ", prefix)
index += 4
}
if length - index > 4
{
let prefix = decimalString.substring(with: NSMakeRange(index, 4))
formattedString.appendFormat("%# ", prefix)
index += 4
}
let remainder = decimalString.substring(from: index)
formattedString.append(remainder)
textField.text = formattedString as String
return false
}

NSNumberFormatter grouping

I would like to format string with numbers to look like this: XX XX XX (e.g. 12 34 56)
I'm using NSNumberFormatter with grouping separator:
let formatter = NSNumberFormatter()
formatter.usesGroupingSeparator = true
formatter.groupingSize = 2
formatter.groupingSeparator = " "
if let number = Int(theText.stringByReplacingOccurrencesOfString(" ", withString: "")) {
let numberToFormat = NSNumber(integer:number)
textField.text = formatter.stringFromNumber(numberToFormat)
}
This will work fine, if you enter number 123456 - it is formatted to 12 34 56
I use this inside shouldChangeCharactersInRange method so it should format number as you type. Grouping separator defines numbers are grouped by groupingSize value from right side, so when you type in 123456 this is how text changes:
1
12
1 23
12 34
1 23 45
12 34 45
and I would like to format it as following:
1
12
12 3
12 34
12 34 5
12 34 56
Is it possible to define from which side grouping separator groups numbers?
Not sure if you have to covert to Int, how about add a "0" at the end? Then remove the last "0".
let formatter = NSNumberFormatter()
formatter.usesGroupingSeparator = true
formatter.groupingSize = 2
formatter.groupingSeparator = " "
var str = "12345"
if str.characters.count % 2 != 0 {
str += "0"
}
if let number = Int(str.stringByReplacingOccurrencesOfString(" ", withString: "")) {
let numberToFormat = NSNumber(integer:number)
formatter.stringFromNumber(numberToFormat)
}
Another solution:
let string = "12345"
var results = [String]()
for index in 0 ..< string.characters.count-1 {
if index % 2 == 0 {
let range = NSRange(location: index, length: 2)
results.append((string as NSString).substringWithRange(range))
}
}
if string.characters.count % 2 != 0 {
let last = (string as NSString).substringFromIndex(string.characters.count-1)
results.append(last)
}
Swit 4
You have to use the delegate of UITextField : func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String)
this works fine :
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if textField == YourTextField {
if (textField.text?.count)! < 8 {
let formatter = NumberFormatter()
formatter.usesGroupingSeparator = true
formatter.locale = NSLocale(localeIdentifier: "en_GB") as Locale?
formatter.groupingSize = 2
formatter.groupingSeparator = "-" // if you want the format XX-XX-XX
// Uses the grouping separator corresponding
if let groupingSeparator = formatter.groupingSeparator {
if string == groupingSeparator {
return true
}
if let textWithoutGroupingSeparator = textField.text?.replacingOccurrences(of: groupingSeparator, with: "") {
var totalTextWithoutGroupingSeparators = textWithoutGroupingSeparator + string
if string == "" { // pressed Backspace key
totalTextWithoutGroupingSeparators.characters.removeLast()
}
if let numberWithoutGroupingSeparator = formatter.number(from: totalTextWithoutGroupingSeparators),
let formattedText = formatter.string(from: numberWithoutGroupingSeparator) {
print(numberWithoutGroupingSeparator)
numberWithoutGroupingSeparators = String(format: "%#", numberWithoutGroupingSeparator)
textField.text = formattedText
return false
}
}
}
return true
}
}
return true
}

Formatting Phone number in Swift

I'm formatting my textfiled text once the user start typing the phone number into this format type 0 (555) 444 66 77 and it is working fine but once I get the number from the server I get it like this 05554446677 So please could you tell me how I can edit it in the same format once I get it fro the server?
My code once I start typing:
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
if textField == phoneNumberTextField{
var newString = (textField.text as NSString).stringByReplacingCharactersInRange(range, withString: string)
var components = newString.componentsSeparatedByCharactersInSet(NSCharacterSet.decimalDigitCharacterSet().invertedSet)
var decimalString = "".join(components) as NSString
var length = decimalString.length
var hasLeadingOne = length > 0 && decimalString.characterAtIndex(0) == (1 as unichar)
if length == 0 || (length > 11 && !hasLeadingOne) || length > 12{
var newLength = (textField.text as NSString).length + (string as NSString).length - range.length as Int
return (newLength > 11) ? false : true
}
var index = 0 as Int
var formattedString = NSMutableString()
if hasLeadingOne{
formattedString.appendString("1 ")
index += 1
}
if (length - index) > 1{
var zeroNumber = decimalString.substringWithRange(NSMakeRange(index, 1))
formattedString.appendFormat("%# ", zeroNumber)
index += 1
}
if (length - index) > 3{
var areaCode = decimalString.substringWithRange(NSMakeRange(index, 3))
formattedString.appendFormat("(%#) ", areaCode)
index += 3
}
if (length - index) > 3{
var prefix = decimalString.substringWithRange(NSMakeRange(index, 3))
formattedString.appendFormat("%# ", prefix)
index += 3
}
if (length - index) > 3{
var prefix = decimalString.substringWithRange(NSMakeRange(index, 2))
formattedString.appendFormat("%# ", prefix)
index += 2
}
var remainder = decimalString.substringFromIndex(index)
formattedString.appendString(remainder)
textField.text = formattedString as String
return false
}else{
return true
}
}
Masked number typing
/// mask example: `+X (XXX) XXX-XXXX`
func format(with mask: String, phone: String) -> String {
let numbers = phone.replacingOccurrences(of: "[^0-9]", with: "", options: .regularExpression)
var result = ""
var index = numbers.startIndex // numbers iterator
// iterate over the mask characters until the iterator of numbers ends
for ch in mask where index < numbers.endIndex {
if ch == "X" {
// mask requires a number in this place, so take the next one
result.append(numbers[index])
// move numbers iterator to the next index
index = numbers.index(after: index)
} else {
result.append(ch) // just append a mask character
}
}
return result
}
Call the above function from the UITextField delegate method:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let text = textField.text else { return false }
let newString = (text as NSString).replacingCharacters(in: range, with: string)
textField.text = format(with: "+X (XXX) XXX-XXXX", phone: newString)
return false
}
So, that works better.
"" => ""
"0" => "+0"
"412" => "+4 (12"
"12345678901" => "+1 (234) 567-8901"
"a1_b2-c3=d4 e5&f6|g7h8" => "+1 (234) 567-8"
Really simple solution:
extension String {
func applyPatternOnNumbers(pattern: String, replacementCharacter: Character) -> String {
var pureNumber = self.replacingOccurrences( of: "[^0-9]", with: "", options: .regularExpression)
for index in 0 ..< pattern.count {
guard index < pureNumber.count else { return pureNumber }
let stringIndex = String.Index(utf16Offset: index, in: pattern)
let patternCharacter = pattern[stringIndex]
guard patternCharacter != replacementCharacter else { continue }
pureNumber.insert(patternCharacter, at: stringIndex)
}
return pureNumber
}
}
Usage:
guard let text = textField.text else { return }
textField.text = text.applyPatternOnNumbers(pattern: "+# (###) ###-####", replacmentCharacter: "#")
Swift 3 & 4
This solution removes any non-numeric characters before applying formatting. It returns nil if the source phone number cannot be formatted according to assumptions.
Swift 4
The Swift 4 solution accounts for the deprecation of CharacterView and Sting becoming a collection of characters as the CharacterView is.
import Foundation
func format(phoneNumber sourcePhoneNumber: String) -> String? {
// Remove any character that is not a number
let numbersOnly = sourcePhoneNumber.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
let length = numbersOnly.count
let hasLeadingOne = numbersOnly.hasPrefix("1")
// Check for supported phone number length
guard length == 7 || (length == 10 && !hasLeadingOne) || (length == 11 && hasLeadingOne) else {
return nil
}
let hasAreaCode = (length >= 10)
var sourceIndex = 0
// Leading 1
var leadingOne = ""
if hasLeadingOne {
leadingOne = "1 "
sourceIndex += 1
}
// Area code
var areaCode = ""
if hasAreaCode {
let areaCodeLength = 3
guard let areaCodeSubstring = numbersOnly.substring(start: sourceIndex, offsetBy: areaCodeLength) else {
return nil
}
areaCode = String(format: "(%#) ", areaCodeSubstring)
sourceIndex += areaCodeLength
}
// Prefix, 3 characters
let prefixLength = 3
guard let prefix = numbersOnly.substring(start: sourceIndex, offsetBy: prefixLength) else {
return nil
}
sourceIndex += prefixLength
// Suffix, 4 characters
let suffixLength = 4
guard let suffix = numbersOnly.substring(start: sourceIndex, offsetBy: suffixLength) else {
return nil
}
return leadingOne + areaCode + prefix + "-" + suffix
}
extension String {
/// This method makes it easier extract a substring by character index where a character is viewed as a human-readable character (grapheme cluster).
internal func substring(start: Int, offsetBy: Int) -> String? {
guard let substringStartIndex = self.index(startIndex, offsetBy: start, limitedBy: endIndex) else {
return nil
}
guard let substringEndIndex = self.index(startIndex, offsetBy: start + offsetBy, limitedBy: endIndex) else {
return nil
}
return String(self[substringStartIndex ..< substringEndIndex])
}
}
Swift 3
import Foundation
func format(phoneNumber sourcePhoneNumber: String) -> String? {
// Remove any character that is not a number
let numbersOnly = sourcePhoneNumber.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
let length = numbersOnly.characters.count
let hasLeadingOne = numbersOnly.hasPrefix("1")
// Check for supported phone number length
guard length == 7 || (length == 10 && !hasLeadingOne) || (length == 11 && hasLeadingOne) else {
return nil
}
let hasAreaCode = (length >= 10)
var sourceIndex = 0
// Leading 1
var leadingOne = ""
if hasLeadingOne {
leadingOne = "1 "
sourceIndex += 1
}
// Area code
var areaCode = ""
if hasAreaCode {
let areaCodeLength = 3
guard let areaCodeSubstring = numbersOnly.characters.substring(start: sourceIndex, offsetBy: areaCodeLength) else {
return nil
}
areaCode = String(format: "(%#) ", areaCodeSubstring)
sourceIndex += areaCodeLength
}
// Prefix, 3 characters
let prefixLength = 3
guard let prefix = numbersOnly.characters.substring(start: sourceIndex, offsetBy: prefixLength) else {
return nil
}
sourceIndex += prefixLength
// Suffix, 4 characters
let suffixLength = 4
guard let suffix = numbersOnly.characters.substring(start: sourceIndex, offsetBy: suffixLength) else {
return nil
}
return leadingOne + areaCode + prefix + "-" + suffix
}
extension String.CharacterView {
/// This method makes it easier extract a substring by character index where a character is viewed as a human-readable character (grapheme cluster).
internal func substring(start: Int, offsetBy: Int) -> String? {
guard let substringStartIndex = self.index(startIndex, offsetBy: start, limitedBy: endIndex) else {
return nil
}
guard let substringEndIndex = self.index(startIndex, offsetBy: start + offsetBy, limitedBy: endIndex) else {
return nil
}
return String(self[substringStartIndex ..< substringEndIndex])
}
}
Example
func testFormat(sourcePhoneNumber: String) -> String {
if let formattedPhoneNumber = format(phoneNumber: sourcePhoneNumber) {
return "'\(sourcePhoneNumber)' => '\(formattedPhoneNumber)'"
}
else {
return "'\(sourcePhoneNumber)' => nil"
}
}
print(testFormat(sourcePhoneNumber: "1 800 222 3333"))
print(testFormat(sourcePhoneNumber: "18002223333"))
print(testFormat(sourcePhoneNumber: "8002223333"))
print(testFormat(sourcePhoneNumber: "2223333"))
print(testFormat(sourcePhoneNumber: "18002223333444"))
print(testFormat(sourcePhoneNumber: "Letters8002223333"))
print(testFormat(sourcePhoneNumber: "1112223333"))
Example Output
'1 800 222 3333' => '1 (800) 222-3333'
'18002223333' => '1 (800) 222-3333'
'8002223333' => '(800) 222-3333'
'2223333' => '222-3333'
'18002223333444' => nil
'Letters8002223333' => '(800) 222-3333'
'1112223333' => nil
Manipulations with characters in String are not very straightforward. You need following:
Swift 2.1
let s = "05554446677"
let s2 = String(format: "%# (%#) %# %# %#", s.substringToIndex(s.startIndex.advancedBy(1)),
s.substringWithRange(s.startIndex.advancedBy(1) ... s.startIndex.advancedBy(3)),
s.substringWithRange(s.startIndex.advancedBy(4) ... s.startIndex.advancedBy(6)),
s.substringWithRange(s.startIndex.advancedBy(7) ... s.startIndex.advancedBy(8)),
s.substringWithRange(s.startIndex.advancedBy(9) ... s.startIndex.advancedBy(10))
)
Swift 2.0
let s = "05554446677"
let s2 = String(format: "%# (%#) %# %# %#", s.substringToIndex(advance(s.startIndex, 1)),
s.substringWithRange(advance(s.startIndex, 1) ... advance(s.startIndex, 3)),
s.substringWithRange(advance(s.startIndex, 4) ... advance(s.startIndex, 6)),
s.substringWithRange(advance(s.startIndex, 7) ... advance(s.startIndex, 8)),
s.substringWithRange(advance(s.startIndex, 9) ... advance(s.startIndex, 10))
)
Code will print
0 (555) 444 66 77
Swift 4
Create this function and call on text field event Editing Changed
private func formatPhone(_ number: String) -> String {
let cleanNumber = number.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
let format: [Character] = ["X", "X", "X", "-", "X", "X", "X", "-", "X", "X", "X", "X"]
var result = ""
var index = cleanNumber.startIndex
for ch in format {
if index == cleanNumber.endIndex {
break
}
if ch == "X" {
result.append(cleanNumber[index])
index = cleanNumber.index(after: index)
} else {
result.append(ch)
}
}
return result
}
Swift 5.1 Update on Дарія Прокопович great solution
extension String {
func applyPatternOnNumbers(pattern: String, replacmentCharacter: Character) -> String {
var pureNumber = self.replacingOccurrences( of: "[^0-9]", with: "", options: .regularExpression)
for index in 0 ..< pattern.count {
guard index < pureNumber.count else { return pureNumber }
let stringIndex = String.Index(utf16Offset: index, in: self)
let patternCharacter = pattern[stringIndex]
guard patternCharacter != replacmentCharacter else { continue }
pureNumber.insert(patternCharacter, at: stringIndex)
}
return pureNumber
}
}
Usage:
let formattedText = text.applyPatternOnNumbers(pattern: "+# (###) ###-####", replacmentCharacter: "#")
You can use this library https://github.com/luximetr/AnyFormatKit
Example
let phoneFormatter = DefaultTextFormatter(textPattern: "### (###) ###-##-##")
phoneFormatter.format("+123456789012") // +12 (345) 678-90-12
Very simple to use.
Swift 3 but should also be translatable to Swift 4
ErrorHandling
enum PhoneNumberFormattingError: Error {
case wrongCharactersInPhoneNumber
case phoneNumberLongerThanPatternAllowes
}
Create Patterns
enum PhoneNumberFormattingPatterns: String {
case mobile = "+xx (yxx) xxxxxxxxxxx"
case home = "+xx (yxxx) xxxx-xxx"
}
Insert Function
/**
Formats a phone-number to correct format
- Parameter pattern: The pattern to format the phone-number.
- Example:
- x: Says that this should be a digit.
- y: Says that this digit cannot be a "0".
- The length of the pattern restricts also the length of allowed phone-number digits.
- phone-number: "+4306641234567"
- pattern: "+xx (yxx) xxxxxxxxxxx"
- result: "+43 (664) 1234567"
- Throws:
- PhoneNumberFormattingError
- wrongCharactersInPhoneNumber: if phone-number contains other characters than digits.
- phoneNumberLongerThanPatternAllowes: if phone-number is longer than pattern allows.
- Returns:
- The formatted phone-number due to the pattern.
*/
extension String {
func vpToFormattedPhoneNumber(withPattern pattern: PhoneNumberFormattingPatterns) throws -> String {
let phoneNumber = self.replacingOccurrences(of: "+", with: "")
var retVal: String = ""
var index = 0
for char in pattern.rawValue.lowercased().characters {
guard index < phoneNumber.characters.count else {
return retVal
}
if char == "x" {
let charIndex = phoneNumber.index(phoneNumber.startIndex, offsetBy: index)
let phoneChar = phoneNumber[charIndex]
guard "0"..."9" ~= phoneChar else {
throw PhoneNumberFormattingError.wrongCharactersInPhoneNumber
}
retVal.append(phoneChar)
index += 1
} else if char == "y" {
var charIndex = phoneNumber.index(phoneNumber.startIndex, offsetBy: index)
var indexTemp = 1
while phoneNumber[charIndex] == "0" {
charIndex = phoneNumber.index(phoneNumber.startIndex, offsetBy: index + indexTemp)
indexTemp += 1
}
let phoneChar = phoneNumber[charIndex]
guard "0"..."9" ~= phoneChar else {
throw PhoneNumberFormattingError.wrongCharactersInPhoneNumber
}
retVal.append(phoneChar)
index += indexTemp
} else {
retVal.append(char)
}
}
if phoneNumber.endIndex > phoneNumber.index(phoneNumber.startIndex, offsetBy: index) {
throw PhoneNumberFormattingError.phoneNumberLongerThanPatternAllowes
}
return retVal
}
}
Usage
let phoneNumber = "+4306641234567"
let phoneNumber2 = "4343211234567"
do {
print(try phoneNumber.vpToFormattedPhoneNumber(withPattern: .mobile))
print(try phoneNumber2.vpToFormattedPhoneNumber(withPattern: .home))
} catch let error as PhoneNumberFormattingError {
switch error {
case .wrongCharactersInPhoneNumber:
print("wrong characters in phone number")
case .phoneNumberLongerThanPatternAllowes:
print("too long phone number")
default:
print("unknown error")
}
} catch {
print("something other went wrong")
}
// output: +43 (664) 1234567
// output: +43 (4321) 1234-567
There are a number of good answers here but I took a completely different approach and thought I'd share in case it helps.
To start I broke up the formatting steps and components into their own separate responsibilities.
Phone number format can generally be broken down into local, domestic or international format types that vary by string length.
I defined the types:
/// Defines the three different types of formatting phone numbers use
///
/// - local: Numbers used locally.
/// - domestic: Numbers used locally including area codes.
/// - international: Numbers used internationally with country codes.
public enum PhoneFormatType {
case local
case domestic
case international
}
Then defined the separators available to format a phone number string:
// Defines separators that are available for use in formatting
// phone number strings.
public enum PhoneFormatSeparator {
case hyphen
case plus
case space
case parenthesisLH
case parenthesisRH
case slash
case backslash
case pipe
case asterisk
public var value: String {
switch self {
case .hyphen: return "-"
case .plus: return "+"
case .space: return " "
case .parenthesisLH: return "("
case .parenthesisRH: return ")"
case .slash: return "/"
case .backslash: return "\\"
case .pipe: return "|"
case .asterisk: return "*"
}
}
}
Next I defined formatting rules that specify the index (in a phone number string) where the separators like +,-,etc are inserted.
// defines the separators that should be inserted in a phone number string
// and the indexes where they should be applied
public protocol PhoneNumberFormatRule {
// the index in a phone number where this separator should be applied
var index: Int { get set }
// the priority in which this rule should be applied. Sorted in inverse, 0 is highest priority, higher numbers are lower priority
var priority: Int { get set }
// the separator to use at this index
var separator: PhoneFormatSeparator { get set }
}
/// Default implementation of PhoneNumberFormatRule
open class PNFormatRule: PhoneNumberFormatRule {
public var index: Int
public var priority: Int
public var separator: PhoneFormatSeparator
public init(_ index: Int, separator: PhoneFormatSeparator, priority: Int = 0) {
self.index = index
self.separator = separator
self.priority = priority
}
}
With these defined, I created rulesets that associate rules with a given format type.
/// Defines the rule sets associated with a given phone number type.
/// e.g. international/domestic/local
public protocol PhoneFormatRuleset {
/// The type of phone number formatting to which these rules apply
var type: PhoneFormatType { get set }
/// A collection of rules to apply for this phone number type.
var rules: [PhoneNumberFormatRule] { get set }
/// The maximum length a number using this format ruleset should be. (Inclusive)
var maxLength: Int { get set }
}
With everything defined this way, you can setup rulesets quickly to suit whatever format you need.
Here's an example of a ruleset that defines 3 rules for a hyphen formatted phone number string typically used in the US:
// Formats phone numbers:
// .local: 123-4567
// .domestic: 123-456-7890
// .international: +1 234-567-8901
static func usHyphen() -> [PhoneFormatRuleset] {
return [
PNFormatRuleset(.local, rules: [
PNFormatRule(3, separator: .hyphen)
], maxLength: 7),
PNFormatRuleset(.domestic, rules: [
PNFormatRule(3, separator: .hyphen),
PNFormatRule(6, separator: .hyphen)
], maxLength: 10),
PNFormatRuleset(.international, rules: [
PNFormatRule(0, separator: .plus),
PNFormatRule(1, separator: .space),
PNFormatRule(4, separator: .hyphen),
PNFormatRule(7, separator: .hyphen)
], maxLength: 11)
]
}
The (not so) heavy lifting of the formatting logic happens here:
// formats a string using the format rule provided at initialization
public func format(number: String) -> String {
// strip non numeric characters
let n = number.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
// bail if we have an empty string, or if no ruleset is defined to handle formatting
guard n.count > 0, let type = type(for: n.count), let ruleset = ruleset(for: type) else {
return n
}
// this is the string we'll return
var formatted = ""
// enumerate the numeric string
for (i,character) in n.enumerated() {
// bail if user entered more numbers than allowed for our formatting ruleset
guard i <= ruleset.maxLength else {
break
}
// if there is a separator defined to be inserted at this index then add it to the formatted string
if let separator = ruleset.separator(for: i) {
formatted+=separator
}
// now append the character
formatted+="\(character)"
}
return formatted
}
I've created a framework with a sample project you can look through here: https://github.com/appteur/phoneformat
Here is how it works as you type:
I also set it up so you can just import it with cocoapods.
pod 'SwiftPhoneFormat', '1.0.0'
Then use it:
import SwiftPhoneFormat
var formatter = PhoneFormatter(rulesets: PNFormatRuleset.usParethesis())
let formatted = formatter.format(number: numberString)
This is the extension which will full fill your requirement:
extension String {
func convertToInternationalFormat() -> String {
let isMoreThanTenDigit = self.count > 10
_ = self.startIndex
var newstr = ""
if isMoreThanTenDigit {
newstr = "\(self.dropFirst(self.count - 10))"
}
else if self.count == 10{
newstr = "\(self)"
}
else {
return "number has only \(self.count) digits"
}
if newstr.count == 10 {
let internationalString = "(\(newstr.dropLast(7))) \(newstr.dropLast(4).dropFirst(3)) \(newstr.dropFirst(6).dropLast(2)) \(newstr.dropFirst(8))"
newstr = internationalString
}
return newstr
}
}
INPUT :
var str1 = "9253248954"
var str2 = "+19253248954"
var str3 = "19253248954"
OUTPUT :
str1.convertToInternationalFormat() // "(925) 324 89 54"
str2.convertToInternationalFormat() // "(925) 324 89 54"
str3.convertToInternationalFormat() // "(925) 324 89 54"
If you rather to do it without using a library.
Here is a link to the best example or you can use the code below.
https://ivrodriguez.com/format-phone-numbers-in-swift/
A simple code snippet to format 10 digit phone numbers in Swift 5.0, instead of including a big library, just implement a delegate function and a formatting function:
The UITextFieldDelegate function
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
var fullString = textField.text ?? ""
fullString.append(string)
if range.length == 1 {
textField.text = format(phoneNumber: fullString, shouldRemoveLastDigit: true)
} else {
textField.text = format(phoneNumber: fullString)
}
return false
}
The formatting function:
func format(phoneNumber: String, shouldRemoveLastDigit: Bool = false) -> String {
guard !phoneNumber.isEmpty else { return "" }
guard let regex = try? NSRegularExpression(pattern: "[\\s-\\(\\)]", options: .caseInsensitive) else { return "" }
let r = NSString(string: phoneNumber).range(of: phoneNumber)
var number = regex.stringByReplacingMatches(in: phoneNumber, options: .init(rawValue: 0), range: r, withTemplate: "")
if number.count > 10 {
let tenthDigitIndex = number.index(number.startIndex, offsetBy: 10)
number = String(number[number.startIndex..<tenthDigitIndex])
}
if shouldRemoveLastDigit {
let end = number.index(number.startIndex, offsetBy: number.count-1)
number = String(number[number.startIndex..<end])
}
if number.count < 7 {
let end = number.index(number.startIndex, offsetBy: number.count)
let range = number.startIndex..<end
number = number.replacingOccurrences(of: "(\\d{3})(\\d+)", with: "($1) $2", options: .regularExpression, range: range)
} else {
let end = number.index(number.startIndex, offsetBy: number.count)
let range = number.startIndex..<end
number = number.replacingOccurrences(of: "(\\d{3})(\\d{3})(\\d+)", with: "($1) $2-$3", options: .regularExpression, range: range)
}
return number
}
SwiftUI code for mobile number formatting textfield
struct MobileNumberTextFieldContainer: UIViewRepresentable {
private var placeholder : String
private var text : Binding<String>
init(_ placeholder:String, text:Binding<String>) {
self.placeholder = placeholder
self.text = text
}
func makeCoordinator() -> MobileNumberTextFieldContainer.Coordinator {
Coordinator(self)
}
func makeUIView(context: UIViewRepresentableContext<MobileNumberTextFieldContainer>) -> UITextField {
let innertTextField = UITextField(frame: .zero)
innertTextField.placeholder = placeholder
innertTextField.text = text.wrappedValue
innertTextField.delegate = context.coordinator
context.coordinator.setup(innertTextField)
return innertTextField
}
func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<MobileNumberTextFieldContainer>) {
uiView.text = self.text.wrappedValue
}
class Coordinator: NSObject, UITextFieldDelegate {
var parent: MobileNumberTextFieldContainer
init(_ textFieldContainer: MobileNumberTextFieldContainer) {
self.parent = textFieldContainer
}
func setup(_ textField:UITextField) {
textField.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged)
}
#objc func textFieldDidChange(_ textField: UITextField) {
var isCursorLast = false
var cursorPosition = 0
let textLenght = textField.text?.count ?? 0
if let selectedRange = textField.selectedTextRange {
cursorPosition = textField.offset(from: textField.beginningOfDocument, to: selectedRange.start)
print("\(cursorPosition) lengh = \(textLenght)")
if cursorPosition < textLenght {
isCursorLast = true
}
}
textField.text = textField.text?.applyPatternOnNumbers(pattern: "+# (###) ###-####", replacementCharacter: "#")
//textField.text = textField.text ?? "".format(phoneNumber: textField.text ?? "")
self.parent.text.wrappedValue = textField.text ?? ""
if isCursorLast {
isCursorLast = false
let arbitraryValue: Int = cursorPosition
if let newPosition = textField.position(from: textField.beginningOfDocument, offset: arbitraryValue) {
textField.selectedTextRange = textField.textRange(from: newPosition, to: newPosition)
}
}
}
}
}
I have use the same formatting function which is #Mark Wilson used
And simply we can add this in our view
MobileNumberTextFieldContainer("Phone Number", text: $phoneNumber)
SwiftUI
My answer tweaks and builds on Mobile Dan's answer and adapts it for a SwiftUI TextField. If formatting fails or it's less than 10 numbers, it will return the unformatted string. This works with the phone number suggestion feature, assuming a one digit country code. Should be easy to adapt for multi-digit country codes.
TextField("Phone", text: $phoneNumber)
.keyboardType(.numberPad)
.textContentType(.telephoneNumber)
.onChange(of: phoneNumber) { _ in
phoneNumber = phoneNumber.formatPhoneNumber()
}
String Extensions:
extension String {
func formatPhoneNumber() -> String {
// Remove any character that is not a number
let numbersOnly = self.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
let length = numbersOnly.count
// Check for supported phone number length
if length > 11 {
return String(numbersOnly.prefix(11)).formatPhoneNumber()
} else if length < 10 {
return numbersOnly
}
var sourceIndex = 0
// Leading Number
var leadingNumber = ""
if length == 11, let leadChar = numbersOnly.first {
leadingNumber = String(leadChar) + " "
sourceIndex += 1
}
// Area code
var areaCode = ""
let areaCodeLength = 3
guard let areaCodeSubstring = numbersOnly.substring(start: sourceIndex, offsetBy: areaCodeLength) else {
return numbersOnly
}
areaCode = String(format: "(%#) ", areaCodeSubstring)
sourceIndex += areaCodeLength
// Prefix, 3 characters
let prefixLength = 3
guard let prefix = numbersOnly.substring(start: sourceIndex, offsetBy: prefixLength) else {
return numbersOnly
}
sourceIndex += prefixLength
// Suffix, 4 characters
let suffixLength = 4
guard let suffix = numbersOnly.substring(start: sourceIndex, offsetBy: suffixLength) else {
return numbersOnly
}
return leadingNumber + areaCode + prefix + "-" + suffix
}
}
extension String {
func substring(start: Int, offsetBy: Int) -> String? {
guard let substringStartIndex = self.index(startIndex, offsetBy: start, limitedBy: endIndex) else {
return nil
}
guard let substringEndIndex = self.index(startIndex, offsetBy: start + offsetBy, limitedBy: endIndex) else {
return nil
}
return String(self[substringStartIndex ..< substringEndIndex])
}
}
Swift 5
String(
format: "(%#) %#-%#",
rawNumber.subString(from: 0, to: 2),
rawNumber.subString(from: 3, to: 5),
rawNumber.subString(from: 6, to: 9)
)

Swift- Text field formatting update while editing

I have a function for formatting my text field as a phone number, but this function is only working after I save my managed object context. For example, I have a UITableView with static cells for text fields as a contact form. When I'm creating a new contact (before I've saved the contact) the text field for phone number doesn't get formatted by my function, but after I save that contact and reopen it, then go and enter a phone number it gets formatted properly. I'm trying to figure out why this is, and what I am do about it so that the number gets formatted in either case. Here's the function that I'm using to format the phone number.
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
if textField == phoneTxt {
var newString = (textField.text as NSString).stringByReplacingCharactersInRange(range, withString: string)
var components = newString.componentsSeparatedByCharactersInSet(NSCharacterSet.decimalDigitCharacterSet().invertedSet)
var decimalString = "".join(components) as NSString
var length = decimalString.length
var hasLeadingOne = length > 0 && decimalString.characterAtIndex(0) == (1 as unichar)
if length == 0 || (length > 10 && !hasLeadingOne) || length > 11 {
var newLength = (textField.text as NSString).length + (string as NSString).length - range.length as Int
return (newLength > 10) ? false : true
}
var index = 0 as Int
var formattedString = NSMutableString()
if hasLeadingOne {
formattedString.appendString("1 ")
index += 1
}
if (length - index) > 3 {
var areaCode = decimalString.substringWithRange(NSMakeRange(index, 3))
formattedString.appendFormat("(%#) ", areaCode)
index += 3
}
if length - index > 3 {
var prefix = decimalString.substringWithRange(NSMakeRange(index, 3))
formattedString.appendFormat("%#-", prefix)
index += 3
}
var remainder = decimalString.substringFromIndex(index)
formattedString.appendString(remainder)
textField.text = formattedString
return false
} else {
return true
}
}
`
It seems when the form is used to create a new object, you somehow have not set the delegate of the text field. Check if the posted function is being called at all in this case.

Resources