I am trying to format the User input phone number. I have implemented this:
import UIKit
class ViewController: UIViewController {
var phoneNumberTextField : UITextField?
var docController: UIDocumentInteractionController?
override func viewDidLoad() {
super.viewDidLoad()
phoneNumberTextField = UITextField(frame: CGRectMake(10, 150, 100, 50))
phoneNumberTextField?.backgroundColor = UIColor.brownColor()
self.view.addSubview(phoneNumberTextField!)
}
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool
{
if (textField == phoneNumberTextField)
{
let newString = (textField.text! as NSString).stringByReplacingCharactersInRange(range, withString: string)
let components = newString.componentsSeparatedByCharactersInSet(NSCharacterSet.decimalDigitCharacterSet().invertedSet)
let decimalString = components.joinWithSeparator("") as NSString
let length = decimalString.length
let hasLeadingOne = length > 0 && decimalString.characterAtIndex(0) == (1 as unichar)
if length == 0 || (length > 10 && !hasLeadingOne) || length > 11
{
let newLength = (textField.text! as NSString).length + (string as NSString).length - range.length as Int
return (newLength > 10) ? false : true
}
var index = 0 as Int
let formattedString = NSMutableString()
if hasLeadingOne
{
formattedString.appendString("1 ")
index += 1
}
if (length - index) > 3
{
let areaCode = decimalString.substringWithRange(NSMakeRange(index, 3))
formattedString.appendFormat("(%#)", areaCode)
index += 3
}
if length - index > 3
{
let prefix = decimalString.substringWithRange(NSMakeRange(index, 3))
formattedString.appendFormat("%#-", prefix)
index += 3
}
let remainder = decimalString.substringFromIndex(index)
formattedString.appendString(remainder)
textField.text = formattedString as String
return false
}
else
{
return true
}
}
}
From Here. When I run the app and I start adding numbers nothing happens. Brackets and hypens do not appear. I want to convert (197)-444-4444 to 1974444444.
What is wrong here?
You dont seems to have set delegate for your textfield:
phoneNumberTextField.delegate = self
And your viewcontroller doesnt seems to conform the UITextFieldDelegate like:
class ViewController: UIViewController, UITextFieldDelegate {
Also, it would never be nil so just change it to var phoneNumberTextField : UITextField!
Related
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
}
I have text label which has phone number in it. I mask the phone number when user is typing so that in shouldChangeCharactersIn function;
I get user input (string)
Add that input to text which is already written in UITextField
Mask text and set it to UITextField
Return false
My question is that after I set text of UITextfield (delete or add new character UITextField, cursor moves to the end but I want it to stay in the same position. (By meaning same position, I mean same when I don't implement shouldChangeCharactersIn function) How can I do that? Thank you.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard CharacterSet(charactersIn: "0123456789").isSuperset(of: CharacterSet(charactersIn: string)) else {
return false
}
if let text = textField.text {
let newLength = text.count + string.count - range.length
let newText = text + string
let textFieldText: NSString = (textField.text ?? "") as NSString
let txtAfterUpdate = textFieldText.replacingCharacters(in: range, with: string)
if(newLength <= 15){
//textField.text = txtAfterUpdate
textField.text = txtAfterUpdate.digits.applyPatternOnNumbers()
return false
}
return newLength <= 15
}
return true
}
Mask Function:
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
}
What I want in GIF
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard CharacterSet(charactersIn: "0123456789").isSuperset(of: CharacterSet(charactersIn: string)) else {
return false
}
if let text = textField.text {
let newLength = text.count + string.count - range.length
let newText = text + string
let textFieldText: NSString = (textField.text ?? "") as NSString
let txtAfterUpdate = textFieldText.replacingCharacters(in: range, with: string)
if(newLength <= 15){
var currentPosition = 0
if let selectedRange = textField.selectedTextRange {
currentPosition = textField.offset(from: textField.beginningOfDocument, to: selectedRange.start)
}
if(string == ""){
if(currentPosition == 2){
if(textField.text!.count > 2){
currentPosition = 1
}else{
currentPosition = 0
}
}else if(currentPosition == 7){
currentPosition = 4
}else if(currentPosition == 11){
currentPosition = 9
}else if(currentPosition == 14){
currentPosition = 12
}else{
if(currentPosition != 1){
currentPosition = currentPosition - 1
}
}
}else{
if(currentPosition == 0){
currentPosition = 2
}else if(currentPosition == 4){
currentPosition = 7
}else if(currentPosition == 9){
currentPosition = 11
}else if(currentPosition == 12){
currentPosition = 14
}else{
currentPosition = currentPosition + 1
}
}
textField.text = txtAfterUpdate.applyPatternOnNumbers()
print("textField Length -> : \(textField.text?.count ?? 0)")
if let newPosition = textField.position(from: textField.beginningOfDocument, offset: currentPosition) {
textField.selectedTextRange = textField.textRange(from: newPosition, to: newPosition)
}
return false
}
return newLength <= 15
}
return true
}
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
}
Newbie in Swift, please help me out, when I type . or after .0 or .00 it should reflect with 0.00, but this is not visible to myTxtField.text.
Following is my code -
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
// return NO to not change text
//Decimal
switch string {
case "0","1","2","3","4","5","6","7","8","9",".":
currentString += string
print(currentString)
formatCurrency(currentString)
default:
if string.characters.count == 0 && currentString.characters.count != 0 {
currentString = String(currentString.characters.dropLast())
formatCurrency(currentString)
}
}
return false
}
func formatCurrency(string: String) {
let formatter = NSNumberFormatter()
formatter.numberStyle = NSNumberFormatterStyle.DecimalStyle
formatter.maximumFractionDigits = 2
formatter.roundingMode = .RoundDown
formatter.locale = NSLocale(localeIdentifier: "en_US")
let numberFromField = (NSString(string: currentString).doubleValue)
let tAmount:String!
if string == "" {
myTxtField.text = ""
tAmount = "$" + formatter.stringFromNumber(numberFromField)!
}else {
myTxtField.text = "$" + formatter.stringFromNumber(numberFromField)!
tAmount = "$" + formatter.stringFromNumber(numberFromField)!
}
print("value=\(HManager.convertCurrencyToDouble(tAmount))")
}
The following is an example of using shouldChangeCharactersIn delegate method and editingDidEnd to achieve your goal.
The shouldChangeCharactersIn to limit the text field to accepting only number and decimal point ..
The editingDidEnd is to format the text field with 2 decimal digits.
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
textField.delegate = self
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let allowedCharacters = CharacterSet.decimalDigits
if string.rangeOfCharacter(from: allowedCharacters.inverted) != nil {
// If the added character is not a number
if string == "." {
// If the added character is the decimal point
let countDots = textField.text!.components(separatedBy: ".").count - 1
if countDots == 0 {
return true
}
}
return false
}
return true
}
#IBAction func editingDidEnd(_ sender: UITextField) {
// Format the input with 2 decimal digits.
if let input = sender.text, let inputDouble = Double(input) {
sender.text = String(format: "%.2f", inputDouble)
} else {
sender.text = "0.00"
}
}
}
UPDATE
In order to add a currency symbol ($) and remove the fractional part if needed, you need to have a function to check whether the input is double or Int.
#IBAction func editingDidEnd(_ sender: UITextField) {
// Format the input with 2 decimal digits.
if let input = sender.text, let number = Double(input) {
if canConvertToInt(number) {
sender.text = "$\(Int(number))"
} else {
sender.text = "$\(number)"
}
} else {
sender.text = "$0"
}
}
// Check if the number is not decimal
func canConvertToInt(_ double: Double) -> Bool {
return rint(double) == double
}
I have searched all around for this but still cannot seem to get it to work. I have a simple iOS program with a UITextField that converts Farenheit to Celsius and Kelvin. I am trying to limit only 1 decimal entry in this UITextField. For example 98.9 is ok but 98..9 is not.
This is the function in which I want to limit only 1 decimal. What I would like is if 1 decimal is entered then it will not allow another 1 to be entered but numbers can still be entered after the first decimal of course.
func farenheitButton(sender: AnyObject)
{
var fVal = self.farenheitTextLabel.text
var cVal = self.celsiusTextLabel.text
var kVal = self.kelvinTextLabel.text
if let fVal = self.farenheitTextLabel.text?.toDouble()
{
var fValx = self.farenheitTextLabel.text
var fDoubleValue : Double = NSString (string: fValx).doubleValue
var fToC = (fDoubleValue - 32) * (5/9) //convert Farenheit to Celsius formula
var fToK = ((fDoubleValue - 32) / (1.8000)) + 273.15 //convert Farenheit to Kelvin formula
//create string from value with a format
var afToC : Double = fToC
var bfToC : String = String(format:"%.4f",afToC)
var afToK : Double = fToK
var bfToK : String = String(format:"%.4f",afToK)
celsiusTextLabel.text = bfToC
kelvinTextLabel.text = bfToK
}
}
You can set the UITextField.delegate = self and use the textField(textField: UITextField,shouldChangeCharactersInRange range: NSRange,replacementString string: String) method to do what you want.
The following code will not allow you to enter more than 1 decimal point in the textfield.
func textField(textField: UITextField,shouldChangeCharactersInRange range: NSRange,replacementString string: String) -> Bool
{
let countdots = textField.text.componentsSeparatedByString(".").count - 1
if countdots > 0 && string == "."
{
return false
}
return true
}
You should add UITextFieldDelegate to class ViewController:
class ViewController: UIViewController, UITextFieldDelegate {
...
}
then
If name of your #IBOutlet is textField, (it could looks like:
#IBOutlet weak var textField: UITextField!), then add
override func viewDidLoad() {
super.viewDidLoad()
self.textField.delegate = self
// Do any additional setup after loading the view, typically from a nib.
}
after that you could use:
func textField(textField: UITextField,shouldChangeCharactersInRange range: NSRange,replacementString string: String) -> Bool
{
let countdots = textField.text.componentsSeparatedByString(".").count - 1
if countdots > 0 || textField.text == "."
{
return false
}
return true
}
You can use this subclass of UITextField. It uses the same principle as the answers above, but with a little bit clearer code.
class WMNumberTextField: UITextField, UITextFieldDelegate {
override func awakeFromNib() {
super.awakeFromNib()
delegate = self
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let text = text else { return false }
let dots = text.characters.filter { $0 == "." }
return dots.count == 0 || string != "."
}
}
Swift 3
func textField(_ textField: UITextField,shouldChangeCharactersIn range: NSRange,replacementString string: String) -> Bool
{
let countdots = (textField.text?.components(separatedBy: (".")).count)! - 1
if (countdots > 0 && string == "."){
return false
}
return true
}
I used below code to limit 1 decimal entry in swift 3.0.
let inverseSet = CharacterSet(charactersIn:".0123456789").inverted
let components = string.components(separatedBy: inverseSet)
let filtered = components.joined(separator: "")
if (textField.text?.contains("."))!, string.contains(".") {
return false
}
return string == filtered
Swift 4: This works !
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let counter = textField.text?.components(separatedBy: ".") else { return false }
if (counter.count - 1 > 0 && string == ".") { return false }
return true
}
For swift 5 to restrict text field to enter only one (.) decimal.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if string == "." {
let countdots = textField.text!.components(separatedBy: ".").count - 1
if countdots == 0 {
return true
} else {
if countdots > 0 && string == "." {
return false
} else {
return true
}
}
}
return true
}