xcode swift - formatting text as phone number [duplicate] - iphone

This question already has answers here:
UITextField for Phone Number
(25 answers)
Closed 8 years ago.
I'm creating an iPhone app using Swift. I'm trying to setup a textfield in which the user can enter a phone number that automatically becomes formatted EXACTLY like it does in the built-in Contacts app as it is typed. I'm hoping xcode has built-in methods for doing this.
As an alternative, I created my own code that would add and delete brackets, dashes, etc as the user types and backspaces, but this quickly became problematic if the user was to move the curser away from the end of the entered text. In the Contacts app, if the cursor is moved just after a bracket and the user hits backspace, it deletes not just the bracket but rather the number preceding it. I'm not sure if this is done with some built-in formatting method or if perhaps there is code that replicates the text shown with brackets, dashes, etc removed and reads the position of the cursor, then calculates what the new string should be, and adds new brackets, dashes, etc.
Specifically, I'd like to know:
1) Is there a built-in method to format text to look like a phone number exactly as is done in the Contacts app?
2) If there is no built-in method, can someone tell me how I can have Swift read in the cursor position?
Thanks!

There is no built-in way to do this. Here's one implementation that uses the UITextField's shouldChangeCharactersInRange method:
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool
{
if textField == phoneTextField
{
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
}
}
As previously answered in this thread: UITextField for Phone Number

Related

Count number of characters between two specific characters

Trying to make a func that will count characters in between two specified char like:
count char between "#" and "." or "#" and ".com"
If this is only solution could this code be written in a simple way with .count or something less confusing
func validateEmail(_ str: String) -> Bool {
let range = 0..<str.count
var numAt = Int()
numDot = Int()
if str.contains("#") && str.contains(".") && str.characters.first != "#" {
for num in range {
if str[str.index(str.startIndex, offsetBy: num)] == "#" {
numAt = num
print("The position of # is \(numAt)")
} else if
str[str.index(str.startIndex, offsetBy: num)] == "." {
numDot = num
print("The position of . is \(numDot)")
}
}
if (numDot - numAt) > 1 {
return true
}
}
return false
}
With help from #Βασίλης Δ. i made a direct if statement for func validateEmail that check if number of char in between are less than 1
if (str.split(separator: "#").last?.split(separator: ".").first!.count)! < 1{
return false
}
It could be usefull
There are many edge cases to what you're trying to do, and email validation is notoriously complicated. I recommend doing as little of it as possible. Many, many things are legal email addresses. So you will need to think carefully about what you want to test. That said, this addresses what you've asked for, which is the distance between the first # and the first . that follows it.
func lengthOfFirstComponentAfterAt(in string: String) -> Int? {
guard
// Find the first # in the string
let firstAt = string.firstIndex(of: "#"),
// Find the first "." after that
let firstDotAfterAt = string[firstAt...].firstIndex(of: ".")
else {
return nil
}
// Return the distance between them (not counting the dot itself)
return string.distance(from: firstAt, to: firstDotAfterAt) - 1
}
lengthOfFirstComponentAfterAt(in: "rob#example.org") // Optional(7)
There's a very important lesson about Collections in this code. Notice the expression:
string[firstAt...].firstIndex(of: ".")
When you subscript a Collection, each element of the resulting slice has the same index as in the original collection. The returned value from firstIndex can be used directly to subscript string without offsetting. This is very different than how indexes work in many other languages, and allows powerful algorithms, and also creates at lot of bugs when developers forget this.

How can I restrict user input when the value inputed is greater than a certain value Swift 5.1

