textField allowing all characters despite ShouldChangeCharactersIn function - swift

As I'm trying to internalize code I've been using for years (without much understanding), I've created my version that in theory should copy its purpose.
I have a textField in which I'm only allowing decimal numbers and one period - ".". However, at the moment, my textField is allowing any character to be entered.
I have imported the UITextFieldDelegate class, connected my UITextField as an outlet, and set my textfield to the textFieldDelefate in viewDidLoad.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
//characterSet that holds digits
var allowed = CharacterSet.decimalDigits
//initialize the period for use as a characterSet
let period = CharacterSet.init(charactersIn: ".")
//adding these two characterSets together
allowed.formUnion(period)
//all the characters not in the allowed union
let inverted = allowed.inverted
//if latest input is from the characters not allowed is present (aka, empty), do not change the characters in the text range
if string.rangeOfCharacter(from: inverted) != nil
{
return false
}
//if the text already contains a period and the string contains one as well, do not change output
else if (textField.text?.contains("."))! && string.contains(".")
{
return false
}
//however, if not in the inverted set, allow the string to replace latest value in the text
else
{
return true
}
This function is not disabling the more than one period and invert of decimal numbers.

I seems to work for me, I moved some of the character setup into some lazy vars so that it is only done once, and not every time the delegate is called.
import UIKit
class ContainerController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var textField: UITextField!
//characterSet that holds digits
lazy var allowed:CharacterSet = {
var allowed = CharacterSet.decimalDigits
//initialize the period for use as a characterSet
let period = CharacterSet.init(charactersIn: ".")
//adding these two characterSets together
allowed.formUnion(period)
return allowed
}()
lazy var inverted:CharacterSet = {
return allowed.inverted
}()
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func viewDidLoad() {
super.viewDidLoad()
textField.delegate = self
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
print("shouldChangeCharactersIn", range)
print("replacementString", string)
//if latest input is from the characters not allowed is present (aka, empty), do not change the characters in the text range
if string.rangeOfCharacter(from: inverted) != nil {
return false
} else if (textField.text?.contains("."))! && string.contains(".") {
//if the text already contains a period and the string contains one as well, do not change output
return false
} else {
//however, if not in the inverted set, allow the string to replace latest value in the text
return true
}
}
}

Related

Set password protection in UITextView

