How to add followed text to uitextfield - ios

I have one text field with place holder called letschat. Now whenever I start typing in my textfield, I want to show my textfield as some #letschat. When my textfield is empty that time my placeholder have to show. That I did. But I want to set whenever I start typing in my textfield. Whatever I am typing with that I want this text also to visible like:
Some #Lletschat
How can I do this?

I created a UITextField subclass that uses the placeholder (if set) as a suffix. As far as I can see everything works as expected. Maybe there are some tweaks needed to suit your needs.
Feel free to ask if anything is unclear:
class SuffixTextField: UITextField {
override init(frame: CGRect) {
super.init(frame: frame)
sharedInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
sharedInit()
}
private func sharedInit() {
addTarget(self, action: #selector(textChanged), for: .editingChanged)
}
override var text: String? {
didSet {
selectedTextRange = maxTextRange
}
}
override var attributedText: NSAttributedString? {
didSet {
selectedTextRange = maxTextRange
}
}
#objc private func textChanged() {
if let currentText = text, let placeholder = placeholder {
if currentText == placeholder {
self.text = nil
} else if !currentText.hasSuffix(placeholder) {
self.text = currentText + placeholder
}
}
}
private var maxCursorPosition: UITextPosition? {
guard let placeholder = placeholder, !placeholder.isEmpty else { return nil }
guard let text = text, !text.isEmpty else { return nil }
return position(from: beginningOfDocument, offset: (text as NSString).range(of: placeholder, options: .backwards).location)
}
private var maxTextRange: UITextRange? {
guard let maxCursorPosition = maxCursorPosition else { return nil }
return textRange(from: maxCursorPosition, to: maxCursorPosition)
}
override var selectedTextRange: UITextRange? {
get { return super.selectedTextRange }
set {
guard let newRange = newValue,
let maxCursorPosition = maxCursorPosition else {
super.selectedTextRange = newValue
return
}
if compare(maxCursorPosition, to: newRange.start) == .orderedAscending {
super.selectedTextRange = textRange(from: maxCursorPosition, to: maxCursorPosition)
} else if compare(maxCursorPosition, to: newRange.end) == .orderedAscending {
super.selectedTextRange = textRange(from: newRange.start, to: maxCursorPosition)
} else {
super.selectedTextRange = newValue
}
}
}
}
here you can see a preview:
https://www.dropbox.com/s/etkbme37wuxbw1q/preview.mov?dl=0

You can take the action of UITextField textFieldDidChange and get the call of every time when the textField text changed.
Just like that:
func textChangedAction(sender:UITextFiled) {
if sender.text.rangeOfString("#Lletschat") != nil{
sender.text = sender.text.replacingOccurrences(of: "#Lletschat", with: "")
}
sender.text = "\(sender.text!) #Lletschat"
}
If you want to changed the color of your specific text you can check here.

Implement textfield delegate like this-
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
textField.text = textField.text?.replacingOccurrences(of: " #\(textField.placeholder!)", with: "", options: .literal, range: nil)
textField.text?.append(string)
textField.text?.append(" #\(textField.placeholder!)")
return false
}

Simple solution:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let newRange = textField.text?.range(from: range), let result = textField.text?.replacingCharacters(in: newRange, with: string) else { return true }
if result.endsWithString("#letschat") {
return true
} else {
textField.text = result + "#letschat"
let position = textField.position(from: textField.beginningOfDocument, offset: result.characters.count)!
textField.selectedTextRange = textField.textRange(from: position, to: position)
return false
}
}
With helper extension:
extension String {
func range(from oldOne: NSRange) -> Range<String.Index>? {
guard
let from16 = utf16.index(utf16.startIndex, offsetBy: oldOne.location, limitedBy: utf16.endIndex),
let to16 = utf16.index(utf16.startIndex, offsetBy: oldOne.location + oldOne.length, limitedBy: utf16.endIndex),
let from = from16.samePosition(in: self),
let to = to16.samePosition(in: self)
else { return nil }
return from ..< to
}
func endsWithString(_ string: String) -> Bool {
guard characters.count >= string.characters.count else { return false }
let index = self.index(startIndex, offsetBy: characters.count - string.characters.count)
let substring = self.substring(from: index)
return substring == string
}
}
Difficult but clear solution is to create your own UIControl-subclass with UITextField and UILabel children views:
+-----------+ +-----------+
| textfield | -(3px)- | #letschat |
+-----------+ +-----------+
Use autolayout to keep the distance of 3 pixels between it.
Don't forget to configure your class to send all the incoming actions to the textfield. You can use different font colours for these controls so user won't be confused about efforts to change label's value.

