How to customize numeric input for a UITextField? - ios

I have a UITextField (that represents a tip value) in my Storyboard that starts out as $0.00. If the user types an 8, I want the textField to read $0.08. If the user then types a 3, I want the textField to read $0.83. If the user then types 5, I want the textField to read $8.35. How would I go about changing the input to a UITextField in this manner?

You can do this with the following four steps:
Make your viewController a UITextFieldDelegate by adding that to the class definition.
Add an IBOutlet to your textField by Control-dragging from the UITextField in your Storyboard to your code. Call it myTextField.
In viewDidLoad(), set your viewController as the textField’s delegate.
Implement textField:shouldChangeCharactersInRange:replacementString:.
Take the incoming character and add it to the tip, and then use the String(format:) constructor to format your string.
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var myTextField: UITextField!
// Tip value in cents
var tip: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
myTextField.delegate = self
myTextField.text = "$0.00"
}
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
if let digit = Int(string) {
tip = tip * 10 + digit
textField.text = String(format:"$%d.%02d", tip/100, tip%100)
}
return false
}
}

Related

Identifying UITextField when using a UITextField Collection in Swift iOS

I am creating a signup screen.
There are four UITextField, which is the ID, password, password check, name,
I am #Iboutlet var signupTextFields: [UITextField]! I connected it. After that, I want to make an ID, password validation.
First, I divided ViewController and UItextFieldDelegate.
class SignUpViewController: UIViewController {
#IBOutlet var signUpTextFields: [UITextField]! {
didSet {
signUpTextFields.forEach { textField in
textField.delegate = textFieldDelegate
textField.returnKeyType = .next
}
}
}
#IBOutlet weak var nextButton: UIButton!
private lazy var textFieldDelegate = TextFieldDelegate(self)
override func viewDidLoad() {
super.viewDidLoad()
signUpTextFields.first?.becomeFirstResponder()
}
#IBAction func nextButtonTapped(_ sender: UIButton) {
}
}
class TextFieldDelegate: NSObject, UITextFieldDelegate {
private weak var signUpViewController: SignUpViewController?
init(_ signUpViewController: SignUpViewController) {
self.signUpViewController = signUpViewController
}
func textFieldDidBeginEditing(_ textField: UITextField) {
textField.layer.borderWidth = 1
textField.layer.borderColor = UIColor.systemBlue.cgColor
}
func textFieldDidEndEditing(_ textField: UITextField, reason: UITextField.DidEndEditingReason) {
textField.layer.borderColor = UIColor.black.cgColor
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
}
The problem when using the functions provided by Delegate is that the example code is to identify UITextField using if-else, such as
func textFieldDidBeginEditing(_ textField: UITextField) {
if textField == idTextField {
//code
} else if textField == passwordTextField {
//code
}
}
I was not this way, but I thought I wanted to abstract the uitextfield a little more to use the Factory method or polymorphism.
Is there a way to identify a UITextField Collection without using if-Else?
I'd recommend - Tag + Enum. (tag field is the norm to identify a view instance from Storyboard / Xib in the cases where you don't have individual IBOutlets)
Set "tag" in the Storyboard - ensure they are unique. (simple index oughta be enough -- 0, 1, 2 .. etc.)
Create an enum inside the view controller.
enum TextField {
case name = 0
case password = 1
}
& so on (explicitly declare the tag values in the enum)
Use Switch instead of if-else for identification.
switch textField.tag {
case .name:
case .password:
}
This should suffice for your case (since you are just going through an example code). But it's good that you are interested in clean code -- I'd recommend creating the TextFields programmatically & using the enum to set the .tag field.
Using the same delegate for all of the text fields was bad design if your intention was to behave differently in the delegate methods. If it’s too late to change that, I would recommend an array of closures corresponding to the array of text fields. That way, calling the right closure is a one-liner based on firstIndex(of:).

Why my label left 1 character when updating UILabel from UITextField?

so I am making UILabel live update from UiTextfield (user input). I am using the code from this thread Swift3: Live UiLabel update on user input
but somehow, my UILabel always left one character when I fully erase the text in my UITextField. like the .gif in here http://g.recordit.co/SPQWnYtHJg.gif
and it seems one character is always missing like the picture below
here is the code I use
import UIKit
class CreateEventVC: UIViewController {
#IBOutlet weak var eventNameTextField: UITextField!
#IBOutlet weak var eventNameLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
//initial value
eventNameLabel.text = " "
// delegate declaration
eventNameTextField.delegate = self
}
}
extension CreateEventVC : UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
eventNameLabel.text = eventNameTextField.text
return true
}
}
I initially suspect because I add this line in viewDidload
eventNameLabel.text = " "
but if i delete this one, the problem is still there
what should I do ?
textField:shouldChangeCharactersIn:range:replacementString is called before the change is applied to the text field, this allows your app to veto the request and filter out unwanted content.
The problem is, you're relying on the text field's text. Instead, you need build the resulting value from the information passed to the delegate and apply that
Maybe something more like...
extension CreateEventVC: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let text = textField.text ?? ""
eventNameLabel.text = (text as NSString).replacingCharacters(in: range, with: string)
return true;
}
}
class CreateEventVC: UIViewController {
#IBOutlet weak var eventNameTextField: UITextField!
#IBOutlet weak var eventNameLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
//initial value
eventNameLabel.text = " "
eventNameTextField.addTarget(self, action: #selector(onTextFieldTextDidChange), for: .editingChanged)
}
#objc func onTextFieldTextDidChange() {
eventNameLabel.text = eventNameTextField.text
}
}
Explanations:
We add target to the eventNameTextField which will call the onTextFieldTextDidChange func each time the textField text is changed.