Can i hide password in UITextView by * or any other symbol? I need to use UITextView instead of UITextField. I want to hide all characters of textView.
Using an UITextView leaves the whole job of masking the text yourself. You also need to make sure you disable copying for security reasons. Set your delegate property and handle this something on these lines:
var originalText: String?
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
originalText = ((originalText ?? "") as NSString).replacingCharacters(in: range, with: text)
return true
}
func textViewDidChange(_ textView: UITextView) {
textView.text = String(repeating: "*", count: (textView.text ?? "").count)
}
If you need to retrieve the value of the actual text that was input use the originalText property.
Create a global variable for password string.
var passwordString = ""
Then set delegates of UITextView like:
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
passwordString = ((passwordString ?? "") as NSString).replacingCharacters(in: range, with: text)
return true
}
func textViewDidChange(_ textView: UITextView) {
//replace character with * or anyother character
yourtextView.text = String(repeating: "*", count: (textView.text ?? "").count)
}
and dont forget to do this:
yourTextview.delegate = self
I like to share my own implementation after using the previous answers for a while in a chat-like app, where the UITextView is constantly filled and emptied.
My UITextView works as an entry of text for different kind of data types (phones, e-mails, etc.) and I did not want to create other UITextView specifically for handling this scenario, so I decided to subclass it and restructure a little bit the code because I faced with circumstances that the logic breaks when using an external keyboard or changing the text property programatically (doing the last one does not call the delegate method).
So first subclassing...
UITextView subclass
class MyTextView: UITextView {
var isProtected = false // `true` for activate the password mode
var plainText: String! = String() // Variable to save the text when `isProtected`
override var text: String! {
get { return isProtected ? plainText : super.text }
set {
if !isProtected {
plainText = newValue
}
super.text = newValue
}
}
}
PS: The overriding of the text property helps us to get always the plain text in the UITextView without calling other variables.
Then, in the view controller where the delegate is implemented...
UITextViewDelegate
extension MyViewController: UITextViewDelegate {
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if myTextView.isProtected {
myTextView.plainText = (myTextView.plainText as NSString).replacingCharacters(in: range, with: text) // Basically: when is in password mode, saves all written characters in our auxiliar variable
}
return true
}
func textViewDidChange(_ textView: UITextView) {
if myTextView.isProtected {
textView.text = String(repeating: "•", count: textView.text.count) // Change every letter written with the character "•"
}
}
}
Finally, you only need to toggle the isProtected flag somewhere in MyViewController and that`s it:
myTextView.isProtected = true //or `false`

How to prevent username textfield for not using whitespace or special characters swift 3.0 [duplicate]

I am creating a trivia application that asks for a username on start up. I'd like to make it impossible to use characters such as #$#!^& etc (also including "space"). I took a look at this post here but it is written entirely in Objective-C. Thanks in advance.
Swift 4 iOS 11.2.x based on using an extension, tests to see if a string is a valid hex number in this example.
extension String {
var containsValidCharacter: Bool {
guard self != "" else { return true }
let hexSet = CharacterSet(charactersIn: "1234567890ABCDEFabcdef")
let newSet = CharacterSet(charactersIn: self)
return hexSet.isSuperset(of: newSet)
}
}
You use it like with the UITextFieldDelegate.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
return (string.containsValidCharacter)
}
Although I read in an earlier post that CharacterSets do not support characters that are composed of more than one Unicode.Scalar; so use with caution I guess.
Since you're explicitly asking for Swift, I've translated the top asnwer in the linked question.
let notAllowedCharacters = " ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.";
func textField(
textField: UITextField,
shouldChangeCharactersInRange range: NSRange,
replacementString string: String)
-> Bool
{
let set = NSCharacterSet(charactersInString: notAllowedCharacters);
let inverted = set.invertedSet;
let filtered = string
.componentsSeparatedByCharactersInSet(inverted)
.joinWithSeparator("");
return filtered != string;
}
internal func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool{
if let text = string{
if text == "#" || text == "$" || text == "!"{ \\and so on
return false
}
}
return true
}
Swift 2.3
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let characters = ["#", "$", "!", "&","#"]
for character in characters{
if string == character{
print("This characters are not allowed")
return false
}
}
}
So this is probably the most robust way to restrict Spaces. Using this user won't be able to Paste/Type Whitespaces
This is how you can Implement using Swift 3.
Add below mentioned extension snippet to a Swift file;
extension String {
var containsWhitespace: Bool {
for scalar in unicodeScalars {
switch scalar.value {
case 0x20:
return true
default:
continue
}
}
return false
}
}
In your ViewController Swift file drag out your Editing Changed Instance and a Referencing Outlet of UITextField from Storyboard, the one mentioned in picture below:
Use the dragged Instances as mentioned below:
Referencing Outlet as:
#IBOutlet weak var textField: UITextField!
and Editing Changed as:
#IBAction func textChanged(_ sender: UITextField) {
if (textField.text!.containsWhitespace) == true {
print("Restrict/Delete Whitespace")
emailField.deleteBackward()
} else {
print("If Its not Whitespace, Its allowed.")
}
}
This will detect and remove whitespace as soon as user tries to type/paste it.
Swift 4 iOS 11.2.x based on using an extension, tests to see if a string is a valid hex number in this example.
extension String {
var containsValidCharacter: Bool {
guard self != "" else { return true }
let hexSet = CharacterSet(charactersIn: "1234567890ABCDEFabcdef")
let newSet = CharacterSet(charactersIn: self)
return hexSet.isSuperset(of: newSet)
}
}
You use it like with the UITextFieldDelegate.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
return (string.containsValidCharacter)
}
Swift : 3 and a different approach:
Add a target function for the text field change in your viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
textField.addTarget(self, action: #selector(ViewController.textFieldDidChange(textField:)), for: UIControlEvents.editingChanged)
}
in the target function, simply detect the entered char and replace it with blank. I have tested it and it prevents the user from entering any non desirable characters in the text field.
func textFieldDidChange(textField: UITextField) {
if let textInField = textField.text{
if let lastChar = textInField.characters.last{
//here include more characters which you don't want user to put in the text field
if(lastChar == "*")
{
textField.text = textInField.substring(to: textInField.index(before: textInField.endIndex))
}
}
}
}
Adding on to what #Evdzhan Mustafa said. You want to add a return statement in case the string is empty. Without it you won't be able to delete your text. Modified Code Below:
Swift 3 Version
let notAllowedCharacters = " ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.";
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if string.isEmpty{
return true
}
print("String: \(string)")
let set = NSCharacterSet(charactersIn: notAllowedCharacters);
let inverted = set.inverted;
let filtered = string.components(separatedBy: inverted).joined(separator: "")
print("String Filtered: \(filtered)")
return filtered != string;
}

Swift: Limit Characters # in textField without disabling/interfering with other functions

I want to limit the number of characters a user can use in a textField. I took a function from this link: Max length UITextField (Imanou Petit)
However, in my viewDidLoad() I have several textFields that I'm already referencing the delegate because I want the keyboard to "Return" when the user presses the Return key on the keyboard. This I'm doing through the textFieldShouldReturn like this (I also have a touchesBegan method but I want the user to also have the option of the Return key):
func textFieldShouldReturn(textField: UITextField!) -> Bool {
self.stuffOneTextField.resignFirstResponder()
self.linkTextField.resignFirstResponder()
self.descriptionTextField.resignFirstResponder()
self.ogTextField.resignFirstResponder()
self.priceTextField.resignFirstResponder()
return true
}
If I add in this function below to the viewDidLoad, then the 'Return' key on the keyboard doesn't work and it limits ALL of the textFields (I have 5).
override func viewDidLoad() {
super.viewDidLoad()
self.stuffOneTextField.delegate = self
self.linkTextField.delegate = self
self.descriptionTextField.delegate = self
self.ogTextField.delegate = self
self.priceTextField.delegate = self
}
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
guard let text = textField.text else { return true }
let newLength = text.characters.count + string.characters.count - range.length
return newLength <= limitLength
}
I only need to limit 2 textFields. I've tried just putting in the specific textField name instead of all the textFields as textFields and then it limits 1 textField and doesn't let me type in the others... Very strange...
How do I go around this?
Any help means a lot.
You're being given the textField that is changing characters in the delegate function. Here, you can compare it to the specific fields that you want to limit:
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
guard let text = textField.text else { return true }
if !(textField == stuffOneTextField || textField == descriptionTextField) {
return true
}
let newLength = text.characters.count + string.characters.count - range.length
return newLength <= limitLength
}
Also, func textFieldShouldReturn(textField: UITextField!) -> Bool is giving you a text field, and you can call resignFirstResponder() on that, if that is what you want to do there.

Swift checking textfield input live

I've been struggling to find how to check the input of a textfield as a user is typing.
if a user types a word, it should change a label and an image according to some defined rules.
my code is working, but I'm always a step behind. (as it reads the content always before the next character is entered.
If it just to check the length I could use countElements(textfield) + 1, but I want it to also show that a user cannot use certain characters as they are typing, therefore that would not work for checking undesired characters.
I am assuming the function I am using is not the right one "shouldChangeCharacters". So I am a bit lost as to what to use. Is there a way to read a println or NSLog command to return to an outlet?
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let passwordcheck = UserPasswordTextField.text
if UserPasswordTextField.isFirstResponder() {
if isValidPassword(passwordcheck) {
PWimg.image = UIImage(named: "passwordapprovedicon")
} else if passwordcheck.isEmpty {
PWimg.image = UIImage(named: "passwordiconwrong")
} else {
PWimg.image = UIImage(named: "passwordiconwrong")
}
}
func isValidPassword(testStr2:String) -> Bool {
println("validate password: \(testStr2)")
let passwordRegEx = "[A-Z0-9a-z._%+-:/><#]{6,30}"
if let passwordTest = NSPredicate(format: "SELF MATCHES %#", passwordRegEx) {
return passwordTest.evaluateWithObject(testStr2)
}
return false
Listen for UIControlEventEditingChanged events from the text field. Register for them either with
the Interface Builder: drag from the text field to the file, and select the action type "Editing Changed"
the following code:
override func viewDidLoad() {
super.viewDidLoad()
// ...
textField.addTarget(self, action:"edited", forControlEvents:UIControlEvents.EditingChanged)
}
func edited() {
println("Edited \(textField.text)")
}
Updated for swift 3:
override func viewDidLoad() {
super.viewDidLoad()
//...
textField.addTarget(self, action: #selector(textFieldDidChange), for:.editingChanged)
}
func textFieldDidChange(){
print(textField.text)
}
Updated for swift 4.2: just add #objc to func textFieldDidChange()
override func viewDidLoad()
{
super.viewDidLoad()
textField.addTarget(self,
action : #selector(textFieldDidChange),
for : .editingChanged)
}
#objc func textFieldDidChange()
{ print(textField.text ?? "Doh!") }

Uneditable prefix inside a UITextField using Swift

I'm having a problem regarding the creation of a prefix inside a UITextField using the new Swift language. Currently I have created the UITextField using the Interface Builder and I have assigned an IBOutlet to it, named usernameField, then using the textFieldDidBeginEditing function I write a NSMutableAttributedString inside it, named usernamePrefix, containing only the word "C-TAD-" and finally I limited the UITextField max characters number to 13, like so:
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet var usernameField : UITextField!
private var usernamePrefix = NSMutableAttributedString(string: "C-TAD-")
func textFieldDidBeginEditing(textField: UITextField) {
if textField == usernameField {
if usernameField.text == "" {
usernameField.attributedText = usernamePrefix
}
}
usernameField.addTarget(self, action: "textFieldDidChangeText:", forControlEvents:UIControlEvents.EditingChanged)
}
func textField(textField: UITextField!, shouldChangeCharactersInRange range: NSRange, replacementString string: String!) -> Bool {
let maxUsernameLength = countElements(usernameField.text!) + countElements(string!) - range.length
return maxUsernameLength <= 13
}
override func viewDidLoad() {
super.viewDidLoad()
usernameField.delegate = self
passwordField.delegate = self
}
}
Now, how can I assign new parameters to the usernamePrefix in order to have to give 2 different colors to the text written in the UITextField? I would like to have the prefix in .lightGreyColor() and the rest in .blackColor(). Also how can I make the usernamePrefix un-editable and un-deletable by the user?
Thanks for the help
Simpler option would be to set leftView of the UITextField and customise it how you like it:
let prefix = UILabel()
prefix.text = "C-TAD-"
// set font, color etc.
prefix.sizeToFit()
usernameField.leftView = prefix
usernameField.leftViewMode = .whileEditing // or .always
It is un-editable and un-deletable and you don't need to do any calculations to check the length of the input.
For the first part, you can refactor your delegate method as follow.
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
//This makes the new text black.
textField.typingAttributes = [NSForegroundColorAttributeName:UIColor.blackColor()]
let protectedRange = NSMakeRange(0, 6)
let intersection = NSIntersectionRange(protectedRange, range)
if intersection.length > 0 {
return false
}
if range.location == 12 {
return true
}
if range.location + range.length > 12 {
return false
}
return true
}
This will lock down both the length at 13 and the prefix can not be deleted. Everything typed will be UIColor.blackColor()
Then you can a method like the following in your viewDidLoad, to set the prefix.
func makePrefix() {
let attributedString = NSMutableAttributedString(string: "C-TAD-")
attributedString.addAttribute(NSForegroundColorAttributeName, value: UIColor.lightGrayColor(), range: NSMakeRange(0,6))
textField.attributedText = attributedString
}
I've adopted the solution from Jeremy and make a little bit improvement to make it a bit more swifty, and also handle the case when user pastes multiple characters into the text field.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let protectedRange = NSRange(location: 0, length: usernamePrefix.length)
let intersection = protectedRange.intersection(range)
// prevent deleting prefix
if intersection != nil {
return false
}
// limit max character count
if (textField.text ?? "").count + string.count > 13 {
return false
}
return true
}