My current code is:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if textField == self.transferBalanceTextField {
if transferBalanceTextField.text! > currentBalance.text! {
return false
}
}
return true
}
But there are issues with this because if I type in a 6 first and the currentBalance is 582, it doesn't let me type in another digit because it believes the 6 is greater then 582, HELP!
Convert string to Ints in your case, and check it:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if textField == self.transferBalanceTextField {
if let str1 = transferBalanceTextField.text,
let int1 = Int(str1),
let str2 = currentBalance.text,
let int2 = Int(str2),
int1 > int2 {
return false
}
}
return true
}
Avoid force unwrap as much as possible - it'll help you a lot when code will be big.
UPDATE after comments
Simple code above only shows converting Strings to Ints and testing it. I thought it's clear, you see it and understand.
So - if you want full test according to your case - you have also take into account what exactly this function check (should in its name) and how to deal with it.
You have two states of your field - current and next one after possible update (should you change the text in field on user input or not?). Above simple code only test current state before new user input. You have to do checking with future possible text value when new user input already accepted to the field.
For example how it work now using code above (lets user already input 100 in first field and have 500 in another field):
user press "0" - want to enter 1000
you got call of this method
you check old value - 100 - against another value and decide should it be updated or not
method return true - (should change)
and it change it - field become 1000
after it you can't update field - method now will always return false (1000 is over 500)
What you need to do:
create string what should be in field after user input (in my example it must be 1000) - you have current value + new input + range on this new input
and check against it and another value.
Why it's matter - in this method you can check all cases of user input - simple input one char by one, paste from clipboard, replacing some characters, input in middle of string, full clear and so on - and all this must be done without and before real field visual updating.
Here is full code, you should use for full your case:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if textField == self.loginField {
if let str1 = loginField.text,
let str2 = passwordField.text,
let rangeExpr = Range(range, in: str1) {
let strFuture = str1.replacingCharacters(in: rangeExpr, with: string)
if let int1 = Int(strFuture),
let int2 = Int(str2),
int1 > int2 {
return false
}
}
}
return true
}
main change is in line
let strFuture = str1.replacingCharacters(in: rangeExpr, with: string)
Please, read methods you use, how it's work, and what your should do for it carefully - and you will get your progress much faster.

Limit the amount of characters inserted into a UITextField [duplicate]

This question already has answers here:
Set the maximum character length of a UITextField in Swift
(22 answers)
How to limit the textfield entry to 2 decimal places in swift 4?
(8 answers)
Closed 4 years ago.
How can I change a UITextField, that the User can just add one . and only two digits after the . -> Decimal Number with two digits after the poi^nt.
Use UITextFieldDelegate
// MARK:- TEXTFIELD DELEGATE
func textField(_ textField: UITextField,shouldChangeCharactersIn range: NSRange,replacementString string: String) -> Bool
{
let countdots = (txf_Amount.text?.components(separatedBy: ".").count)! - 1
if countdots > 0 && string == "."
{
return false
}
let MAX_BEFORE_DECIMAL_DIGITS = 7
let MAX_AFTER_DECIMAL_DIGITS = 3
let computationString = (textField.text! as NSString).replacingCharacters(in: range, with: string)
// Take number of digits present after the decimal point.
let arrayOfSubStrings = computationString.components(separatedBy: ".")
if arrayOfSubStrings.count == 1 && computationString.characters.count > MAX_BEFORE_DECIMAL_DIGITS {
return false
} else if arrayOfSubStrings.count == 2 {
let stringPostDecimal = arrayOfSubStrings[1]
return stringPostDecimal.characters.count <= MAX_AFTER_DECIMAL_DIGITS
}
return true
}

Bulleted list with NSTextList in NSTextView example?

