chickenparm
chickenparm

Reputation: 1630

Limit Text Field to one decimal point input, numbers only, and two characters after the decimal place - Swift 3

I am struggling to do this with Swift 3. I have a text field that I would like to limit to only numbers and one decimal point and two characters after the decimal place. I would also like to have it work in regions where a decimal point is not used when entering non-integers. Thank you for any suggestions!

Upvotes: 1

Views: 1974

Answers (3)

Sushant Gosavi
Sushant Gosavi

Reputation: 3845

If you are using Swift UI then the complete solution

  1. TextField allow numeric value only
  2. Should accept only one comma (".")
  3. Restrict decimal point upto x decimal place

File NumbersOnlyViewModifier

import Foundation
import SwiftUI
import Combine
struct NumbersOnlyViewModifier: ViewModifier {
    
    @Binding var text: String
    var includeDecimal: Bool
    var digitAllowedAfterDecimal: Int = 1
    
    func body(content: Content) -> some View {
        content
            .keyboardType(includeDecimal ? .decimalPad : .numberPad)
            .onReceive(Just(text)) { newValue in
                var numbers = "0123456789"
                let decimalSeparator: String = Locale.current.decimalSeparator ?? "."
                if includeDecimal {
                    numbers += decimalSeparator
                }
                if newValue.components(separatedBy: decimalSeparator).count-1 > 1 {
                    let filtered = newValue
                    self.text = isValid(newValue: String(filtered.dropLast()), decimalSeparator: decimalSeparator)
                } else {
                    let filtered = newValue.filter { numbers.contains($0)}
                    if filtered != newValue {
                        self.text = isValid(newValue: filtered, decimalSeparator: decimalSeparator)
                    } else {
                        self.text = isValid(newValue: newValue, decimalSeparator: decimalSeparator)
                    }
                }
            }
    }
    
    private func isValid(newValue: String, decimalSeparator: String) -> String {
        guard includeDecimal, !text.isEmpty else { return newValue }
        let component = newValue.components(separatedBy: decimalSeparator)
        if component.count > 1 {
            guard let last = component.last else { return newValue }
            if last.count > digitAllowedAfterDecimal {
                let filtered = newValue
               return String(filtered.dropLast())
            }
        }
        return newValue
    }
}

File View+Extenstion

extension View {
    func numbersOnly(_ text: Binding<String>, includeDecimal: Bool = false) -> some View {
        self.modifier(NumbersOnlyViewModifier(text: text, includeDecimal: includeDecimal))
    }
} 

File ViewFile

 TextField("\(count, specifier: Constants.FloatFormat.twoDecimalFloat)", text: $value,  onEditingChanged: { isEditing in
      self.isEditing = isEditing
   })

  .foregroundColor(Color.neutralGray900)
  .numbersOnly($value, includeDecimal: true)
  .font(.system(size: Constants.FontSizes.fontSize22))
  .multilineTextAlignment(.center)

Upvotes: 0

Do2
Do2

Reputation: 1791

 var number = Double(yourTextfield.text)
 if number != nil {
   //if user enters more than 2 digits after the decimal point it will round it up to 2
   let roundedNumber = Double(num!).roundTo(places: 2)  
 }
 else {
//probably print an error message
}

Upvotes: 0

Oleg Sherman
Oleg Sherman

Reputation: 2802

You need to assign delegate to your textfield and in the shouldChangeCharactersIn delegate method do your validations:

  1. Add extension with validation methods for the string:

    extension String{
    
        private static let decimalFormatter:NumberFormatter = {
            let formatter = NumberFormatter()
            formatter.allowsFloats = true
            return formatter
        }()
    
        private var decimalSeparator:String{
            return String.decimalFormatter.decimalSeparator ?? "."
        }
    
        func isValidDecimal(maximumFractionDigits:Int)->Bool{
    
            // Depends on you if you consider empty string as valid number
            guard self.isEmpty == false else {
                return true
            }
    
            // Check if valid decimal
            if let _ = String.decimalFormatter.number(from: self){
    
                // Get fraction digits part using separator
                let numberComponents = self.components(separatedBy: decimalSeparator)
                let fractionDigits = numberComponents.count == 2 ? numberComponents.last ?? "" : ""
                return fractionDigits.characters.count <= maximumFractionDigits
            }
    
            return false
        }
    
    }
    
  2. In your delegate method:

    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    
        // Get text
        let currentText = textField.text ?? ""
        let replacementText = (currentText as NSString).replacingCharacters(in: range, with: string)
    
        // Validate
        return replacementText.isValidDecimal(maximumFractionDigits: 2)
    
    }
    

Upvotes: 2

Related Questions