Conform your class to UITextfieldDelegate and then assign textfield.delegate = self
Now, add this Delegate Method, if you want your #letschat to be appended after user endtyping.
func textFieldDidEndEditing(textField: UITextField) {
textField.text = "\(textField.text)#letschat"
}
or if you want it at the typing time.
textField.addTarget(self, action: #selector(YourViewController.textFieldDidChange(_:)), forControlEvents: UIControlEvents.EditingChanged)
func textFieldDidChange(textField: UITextField) {
if textField.containsString("#letschat") {
textField.text = textField.text.stringByReplacingOccurrencesOfString("#letschat", withString: "")
}
textField.text = "\(textField.text)#letschat"
}
Hope this helps.

Related

Disallow Alphabetic Characters in Swift 3

I am new to Swift, I am currently reading the Big Nerd Ranch book to understand more. I have an application that allows the user to type the temperature in Fahrenheit then will be converted to Celsius and displayed on the interface. The application only displays one fractional digit on the Celsius label. Now I want to disallow the user from entering the alphabetic characters. I have been trying to figure out how to do so but I wasn't able to get the idea. Any help would be much appreciated.
Here is my code so far:
import UIKit
class ConversionViewController : UIViewController , UITextFieldDelegate {
#IBOutlet var textField: UITextField!
#IBOutlet var celsiusLabel: UILabel!
var fahrenheitValue: Measurement<UnitTemperature>?
{
didSet {
updateCelsiusLabel()
}
}
var celsiusValue: Measurement<UnitTemperature>? {
if let fahrenheitValue = fahrenheitValue {
return fahrenheitValue.converted(to: .celsius)
} else {
return nil
}
}
func updateCelsiusLabel() {
if let celsiusValue = celsiusValue {
celsiusLabel.text = numberFormatter.string(from: NSNumber(value: celsiusValue.value))
} else {
celsiusLabel.text = "???"
}
}
let numberFormatter: NumberFormatter = {
let nf = NumberFormatter()
nf.numberStyle = .decimal
nf.minimumFractionDigits = 0
nf.maximumFractionDigits = 1
return nf
}()
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange,replacementString string: String) -> Bool {
let existingTextHasDecimalSeparator = textField.text?.range(of: ".")
let replacementTextHasDecimalSeparator = string.range(of: ".")
if existingTextHasDecimalSeparator != nil,
replacementTextHasDecimalSeparator != nil {
return false
} else {
return true
}
}
#IBAction func fahrenheitFieldEditingChanged(_ textField: UITextField) {
if let text = textField.text, let value = Double(text) {
fahrenheitValue = Measurement(value: value, unit: .fahrenheit)
} else {
fahrenheitValue = nil
}
}
#IBAction func dismissKeyboard(_ sender: UITapGestureRecognizer) {
textField.resignFirstResponder()
}
override func viewDidLoad() {
super.viewDidLoad()
updateCelsiusLabel()
}
}
You already have the function:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange,replacementString string: String) -> Bool
Simply change it to return false using this:
for tempChar in string.unicodeScalars {
if tempChar.isAlpha() {
return false
}
}
Here's my solution which builds on the book's if statement by going another layer deep.
if existingTextHasDecimalSeparator != nil && replacementTextHasDecimalSeparator != nil {
return false
}
else {
if CharacterSet.decimalDigits.isSuperset(of: CharacterSet(charactersIn: string)) || (string.range(of: ".") != nil) {
// If incoming character is a decimalDigit or a period, return true
return true
}
else {
// If a letter is detected, returns false
return false
}
}
You can set the keyboardType of a UITextField to various UIKeyboardTypes that will change the input options available to the user. The different keyboard types are listed in the Apple docs here: https://developer.apple.com/reference/uikit/uikeyboardtype