How to show entered data from UITextField to Label permanently?

I'm trying to make a function where when user enter a text in UITextField in the same moment label shows entered text.
How could I make it?
Like:
textField.text = "10"
Label.text = "\(textField.text) smthg" //. (10 smthg)
textField.text = "10.56"
Label.text = "\(textField.text) smthg" //. (10.56 smthg)
Implement UITextFieldDelegate and set it to textField.delegate property. From UITextFieldDelegate implement shouldChangeCharactersIn callback method that gets called everytime the user tries to change input in the textfield:
class MyViewController: UIViewController {
...
func viewDidLoad() {
super.viewDidLoad()
// set the textField's delegate to self
textField.delegate = self
}
}
extension MyViewController: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// to be always updated, you cannot use textField.text directly, because after this method gets called, the textField.text will be changed
let newStringInTextField = (textField.text as NSString?)?.replacingCharacters(in: range, with: string)
yourLabel.text = "\(newStringInTextField) smthg"
return true
}
}
Using arguments of the function you can get a string that will appear in textField and you can set it as text in yourLabel.
You need to implement textfield's delegate method shouldChangeCharactersIn, it will be called when user start typing, delete a character from textfield, click on clear button appear at right side of textfield when there is text in textfield.
You can use Editing Changed Action for that textField
#IBAction func changeText(_ sender: UITextField) {
Label.text = "\(textField.text) smthg"
}

Updating UILabel when one of 2 textfields are updated

Basically, I am trying to develop an app that has 3 labels, 2 editable & 1 which is not. The 2 editable texts are parameters in a formula, when one of them is updated, the uneditable label should automatically be updated (Think about this as y = a + b, if a is updated, y should automatically be updated).
My problem is that while I formulated a function for the y formula above, I can't seem to get y to update automatically. I have used UITextFieldDelegate's function textField as below:
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var weight: UITextField!
#IBOutlet weak var length: UITextField!
#IBOutlet weak var bmi: UILabel!
#IBOutlet weak var bmiCategory: UILabel!
#IBOutlet weak var textField1: UILabel!
#IBOutlet weak var textField2: UILabel!
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
bmi.text = String(bmiCalculator())
return true
}
func bmiCalculator() -> Float {
let weightValue: Float
let lengthValue: Float
let bmiValue: Float
weightValue = (weight.text! as NSString).floatValue
lengthValue = (length.text! as NSString).floatValue
bmiValue = Float(formatFloat(value: weightValue / (lengthValue * lengthValue)))!
print(bmiValue)
return bmiValue
}
func formatFloat(value: Float) -> String {
let formatter = NumberFormatter()
formatter.maximumFractionDigits = 1
let result = formatter.string(from: value as NSNumber)
return result!
}
}
Note: The code will execute and the build will succeed, but the label will not be automatically updated.
You're attempting the calculation too soon. You've been asked whether the field should change and you're calling a method to use existing values before you respond to allow it.
I suggest handling the UITextFieldTextDidChange notification and updating your dependent label there.
Found the answer in a different way: I defined "a" & "b" as both IBOutlets and IBActions, where the IBAction would execute a function that will call the function bmiCalculator() upon an update in the textfield. see below:
#IBAction func weightFieldUpdate(_ textField: UITextField) {
bmi.text = String(bmiCalculator())
}
#IBAction func lengthFieldUpdate(_ textField: UITextField) {
bmi.text = String(bmiCalculator())
}
EDIT: I also found out that this functionality can be done with only 1 IBAction since the same code is executing
#IBAction func weightAndLengthUpdate(_ textField: UITextField) {
bmi.text = String(bmiCalculator())
}
All thats required is to link the two UITextFields to this IBAction.

UITextView shouldChangeCharactersInRange in Swift

Trying to disable/enable the next button (which begins disabled on the storyboard) based on how many characters are in the textView field. It isn't working, which makes me think that only the textField has this feature and not the textView (they were really lazy with developing the textView field).
I have an outlet connected for the textView and next button:
#IBOutlet weak var textView: UITextView!
#IBOutlet weak var nextButton: UIBarButtonItem!
func textView(textView: UITextView, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let oldText: NSString = textView.text
let newText: NSString = oldText.stringByReplacingCharactersInRange(range, withString: string)
if newText.length > 0 {
nextButton.enabled = true
} else {
nextButton.enabled = false
}
return true
}
How do I get this to work, because it just completely ignores it even if it compiles without errors.
The used delegates method was wrong.
shouldChangeCharactersInRange is for UITextField not UITextView
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string;
Method for UITextView
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text;
Requires UITextViewDelegate reference in class line:
class MyViewController: UIViewController, UITextViewDelegate {
}
AND it also requires a referencing outlet from the view controller to the textView delegate. Select the view controller, click on outlets, drag from referencing outlet to the textView, click delegate.
Probably you have not set the viewController as the delegate of the textField.
There are 2 ways to do that ...
1) Override viewDidLoad and mention textField.delegate = self
override func viewDidLoad() {
super.viewDidLoad()
textField.delegate = self
}
2) open the corresponding storyBoard, control drag the textField to the viewController and select delegate from the pop-up.
You have to define the function name with the correct name, otherwise it will not be recognised and not triggered
func textView(textView: UITextView, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
You'll have to include the UITextViewDelegate reference in the class definition, so that the deligate it assigned to the class:
class ViewController: UIViewController, UITextViewDelegate {
And you have to link the textView delegate (eg. in the viewDidLoad):
textView.delegate = self

Resources