For your unsigned text field you can use the same approach shown in this post. For you will need to do some changes to allow negative input as well. First add a toolbar to allow the user to switch positive/negative input and use a range instead of a maximum value. You don't need to check how many digits the user is entering just make sure the value entered is contained in that range. To allow the user to finish editing the field with a return key you can make the field its delegate and implement textFieldShouldReturn method. Last but not least when filtering the digits make sure you don't remove the minus sign prefix when present:
Your SignedIntegerField should look like this:
import UIKit
class SignedIntegerField: UITextField, UITextFieldDelegate {
var value: Int { string.digits.integer ?? 0 }
var range = -999...999
private var lastValue: Int = 0
override func willMove(toSuperview newSuperview: UIView?) {
precondition(range ~= 0)
delegate = self
addToolbar()
addTarget(self, action: #selector(editindDidEnd), for: .editingDidEnd)
addTarget(self, action: #selector(editingChanged), for: .editingChanged)
keyboardType = .numberPad
textAlignment = .right
sendActions(for: .editingChanged)
}
func addToolbar() {
let toolbar = UIToolbar()
toolbar.sizeToFit()
toolbar.barStyle = .default
toolbar.items = [
.init(title: "+/-",
style: .plain,
target: self,
action: #selector(minusAction)),
.init(barButtonSystemItem: .flexibleSpace,
target: self,
action: nil),
.init(title: "Clear",
style: .plain,
target: self,
action: #selector(clearAction)),
.init(barButtonSystemItem: .flexibleSpace,
target: self, action: nil),
.init(title: "Done",
style: .plain,
target: self,
action: #selector(doneAction))]
inputAccessoryView = toolbar
}
@objc func minusAction() {
if text!.hasPrefix("-") {
text!.removeFirst()
} else {
text!.insert("-", at: text!.startIndex)
}
}
@objc func clearAction() { text = "0" }
@objc func doneAction() {
if text == "-" { text = "0" }
resignFirstResponder()
}
override func deleteBackward() {
text!.remove(at: text!.index(before: text!.endIndex))
sendActions(for: .editingChanged)
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
resignFirstResponder()
return true
}
@objc func editingChanged() {
guard range ~= value else {
text = Formatter.decimal.string(for: lastValue)
return
}
text = Formatter.decimal.string(for: value)
print("Value:", value)
lastValue = value
}
@objc func editindDidEnd() {
if text == "-" { text = "0" }
}
}
fileprivate extension UITextField {
var string: String { text ?? "" }
}
fileprivate extension Formatter {
static let decimal = NumberFormatter(numberStyle: .decimal)
}
fileprivate extension NumberFormatter {
convenience init(numberStyle: Style) {
self.init()
self.numberStyle = numberStyle
}
}
fileprivate extension StringProtocol where Self: RangeReplaceableCollection {
var digits: Self {
first == "-" ?
"-" + dropFirst().filter("0123456789".contains) :
filter("0123456789".contains)
}
var integer: Int? { Int(self) }
}
sample project