Swift. Format input in textField

I created textField and want to format text in it like 43/35. Number / number - for credit card month and year.
Can I use number fomatter for it or how can I do it more easily?
The issue here that I need to replace 3rd character if I add new character and remove it if I remove 2nd one.
I do not want use any 3rd party library, I need native implementation
This is my current solution. Basically you need to:
1) Implement the delegate of your textfield somewhere (in my code below I implemented on the ViewController)
2) Implement textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool.
To apply the mask, I created some extensions for String and Characters, as you can see at the end of the following code:
class ViewController: UIViewController {
#IBOutlet weak var textfield: UITextField!
let mask = "##/##"
override func viewDidLoad() {
super.viewDidLoad()
textfield.delegate = self
}
}
extension ViewController: UITextFieldDelegate {
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
guard let normalText = textField.text else { return false }
let beginning = textField.beginningOfDocument
// save cursor location
let cursorLocation = textField.positionFromPosition(beginning, offset: range.location + string.characters.count)
let newString = (normalText as NSString).stringByReplacingCharactersInRange(range, withString: string)
let newStringClean = newString.stringWithOnlyNumbers().withMask(mask)
guard newString != newStringClean else { return true }
textField.text = newStringClean
guard string != "" else { return false }
// fix cursor location after changing textfield.text
if let cL = cursorLocation {
let textRange = textField.textRangeFromPosition(cL, toPosition: cL)
textField.selectedTextRange = textRange
}
return false
}
}
extension String {
func stringWithOnlyNumbers() -> String {
return self.characters.reduce("") { (acc, c) -> String in
guard c.isDigit() else { return acc }
return "\(acc)\(c)"
}
}
func withMask(mask: String) -> String {
var resultString = String()
let chars = self.characters
let maskChars = mask.characters
var stringIndex = chars.startIndex
var maskIndex = mask.startIndex
while stringIndex < chars.endIndex && maskIndex < maskChars.endIndex {
if (maskChars[maskIndex] == "#") {
resultString.append(chars[stringIndex])
stringIndex = stringIndex.successor()
} else {
resultString.append(maskChars[maskIndex])
}
maskIndex = maskIndex.successor()
}
return resultString
}
}
extension Character {
func isDigit() -> Bool {
let s = String(self).unicodeScalars
let uni = s[s.startIndex]
let digits = NSCharacterSet.decimalDigitCharacterSet()
let isADigit = digits.longCharacterIsMember(uni.value)
return isADigit
}
}
Swift 4:
extension CodeEnterViewController: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let normalText = textField.text else { return false }
let beginning = textField.beginningOfDocument
// save cursor location
let cursorLocation = textField.position(from: beginning, offset: range.location + string.count)
let newString = (normalText as NSString).replacingCharacters(in: range, with: string)
let newStringClean = newString.stringWithOnlyNumbers().withMask(mask: mask)
guard newString != newStringClean else { return true }
textField.text = newStringClean
guard string != "" else { return false }
// fix cursor location after changing textfield.text
if let cL = cursorLocation {
let textRange = textField.textRange(from: cL, to: cL)
textField.selectedTextRange = textRange
}
return false
}
}
String
extension String {
func stringWithOnlyNumbers() -> String {
return self.reduce("") { (acc, c) -> String in
guard c.isDigit() else { return acc }
return "\(acc)\(c)"
}
}
func withMask(mask: String) -> String {
var resultString = String()
let chars = self
let maskChars = mask
var stringIndex = chars.startIndex
var maskIndex = mask.startIndex
while stringIndex < chars.endIndex && maskIndex < maskChars.endIndex {
if (maskChars[maskIndex] == "#") {
resultString.append(chars[stringIndex])
stringIndex = chars.index(after: stringIndex)
} else {
resultString.append(maskChars[maskIndex])
}
maskIndex = chars.index(after: maskIndex)
}
return resultString
}
}
Character
extension Character {
func isDigit() -> Bool {
let s = String(self).unicodeScalars
let uni = s[s.startIndex]
let digits = NSCharacterSet.decimalDigits
let isADigit = digits.hasMember(inPlane: UInt8(uni.value))
return isADigit
} }