I would want to parse user input in a NSTextView, so that a line beginning with "- " would automatically start a bulleted list. What do I have to do to the NSTextView.textStorage to enable a bulleted list so that the next bullet automatically appears when you press Enter after each bullet line?
I've found a few examples but they all insert the bullets by hand, so I was wondering what's the point of specifying let textList = NSTextList(markerFormat: "{box}", options: 0) if you then have to manually insert the box-symbol yourself?
Currently I'm trying to implement this in a custom NSTextView with an overriden shouldChangeTextInRange(affectedCharRange: NSRange, replacementString: String?) So a simple example that would solve the simplest input case of "- " starting an automatic bulleted list, would be highly regarded.
Update:
Here is the code I'm currently using:
override func shouldChangeTextInRange(affectedCharRange: NSRange, replacementString: String?) -> Bool {
super.shouldChangeTextInRange(affectedCharRange, replacementString: replacementString)
if string == "-" && replacementString == " " {
let textList = NSTextList(markerFormat: "{disc}", options: 0)
let textListParagraphStyle = NSMutableParagraphStyle()
textListParagraphStyle.textLists = [textList]
let attributes = [NSParagraphStyleAttributeName: textListParagraphStyle]
string = "\t\(textList.markerForItemNumber(0))\t"
textStorage?.addAttributes(attributes, range: NSMakeRange(0, textStorage!.string.length))
return false
}
else if affectedCharRange.location > 0 && replacementString == "\n\n" {
textStorage?.insertAttributedString(NSAttributedString(string: "\t"), atIndex: affectedCharRange.location)
return true
}
return true
}
I'm just trying to solve the simplest case of the user typing "- " as the first characters in the NSTextView. This is what happens with the code above:
The bigger font in the beginning comes from the typingAttributes I have set. This gets automatically overriden later as you can see.
Returning false from the else if clause and inserting the \n directly there will prevent the NSTextView from automatically adding new bullets. After the first time I return true from there, new bullets are added automatically without calling this method???
Try to add bullets using Unicode character by replace "- " with \u{2022}
textView.text = " \u{2022} This is a list item!

How do I force a text field to be upper case only in Swift?

I would like the text entry on a text field to be upper case only.
Is there a way to limit the text field to output only upper case letters or even limit the software keyboard to only display upper case letters to users?
let textFieldOutput = "Wait a moment, please."
let newString = textFieldOutput.uppercased()
//The string is now "WAIT A MOMENT, PLEASE."
Step-1. In Main.Storyboard select the textfield and click on attribute inspector
Step-2. Then in text input traits -> select 'All Characters' in Capitalization.
Changing the keyboard input type to All Characters does not prevent the user to switch back to lowercase letters (at least on iOS 13).
I use the following code (Swift 5.1) to capitalise only new characters added to the textfield instead of setting the full string over and over again as suggested in some other answers.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let firstLowercaseCharRange = string.rangeOfCharacter(from: NSCharacterSet.lowercaseLetters)
if let _ = firstLowercaseCharRange {
if let text = textField.text, text.isEmpty {
textField.text = string.uppercased()
}
else {
let beginning = textField.beginningOfDocument
if let start = textField.position(from: beginning, offset: range.location),
let end = textField.position(from: start, offset: range.length),
let replaceRange = textField.textRange(from: start, to: end) {
textField.replace(replaceRange, withText: string.uppercased())
}
}
return false
}
return true
}
You can change the text to upper case with
string.uppercaseStringWithLocale(NSLocale.currentLocale())
You can use the method textField:shouldChangeCharactersInRange:replacementString: to change the text.
There are at least two options:
Use the uppercaseString property of Swift's String class to generate an all-uppercase version of the text. This is a reasonable option if you want an upper case version of whatever is typed into the text field.
Implement the method textField(_:shouldChangeCharactersInRange:replacementString:) in the text field's delegate. Your implementation should do the replacement itself using an upper case version of the replacement string, and then return false. This is the approach you want if it's important that the text appear upper case in the text field.
You can change the string afterwards to uppercase using the below:
var newString = myString.uppercaseString
in swift 3:
var newString = myString.uppercased()
Swift 4.2:
There are two ways.
Firstly. Entered value from keyboard, you can do (upperCase) it in the programmatically.
yourTextField.text = yourString.uppercased()
Or story board force upper keyboard.
StorBoard->TextInputTraits->Capitalization->Select ALL Characters