UITextView length issue - swift

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
}

Related

TextField i.e. showing % during text input

how to append % sign after input
extension ViewController: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let currentText = textField.text! as NSString
let newText = currentText.replacingCharacters(in: range, with: string)
if newText.hasSuffix("%"){
textField.text = newText
}else{
textField.text = "\(newText) %"
}
return true
}
}
as in the picture i just want 1 % sign after the numbers.
https://developer.apple.com/documentation/uikit/uitextfielddelegate/1619599-textfield says:
Return Value: true if the specified text range should be replaced;
otherwise, false to keep the old text.
probably because you return true swift updates with the old text replacing the text property of the text-field you just set. Try returning false as this 'keeps the old text' which is already updated by your code.
The reason is func shouldChangeCharactersIn call before the text is input so it is wrong. What we want in here is after the value changed, add % at the end if not have. Here is the code for do that.
// add action for value change recognized
textField.addTarget(self, action: #selector(didChangeValueTextField(textField:)), for: .editingChanged)
#objc final private func didChangeValueTextField(textField: UITextField)
{
let text = textField.text ?? ""
if !text.hasSuffix("%") {
textField.text! = text + "%"
// make cursor position to be left 1 index because final index is %
let newPosition = textField.position(from: textField.endOfDocument, offset: -1)!
textField.selectedTextRange = textField.textRange(from: newPosition, to: newPosition)
}
}

Slow NSAttributed String Rendering

I'm working on an app for text editing, I when I enter more than 10 000 the app is very slow. I run some for loops during typing, but it's should be a big deal. Is it possible to load the text as you scroll down, in chunks, or something else? Like Dispatch etc? I'm working with NSAttributed String
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool
{
textView.textStorage.addAttribute(NSForegroundColorAttributeName, value: styles.titleTextColor().withAlphaComponent(0.10), range: NSMakeRange(0, textView.attributedText.length))
let textRange: UITextRange? = textView.tokenizer.rangeEnclosingPosition((textView.selectedTextRange?.start)!, with: UITextGranularity.sentence, inDirection: 1)
if textRange != nil
{
let sentenceRange = textView.rangeFromTextRange(textRange: textRange!)
textView.textStorage.addAttribute(NSForegroundColorAttributeName, value: styles.titleTextColor().withAlphaComponent(1), range: sentenceRange)
}
}
extension UITextView
{
func rangeFromTextRange(textRange: UITextRange) -> NSRange
{
let location : Int = self.offset(from: self.beginningOfDocument, to: textRange.start)
let length : Int = self.offset(from: textRange.start, to: textRange.end)
return NSMakeRange(location, length)
}

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.

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
}