Multiple UITextView placeholders

I'm trying to put in placeholders into my UITextViews, and found a solution here using this chunk of code:
func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool {
let currentText:NSString = storyView.text
let updatedText = currentText.stringByReplacingCharactersInRange(range, withString:text)
if updatedText.isEmpty {
storyView.text = "Write out your story here!"
storyView.textColor = UIColor.lightGrayColor()
storyView.selectedTextRange = storyView.textRangeFromPosition(storyView.beginningOfDocument, toPosition: storyView.beginningOfDocument)
return false
}else if storyView.textColor == UIColor.lightGrayColor() && !text.isEmpty {
storyView.text = nil
storyView.textColor = UIColor.blackColor()
}
return true
}
func textViewDidChangeSelection(storyView: UITextView) {
if self.view.window != nil {
if storyView.textColor == UIColor.lightGrayColor() {
storyView.selectedTextRange = storyView.textRangeFromPosition(storyView.beginningOfDocument, toPosition: storyView.beginningOfDocument)
}
}
}
It works, and I'm happy with it, but I'd like to apply it to other UITextViews in the same ViewController. How would I do it without creating a duplicate instance of the textView method?
You can use textField.tag in your shouldChangeTextInRange method.
Give tag to your textField of same viewcontroller. And use that tag into shouldChangeTextInRange method.
func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool {
let currentText:NSString = storyView.text
let updatedText = currentText.stringByReplacingCharactersInRange(range, withString:text)
if updatedText.isEmpty {
if (textField.tag==0)//
{
storyView.text = "Write out your story here!" // stuff
}
else if(textField.tag==1)//
{
storyView.text = "Example 2 "// stuff
}
storyView.textColor = UIColor.lightGrayColor()
storyView.selectedTextRange = storyView.textRangeFromPosition(storyView.beginningOfDocument, toPosition: storyView.beginningOfDocument)
return false
}else if storyView.textColor == UIColor.lightGrayColor() && !text.isEmpty {
storyView.text = nil
storyView.textColor = UIColor.blackColor()
}
return true
}
func textViewDidChangeSelection(storyView: UITextView) {
if self.view.window != nil {
if storyView.textColor == UIColor.lightGrayColor() {
storyView.selectedTextRange = storyView.textRangeFromPosition(storyView.beginningOfDocument, toPosition: storyView.beginningOfDocument)
}
}
}
May be it will help you.

How can I change the dots to another character on password field in iOS Swift

