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`
Related
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
}
}
}
I currently have a text view with placeholder text that dissapears whenever a user taps on the text view and the text in the textview reappears whenever the first responder is resigned, if the text view is empty. (Heres the code I use for that in case anyone wants to use it)
*Note, first set the text color of the textview to light gray and set the placeholder text. Then use these methods:
func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
//If it begins editing, then set the color to black
if (textView.tag == 0){
textView.text = ""
textView.textColor = .black
textView.tag = 1
}
return true
}
func textViewDidEndEditing(_ textView: UITextView) {
if textView.text.isEmpty {
textView.text = "Example: I started my career as a street wear model based in Maryland. After 3 years of working with some of the top companies there, I moved to LA, where I currently reside. I’ve been featured in shows, 12 magazines, commercials, and a number of music videos. Now, Im currently looking to continue working with clothing companies and campaigns."
textView.textColor = .lightGray
textView.tag = 0
}
}
I wanted to step things up a notch. Right now, the text disappears whenever the text view becomes the first responder. I want the text to disappear whenever the user actually starts typing, not just when the text view is selected. When the screen appears, I automatically set the first responder to the text view and I plan on keeping it that way. But because its automatically set, you're not able to see the placeholder text. I only want the text view to disappear whenever the user presses a key, not because its selected.
Lets assume you have a placeholder text as this,
let placeholderText = "Example: I started my career as a street wear model based in Maryland. After 3 years of working with some of the top companies there, I moved to LA, where I currently reside. I’ve been featured in shows, 12 magazines, commercials, and a number of music videos. Now, Im currently looking to continue working with clothing companies and campaigns."
And you are setting this text to textView in storyboard or in viewDidLoad before calling becomeFirstResponder of textView.
Then in these two delegate methods you can achieve this behavior as below,
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if textView.text == placeholderText {
textView.text = ""
}
return true
}
func textViewDidEndEditing(_ textView: UITextView) {
if textView.text.isEmpty {
textView.text = placeholderText
}
}
Currently you are clearing the text in textViewShouldBeginEditing so, that is the main reason you are not able to see that text. You should remove clearing the text there but you can keep changing the colors etc as it is.
Here's the code
class ViewController: UIViewController {
#IBOutlet weak var textView: UITextView!
let placeholder = "Example: I started my career as a street wear model based in Maryland. After 3 years of working with some of the top companies there, I moved to LA, where I currently reside. I’ve been featured in shows, 12 magazines, commercials, and a number of music videos. Now, Im currently looking to continue working with clothing companies and campaigns."
let start = NSRange(location: 0, length: 0)
override func viewDidLoad() {
super.viewDidLoad()
textView.delegate = self
textView.text = placeholder
}
}
extension ViewController: UITextViewDelegate {
func textViewDidChangeSelection(_ textView: UITextView) {
// Moves cursor to start when tapped on textView with placeholder
if textView.text == placeholder {
textView.selectedRange = start
}
}
func textViewDidChange(_ textView: UITextView) {
// Manages state of text when changed
if textView.text.isEmpty {
textView.text = placeholder
textView.textColor = .lightGray
} else if textView.text != placeholder {
textView.textColor = .black
}
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
// Called when you're trying to enter a character (to replace the placeholder)
if textView.text == placeholder {
textView.text = ""
}
return true
}
}
Updated #1: Maximum length
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
// Called when you're trying to enter a character (to replace the placeholder)
if textView.text == placeholder {
textView.text = ""
} else if textView.text.count >= 175 && text.count > 0 {
// Now it can delete symbols but can't enter new
return false
}
return true
}
There's slight bug when trying to clear textView by holding erase button. Placeholder is not showing. Method textViewDidChange somehow is not called (goes out of sync with shouldChangeTextIn). Possible workaround https://stackoverflow.com/a/9169556/5228431
I'm implementing a tagging feature similar to that of Facebook. So when I type # and some character(s) after it, the function should return the word being typed.
So if the textView contains (and the cursor is at c)
Hello #Jac !
The function should return "#Jac"
If it contains (and the cursor is at a)
Hello #Ja !
Then the function should return "#Ja"
The final string of both examples would be,
Hello Jack !
I have attempted multiple solutions but none are working. One particular question was very similar to my question, but the solution has errors. Here is the link.
Update 1
Here is how I've set the delegate on the textView,
postView.textView.delegate = self
This is the code for detecting if the # character was tapped (display the friends list table, if it was)
if let text = self?.characterBeforeCursor() {
if (text == "#" && self?.friends.count != 0) {
self?.friendTableView.isHidden = false
} else {
var word // Need to get the word being typed
self?.displayedFriends = (self?.displayedFriends.filter { ($0["firstName"]?.hasPrefix(word))! })!
}
}
Update 2
The solution below did not solve the problem. It is returning all text in the textfield instead of just the word that is being typed.
There is a delegate function called shouldChangeCharactersInRange. From there you can get the current text after the user tapped the letter.
Make sure you use UITextFieldDelegate in your class declaration and set the textField's delegate to self.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
//get the updated text from the text field like this:
let text = (textField.text as NSString?)?.replacingCharacters(in: range, with: string)
//note that we *need* to use the text as NSString, because the delegate method gives us an NSRange, rather than a Range which we can't use on String, but NSString, so we need to convert that first
return true //so the text is visually updated in the textfield
}
Edit
I just saw that you posted about UITextView. It's pretty much the same:
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
let text = (textView.text as NSString?)?.replacingCharacters(in: range, with: text)
return true
}
Edit 2
You need to also assign the text view's text to your variable in the code snippet you provided:
} else {
var word = postView.textView.text
self?.displayedFriends = (self?.displayedFriends.filter { ($0["firstName"]?.hasPrefix(word))! })!
}
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
}
I've a UITextView with scroll enabled, I can change font and size of the text inside. So, How can I set a text limit? I've already tried to set max row and max number of character but it doesn't working because I need to set a size limit of the text, How can I do this?
You have to implement the UITextViewDelegate:
class ViewController: UIViewController, UITextViewDelegate {
and then set the delegate of the textView in the viewDidLoad-method:
yourTextView.delegate = self
After that you can use the shouldChangeTextInRange method to check the letters:
func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool {
let maxtext: Int = 140
//If the text is larger than the maxtext, the return is false
// Swift 2.0
return textView.text.characters.count + (text.characters.count - range.length) <= maxtext
// Swift 1.1
// return countElements(textView.text) + (countElements(text.length) - range.length) <= maxtext
}
The nice part in this solution is, that you can even control cut/copy and paste. So the user can't trick the field to accept more letters by copying.
1) class ViewController: UIViewController, UITextViewDelegate {
2) textView.delegate = self
3)
func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool {
let maxCharacter: Int = 1000
return (textView.text?.utf16.count ?? 0) + text.utf16.count - range.length <= maxCharacter
}
Usually you'd conform to the UITextFieldDelegate protocol, specify your view controller as the delegate for the UITextField (specifying this either in IB or in code) and then implement a shouldChangeCharactersInRange that considers the length of the string:
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let oldString = textField.text ?? ""
let startIndex = oldString.startIndex.advancedBy(range.location)
let endIndex = startIndex.advancedBy(range.length)
let newString = oldString.stringByReplacingCharactersInRange(startIndex ..< endIndex, withString: string)
return newString.characters.count <= kMaxLength
}
If, on the other hand, you want to control the amount of text entered to conform to the size of the control, you could use the aforementioned shouldChangeTextInRange, but use sizeWithAttributes to dictate whether that method returns true or false rather than the number of characters.
This isn't quite right, but it illustrates the basic idea:
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let oldString = textField.text ?? ""
let startIndex = oldString.startIndex.advancedBy(range.location)
let endIndex = startIndex.advancedBy(range.length)
let newString = oldString.stringByReplacingCharactersInRange(startIndex ..< endIndex, withString: string)
let stringSize = newString.sizeWithAttributes([NSFontAttributeName : textField.font ?? UIFont.systemFontSize()])
return stringSize.width < textField.editingRectForBounds(textField.bounds).size.width
}