I have a Text Field and I would like to replace the default dot character to something else when the password is hidden.
Is there any way to do this easily?
2 options here:
Use a normal textfield without the secure input option. When a user enters a character, save it to a string variable, and replace it in the textfield with the character you wish to present instead of the bullets.
Here's the code (will show the password as $$$$):
var password: String = ""
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool
{
password = password+string
textField.text = textField.text+"$"
println("\(password)")
return false
}
check out the answers here: UITextField secureTextEntry bullets with a custom font?
based on Ron.Kliffer's idea, I have implemented workable full code:
//
// PasswordTextField.swift
// CrifanLibSwift
//
// Created by licrifan on 16/7/8.
// Copyright © 2016年 licrifan. All rights reserved.
//
import UIKit
class PasswordTextField: CommonTextField, UITextFieldDelegate {
var realText:String {
didSet {
print("self.text=\(self.text), realText=\(realText)")
updateMaskStr()
}
}
var maskChar:Character
init(frame: CGRect = CGRectZero, maskChar:Character = "*") {
print("frame=\(frame), maskChar=\(maskChar)")
self.realText = ""
self.maskChar = maskChar
super.init(frame: frame)
self.secureTextEntry = false
self.addTarget(self, action: #selector(self.textFiledEditingChanged(_:)), forControlEvents: UIControlEvents.EditingChanged)
self.delegate = self
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func updateMaskStr(){
print("before update: self.text=\(self.text), self.realText=\(self.realText)")
//change real text to mask char
var maskStr = ""
for _ in self.realText.characters {
maskStr += String(self.maskChar)
}
self.text = maskStr
print("after update: self.text=\(self.text), self.realText=\(self.realText)")
}
func textFiledEditingChanged(textField: UITextField) {
print("textField=\(textField), textField.text=\(textField.text)")
updateMaskStr()
}
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
print("textField=\(textField), range=\(range), string=\(string)")
var allow = true
let curText = self.realText
let updatedStr:String = (curText as NSString).stringByReplacingCharactersInRange(range, withString: string)
if updatedStr.characters.count > LoginRegisterPasswordLengthMax {
print("password exceed max length \(LoginRegisterPasswordLengthMax)")
allow = false
}
if allow {
self.realText = updatedStr
}
print("curText=\(curText), updatedStr=\(updatedStr), self.realText=\(self.realText)")
return false
}
}
and in another view EditInfoView.swift, to use it:
class EditInfoView: UIView {
var passwordTextField:PasswordTextField
init(editMode:EditInfoMode) {
self.passwordTextField = PasswordTextField()
self.addSubview(self.passwordTextField)
if self.passwordTextField.notHidden {
//5. password text
var passwordPlaceholder = "密码(6-20位)"
if self.editMode == .ChangeLoginPassword {
passwordPlaceholder = "旧密码(6-20位)"
} else if self.editMode == .ForgotPassword {
passwordPlaceholder = "输入新密码(6-20位)"
}
self.passwordTextField.placeholder = passwordPlaceholder
self.passwordTextField.returnKeyType = UIReturnKeyType.Go
// self.passwordTextField.secureTextEntry = true
constrain(passwordTextField, smsCodeTextField, phoneTextField) {passwordTextField, smsCodeTextField, phoneTextField in
passwordTextField.centerX == passwordTextField.superview!.centerX
passwordTextField.width == passwordTextField.superview!.width - 2 * LoginRegisterPaddingX
passwordTextField.height == CommonTextFieldHeight
if self.editMode == .ChangeLoginPassword {
passwordTextField.top == passwordTextField.superview!.top + EditInfoViewTopPadding
} else {
passwordTextField.top == smsCodeTextField.bottom + EditInfoViewCommonPaddingY
}
}
}
}
finally in a view controller EditInfoViewController.swift to real use it:
isValid = validatePassword(self, alertPrefix: alertPrefix, passwordStr: self.editInfoView.passwordTextField.realText, doAlertWhenInvalid: doAlertWhenInvalid)
the final effect is:
Swift 5 ready to use, updated version of crifan answer:
import UIKit
class PasswordTextField: UITextField, UITextFieldDelegate {
var realText: String {
didSet {
updateMaskStr()
}
}
let maskChar: Character
init(
frame: CGRect = .zero,
maskChar: Character = "*"
) {
realText = ""
self.maskChar = maskChar
super.init(frame: frame)
setupActions()
setupDelegates()
}
private func setupActions() {
addTarget(self, action: #selector(textFiledEditingChanged(textField:)), for: .editingChanged)
}
private func setupDelegates() {
delegate = self
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func updateMaskStr(){
//change real text to mask char
var maskStr = ""
for _ in realText {
maskStr += String(self.maskChar)
}
text = maskStr
}
#objc
private func textFiledEditingChanged(textField: UITextField) { updateMaskStr() }
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let curText = realText
let updatedStr:String = (curText as NSString).replacingCharacters(in: range, with: string)
realText = updatedStr
return false
}
}
It's work for me on Swift 5 ( also support backspace key )
var password: String = ""
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if textField == txtPassword {
if string == "" {
password.removeLast()
return true
}
password += string
txtPassword.text = (textField.text ?? "")+"*"
print("\(password)")
return false
} else {
return true
}
}

How to input currency format on a text field (from right to left) using Swift?

I have a number let’s say 0.00.
When the user taps 1. We should have 0.01
When the user taps 2. We should display 0.12
When the user taps 3. We should display 1.23
When the user taps 4. We should display 12.34
How can I do that with Swift?
For Swift 3. Input currency format on a text field (from right to left)
override func viewDidLoad() {
super.viewDidLoad()
textField.addTarget(self, action: #selector(myTextFieldDidChange), for: .editingChanged)
}
#objc func myTextFieldDidChange(_ textField: UITextField) {
if let amountString = textField.text?.currencyInputFormatting() {
textField.text = amountString
}
}
extension String {
// formatting text for currency textField
func currencyInputFormatting() -> String {
var number: NSNumber!
let formatter = NumberFormatter()
formatter.numberStyle = .currencyAccounting
formatter.currencySymbol = "$"
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2
var amountWithPrefix = self
// remove from String: "$", ".", ","
let regex = try! NSRegularExpression(pattern: "[^0-9]", options: .caseInsensitive)
amountWithPrefix = regex.stringByReplacingMatches(in: amountWithPrefix, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSMakeRange(0, self.count), withTemplate: "")
let double = (amountWithPrefix as NSString).doubleValue
number = NSNumber(value: (double / 100))
// if first number is 0 or all numbers were deleted
guard number != 0 as NSNumber else {
return ""
}
return formatter.string(from: number)!
}
}
You can create a currency text field subclassing UITextField. Add a target for UIControlEvents .editingChanged. Add a selector method to filter the digits from your textfield string. After filtering all non digits from your string you can format again your number using NumberFormatter as follow:
Xcode 11.5 • Swift 5.2 or later
import UIKit
class CurrencyField: UITextField {
var decimal: Decimal { string.decimal / pow(10, Formatter.currency.maximumFractionDigits) }
var maximum: Decimal = 999_999_999.99
private var lastValue: String?
var locale: Locale = .current {
didSet {
Formatter.currency.locale = locale
sendActions(for: .editingChanged)
}
}
override func willMove(toSuperview newSuperview: UIView?) {
// you can make it a fixed locale currency if needed
// self.locale = Locale(identifier: "pt_BR") // or "en_US", "fr_FR", etc
Formatter.currency.locale = locale
addTarget(self, action: #selector(editingChanged), for: .editingChanged)
keyboardType = .numberPad
textAlignment = .right
sendActions(for: .editingChanged)
}
override func deleteBackward() {
text = string.digits.dropLast().string
// manually send the editingChanged event
sendActions(for: .editingChanged)
}
#objc func editingChanged() {
guard decimal <= maximum else {
text = lastValue
return
}
text = decimal.currency
lastValue = text
}
}
extension CurrencyField {
var doubleValue: Double { (decimal as NSDecimalNumber).doubleValue }
}
extension UITextField {
var string: String { text ?? "" }
}
extension NumberFormatter {
convenience init(numberStyle: Style) {
self.init()
self.numberStyle = numberStyle
}
}
private extension Formatter {
static let currency: NumberFormatter = .init(numberStyle: .currency)
}
extension StringProtocol where Self: RangeReplaceableCollection {
var digits: Self { filter (\.isWholeNumber) }
}
extension String {
var decimal: Decimal { Decimal(string: digits) ?? 0 }
}
extension Decimal {
var currency: String { Formatter.currency.string(for: self) ?? "" }
}
extension LosslessStringConvertible {
var string: String { .init(self) }
}
View Controller
class ViewController: UIViewController {
#IBOutlet weak var currencyField: CurrencyField!
override func viewDidLoad() {
super.viewDidLoad()
currencyField.addTarget(self, action: #selector(currencyFieldChanged), for: .editingChanged)
currencyField.locale = Locale(identifier: "pt_BR") // or "en_US", "fr_FR", etc
}
#objc func currencyFieldChanged() {
print("currencyField:",currencyField.text!)
print("decimal:", currencyField.decimal)
print("doubleValue:",(currencyField.decimal as NSDecimalNumber).doubleValue, terminator: "\n\n")
}
}
Sample project
SwiftUI version of this post here
I started with Leo Dabus' answer (which didn't work out of the box for me) and in the process of trying to simplify and make it work ended up with this, which I think is pretty lean & clean if I do say so myself 😎
class CurrencyTextField: UITextField {
/// The numbers that have been entered in the text field
private var enteredNumbers = ""
private var didBackspace = false
var locale: Locale = .current
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
addTarget(self, action: #selector(editingChanged), for: .editingChanged)
}
override func deleteBackward() {
enteredNumbers = String(enteredNumbers.dropLast())
text = enteredNumbers.asCurrency(locale: locale)
// Call super so that the .editingChanged event gets fired, but we need to handle it differently, so we set the `didBackspace` flag first
didBackspace = true
super.deleteBackward()
}
#objc func editingChanged() {
defer {
didBackspace = false
text = enteredNumbers.asCurrency(locale: locale)
}
guard didBackspace == false else { return }
if let lastEnteredCharacter = text?.last, lastEnteredCharacter.isNumber {
enteredNumbers.append(lastEnteredCharacter)
}
}
}
private extension Formatter {
static let currency: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
return formatter
}()
}
private extension String {
func asCurrency(locale: Locale) -> String? {
Formatter.currency.locale = locale
if self.isEmpty {
return Formatter.currency.string(from: NSNumber(value: 0))
} else {
return Formatter.currency.string(from: NSNumber(value: (Double(self) ?? 0) / 100))
}
}
}
Try this piece of code:
struct DotNum {
private var fraction:String = ""
private var intval:String = ""
init() {}
mutating func enter(s:String) {
if count(fraction) < 2 {
fraction = s + fraction
} else {
intval = s + intval
}
}
private var sFract:String {
if count(fraction) == 0 { return "00" }
if count(fraction) == 1 { return "0\(fraction)" }
return fraction
}
var stringVal:String {
if intval == "" { return "0.\(sFract)" }
return "\(intval).\(sFract)"
}
}
var val = DotNum()
val.enter("1")
val.stringVal
val.enter("2")
val.stringVal
val.enter("3")
val.stringVal
val.enter("4")
val.stringVal
My final code thanks for your help
extension Double {
var twoDigits: Double {
let nf = NSNumberFormatter()
nf.numberStyle = NSNumberFormatterStyle.DecimalStyle
nf.minimumFractionDigits = 2
nf.maximumFractionDigits = 2
return self
}
}
var cleanText:String!
let number:String = sender.currentTitle as String!
if(amountDisplay.text != nil)
{
cleanText = String(Array(amountDisplay.text!).map{String($0)}.filter{ $0.toInt() != nil }.map{Character($0)} ) as String
cleanText = cleanText + number
}else{
cleanText = number
}
amount = (Double(cleanText.toInt()!) / 100).twoDigits
formatter.locale = NSLocale(localeIdentifier: currencies[current_currency_index])
amountDisplay.text = "\(formatter.stringFromNumber(amount!)!)"
Here is a code for swift 2
#IBOutlet weak var txtAmount: UITextField!
//MARK: - UITextField Delegate -
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool{
if string.characters.count == 0 {
return true
}
let userEnteredString = textField.text ?? ""
var newString = (userEnteredString as NSString).stringByReplacingCharactersInRange(range, withString: string) as NSString
newString = newString.stringByReplacingOccurrencesOfString(".", withString: "")
let centAmount : NSInteger = newString.integerValue
let amount = (Double(centAmount) / 100.0)
if newString.length < 16 {
let str = String(format: "%0.2f", arguments: [amount])
txtAmount.text = str
}
return false //return false for exact out put
}
Note : Connect delegate for textField from storyboard or programatically
Just for fun: copied Thomas's answer (full credits -and points- to him please) into a file to run as a Swift 4.1 script (with minor fixes):
dotnum.swift:
#!/usr/bin/swift
struct DotNum {
private var fraction:String = ""
private var intval:String = ""
init() {}
mutating func enter(_ s:String) {
if fraction.count < 2 {
fraction = s + fraction
} else {
intval = s + intval
}
}
private var sFract:String {
if fraction.count == 0 { return "00" }
if fraction.count == 1 { return "0\(fraction)" }
return fraction
}
var stringVal:String {
if intval == "" { return "0.\(sFract)" }
return "\(intval).\(sFract)"
}
}
var val = DotNum()
val.enter("1")
print(val.stringVal)
val.enter("2")
print(val.stringVal)
val.enter("3")
print(val.stringVal)
val.enter("4")
print(val.stringVal)
Then run it in a terminal:
$ chmod +x dotnum.swift
$ ./dotnum.swift
0.01
0.21
3.21
43.21
Thanks to everyone here. From all the answers here I managed to come out with mine.
First I set up the initial value of the textField to be:
private func commonInit() {
amountTextField.text = "0.00"
}
Then I use the UITextFieldDelegate to get the input value and the current textview.text:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
//Need to check if the textfield.text can be evaluated as number or not before passing it to the function
//Get the current text value, and current user input and pass it to the
let formattedAmount = formatAmount(oldAmount: textField.text, userInput: string)
textField.text = formattedAmount
return false
}
Here go my private function to format the number to move from right to left:
private func formatAmount(currentText: String, userInput: String) -> String {
let amount = currentText.components(separatedBy: ".")
var intValue: String = amount[0]
var decimalValue: String = amount[1]
//backspace registered, need to move the number to the right
if userInput.isEmpty {
decimalValue.remove(at: decimalValue.index(before: decimalValue.endIndex))
decimalValue = intValue.last!.string + decimalValue
intValue.remove(at: intValue.index(before: intValue.endIndex))
if intValue.isEmpty {
intValue = "0"
}
} else {
//Need to consider if user paste value
if userInput.count > 2 {
decimalValue = String(userInput.suffix(2))
intValue = String(userInput.dropLast(2))
} else {
decimalValue = rmAmount[1] + userInput
//Add to int value (move to the right)
intValue = intValue + decimalValue.first!.string
if Int(intValue) == 0 {
intValue = "0" //00 -> 0
} else if intValue.first == "0" {
//remove 0 from at the first position in intValue
intValue.remove(at: intValue.startIndex) //01 -> 1
}
//Remove tenth place from decimal value since it goes to Int already
decimalValue.remove(at: decimalValue.startIndex)
}
}
return intValue + "." + decimalValue
}
This is basically it. Other extra implementations can be added by your own initiatives. Let me know if there is any problem with my implementation.
PS: This is of course only works for certain currency only, in my case, my apps is set up only for that local so thats why I use this way.
After a lot of trial and error with the suggested answers, I found a pretty straight forward solution:
The setup for the textField needs to be called in your view's setup.
In the switch statement, if the user puts in a number between 0 and 9, the number is added to the previous string value. The default case covers the backspace button and removes the last character from the string.
The locale for the numberFormatter is set to current, so it works with different currencies.
func setupTextField() {
textField.delegate = self
textField.tintColor = .clear
textField.keyboardType = .numberPad
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
setFormattedAmount(string)
return false
}
private func setFormattedAmount(_ string: String) {
switch string {
case "0", "1", "2", "3", "4", "5", "6", "7", "8", "9":
amountString = amountString + string
default:
if amountString.count > 0 {
amountString.removeLast()
}
}
let amount = (NSString(string: amountString).doubleValue) / 100
textField.text = formatAmount(amount)
}
private func formatAmount(_ amount: Double) -> String {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.locale = .current
if let amount = formatter.string(from: NSNumber(value: amount)) {
return amount
}
return ""
}

Resources