Swift UITextView Delegate - ios

I am having a problem and have searched all across StackO and did not see a solution.
I have a UITextview extension with TextViewDelegate that I call inside of my VC so that i can have a placeholder label. The problem is i now need to add a func that checks for remaining chars in that same textView which i am able to get to work properly. But i cant grab a label to present it on the VC from that extension. I have been trying delegates but since it is a delegate itself i cant use my normal methods. What is the best route to go about this? Thank You for your help!
Here is the code. The placeholder label code is left out since it will make everything longer and I do not feel its needed for a solution. But I can add if necessary. And i can not move this code straight into VC as i need this extension to stay like this.
extension UITextView: UITextViewDelegate {
/// When the UITextView change, show or hide the label based on if the UITextView is empty or not
public func textViewDidChange(_ textView: UITextView) {
if let placeholderLabel = self.viewWithTag(100) as? UILabel {
placeholderLabel.isHidden = !self.text.isEmpty
}
checkRemainingChars(textView: textView)
}
func checkRemainingChars(textView: UITextView) {
let allowedChars = 140
if let charsInTextField = textView.text?.count {
let charsInLabel = charsInTextField
let remainingChars = charsInLabel
if remainingChars <= allowedChars {
//Need to grab this label
charsLeftLabel.textColor = UIColor.lightGray
}
if remainingChars >= 120 {
//Need to grab this label
charsLeftLabel.textColor = UIColor.orange
}
if remainingChars >= allowedChars {
//Need to grab this label
charsLeftLabel.textColor = UIColor.red
}
//This prints fine
print("Remaining chars is \(remainingChars)/140")
//Need to grab this label
charsLeftLabel.text = String(remainingChars)
}
}
Thanks again.

Related

UITextView dynamic height scrolling doesn't work when long text is pasted

I've implemented changing the height of UITextView dynamically when height reaches a certain value by following this solution https://stackoverflow.com/a/38454252/12006517
This works fine however text view freezes when I paste a large chunk of text in it first time. After pasting large chunk of text it doesn't go to the end of text content and cursor disappears while text view freezes. I've to hit delete key and start entering then it starts to work fine.
Subsequent paste of large chunk of text works. So problem happens only pasting first time.
How do I fix this issue?
class MyViewController: UIViewController {
let messageTextViewMaxHeight: CGFloat = 200
}
extension MyViewController: UITextViewDelegate {
func textViewDidChange(_ textView: UITextView) {
if textView.contentSize.height >= self.messageTextViewMaxHeight {
textView.isScrollEnabled = true
} else {
textView.frame.size.height = textView.contentSize.height
}
}
}
You can try below code. It working fine.
func textViewDidChange(_ textView: UITextView) {
let sizeThatShouldFitTheContent = textView.sizeThatFits(textView.frame.size)
if sizeThatShouldFitTheContent.height > 120 {
textView.isScrollEnabled = true
}else{
textView.isScrollEnabled = false
}
}

How to go back when the UITextfield is empty in Swift code?

My question is: When the UITextField is empty, how do I click the "Backspace" button to go to the previous UITextField? I have been struggling trying to do this in my code below?
Second Question: How do I only allow 1 character to get entered in the UITextField?
I am new at Swift code and trying to learn. Any help would be great.
What I am trying to do is have the user be able to type in a code in the 6 UITextFields and be able to click the "Backspace" button on any one of the UITextFields with only allowing the user to enter one number in each UITextField.
Code Below:
#objc func textFieldDidChange(textfield: UITextField) {
let text = textfield.text!
if text.utf16.count == 0 {
switch textfield {
case textField2:
textField1.becomeFirstResponder()
textField1.backgroundColor = UIColor.blue
textField1.tintColor = .clear
case textField3:
textField2.becomeFirstResponder()
textField2.backgroundColor = UIColor.blue
textField2.tintColor = .clear
case textField4:
textField3.becomeFirstResponder()
textField3.backgroundColor = UIColor.blue
textField3.tintColor = .clear
case textField5:
textField4.becomeFirstResponder()
textField4.backgroundColor = UIColor.blue
textField4.tintColor = .clear
case textField6:
textField5.becomeFirstResponder()
textField5.backgroundColor = UIColor.blue
textField5.tintColor = .clear
textField6.resignFirstResponder()
textField6.backgroundColor = UIColor.blue
textField6.tintColor = .clear
default:
break
}
}
else if text.utf16.count == 1 {
switch textfield {
case textField1:
textField1.backgroundColor = UIColor.black
textField1.textColor = .white
textField1.tintColor = .clear
textField2.becomeFirstResponder()
textField2.backgroundColor = UIColor.black
textField2.textColor = .white
textField2.tintColor = .clear
case textField2:
textField3.becomeFirstResponder()
textField3.backgroundColor = UIColor.black
textField3.textColor = .white
textField3.tintColor = .clear
case textField3:
textField4.becomeFirstResponder()
textField4.backgroundColor = UIColor.black
textField4.textColor = .white
textField4.tintColor = .clear
case textField4:
textField5.becomeFirstResponder()
textField5.backgroundColor = UIColor.black
textField5.textColor = .white
textField5.tintColor = .clear
case textField5:
textField6.becomeFirstResponder()
textField6.backgroundColor = UIColor.black
textField6.textColor = .white
textField6.tintColor = .clear
case textField6:
textField6.resignFirstResponder()
default:
break
}
}
}
I'd just like to point out that I'm still relatively new to iOS and Swift in general, but even with just a few minutes of searching, I was able to find some seeds of ideas which provided me with the suggested solution.
Based on your (improved) question, I believe a different approach is required. What you really don't want to use a text component. "Why"?
I here you ask. Because they don't actually provide you with the functionality that you want and come with a considerable overhead.
For this, what you really want is more control. You want to know when a key is pressed and you want to respond to it (I know, sounds like a text component, but) and be notified when more extended functionality occurs, like the delete key is pressed.
After a few minutes of research, some trial and error, I found that the UIKeyInput is more along the lines of what you want.
It will tell you when text is inserted and, more importantly, will tell you when Delete is pressed
The added benefit is, you can filter the input directly. You can take the first character from the String and ignore the rest or auto fill the following elements with the remaining text. You can perform validation (for numerical only content) and what ever else you might want to do
So, I started a really new project, added a UILabel to the UIViewController in the storyboard, bound it to the source and implemented the UIKeyInput protocol as such...
class ViewController: UIViewController {
override var canBecomeFirstResponder: Bool {
return true
}
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
becomeFirstResponder()
}
}
extension ViewController: UIKeyInput {
var hasText: Bool {
return true
}
func insertText(_ text: String) {
print(text)
label.text = text
}
func deleteBackward() {
print("Delete backward")
}
}
I ran the project and when a key was typed, the label was updated with the new key and when delete was pressed, the Delete backward text was printed to console.
Now. You have some choices to make. To use a single UIViewController and (maybe) a series of UILabels and manage interactions within it, so when a key is typed, you present the next label as the input focus (and when delete is pressed, you move back) or do you create a series of UIControls which represent each digit and manage via some delegate call back process.
You may also need to implement the UITextInputTraits protocol, which will allow you to control the keyboard presented
You might also like to have a read through Responding to Keyboard Events on iOS, CustomTextInputView.swift and Showing the iOS keyboard without a text input which were just some of the resources I used to hobble this basic example together with.
you can use this extension for your second question:
import UIKit
private var maxLengths = [UITextField: Int]()
extension UITextField {
#IBInspectable var maxLength: Int {
get {
guard let length = maxLengths[self] else {
return Int.max
}
return length
}
set {
maxLengths[self] = newValue
addTarget(
self,
action: #selector(limitLength),
for: UIControlEvents.editingChanged
)
}
}
#objc func limitLength(textField: UITextField) {
guard let prospectiveText = textField.text,
prospectiveText.count > maxLength
else {
return
}
let selection = selectedTextRange
let maxCharIndex = prospectiveText.index(prospectiveText.startIndex, offsetBy: maxLength)
text = prospectiveText.substring(to: maxCharIndex)
selectedTextRange = selection
}
}
when you add this extension to your project you can see an extra attribute in "Attribute Inspector" tab and you can set the max length of UITextField.

UITextView inside of PopoverPresentationController doesn't response to its delegate method

I am having problem making my UILabel to react to my UITextView by changing its textColor.
So when there over 250 words in my UITextView, my UILabel will should turn red. But it for some reason doesn't do it.
// class NewAppViewController: UIViewController, UITextViewDelegate...
func textViewDidChange(_ textView: UITextView) {
let characterCounts = commentTextField.text.characters.count
wordCountLabel.text = String(250 - characterCounts)
if (250 - characterCounts) < 0 {
self.wordCountLabel.textColor = UIColor.red
sendButton.isEnabled = false
} else {
sendButton.isEnabled = true
}
}
I was being careless. I used UITextField by mistake. So now when I change back to UITextView, everything works fine.
Here is a picture.

how to detect which textview is being edited swift

I've got two textviews on my view and I want them both to be editable.
But each one pertains to a different record in my database.
How would I be able to detect which textView is being edited?
Here is my code so far
func textViewDidChange(textView: UITextView) { //Handle the text changes here
if(textView.textAlignment == .Center){
PFUser.currentUser()!["bio"] = textView.text
PFUser.currentUser()!.saveInBackground()
}
else{
PFUser.currentUser()!["displayName"] = textView.text
PFUser.currentUser()!.saveInBackground()
}
}
What I'm currently doing is detecting if the view is right aligned or center aligned to be able to tell them apart.
This works but it's not ideal as I'd like to have both of them center aligned. But I'm unaware which field in the textView object would contain an ID or some method of identification as to which textView the function was called for.
As long as you have properties that refer to the two text views you can simply see which one was passed to your delegate and act accordingly:
func textViewDidChange(textView: UITextView) { //Handle the text changes here
guard let currentUser = PFUser.currentUser() else {
return
}
if (textView == self.bioTextView){
currentUser["bio"] = textView.text
currentUser.saveInBackground()
} else {
currentUser["displayName"] = textView.text
currentUser.saveInBackground()
}
}

Trigger an event (tableview reload) whenever a text is in a certain position of a textView in iOS

I just started with iOS programming and I could not find anywhere an answer about this.
I have a Textview, containing a large text divided in paragraphs.
I would like to trigger the reload of a Tableview whenever each paragraph reaches the middle of the TextView.
How could I do it?
Here is the code I wrote so far:
#IBOutlet weak var testo: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
if databaseDB.open() {
// Here I definethe string attributes
var style = NSMutableParagraphStyle()
style.lineSpacing = 15
let font = UIFont(name: "Georgia", size: 18.0) ?? UIFont.systemFontOfSize(18.0)
let textFont = [NSFontAttributeName:font, NSParagraphStyleAttributeName : style]
let fontSuper = UIFont(name: "Georgia-Italic", size: 13.0) ?? UIFont.systemFontOfSize(13.0)
let superFont = [NSFontAttributeName:fontSuper, NSBaselineOffsetAttributeName:6]
// and here the variables
let mainText = NSMutableAttributedString()
var paragraphId : String = ""
// I load the text from a table in the database
let querySQL = "SELECT id, versetext FROM table1"
let results:FMResultSet? = databaseDB.executeQuery(querySQL, withArgumentsInArray: nil)
while results!.next(){
paragraphId = results!.stringForColumn("id")
// I add some other information from another table (here goes also other code, but I have skipped it for simplicity)
let queryDescrSQL = "SELECT paragraph FROM table2 WHERE id = \(paragraphId)"
let paragraphResults:FMResultSet? = databaseDB.executeQuery(queryDescrSQL, withArgumentsInArray: nil)
if paragraphResults?.next() == true {
// here I add the attributes to the text to be shown
let numberParagraph = paragraphResults!.stringForColumn("paragraph")
let paragraphNumber = NSAttributedString(string: numberParagraph, attributes:superFont)
mainText.appendAttributedString(paragraphNumber)
let textParagraph = results!.stringForColumn("versetext")+" "
let paragraphText = NSAttributedString(string: textParagraph, attributes:textFont)
mainText.appendAttributedString(paragraphText)
}
}
databaseDB.close()
// and finally everything is loaded on the TextView
testo.attributedText = mainText
}
}
// I'm letting out the code for the TableView, as it is probably not relevant for the matter
I was thinking of adding some other attribute to paragraphNumber, something like here, but I haven't been able to find how to do it.
Thanks in advance,
Silvo
It's not clear how your paragraphs are divided up (is each in its own UITextView, or do they all form one large text view), but the basics of this are that you need to reload a table view when the scroll view reaches a certain scrolled position.
UITableView has a method reloadData, so call this when your scroll view is at the position.
Scrolling a scroll view just means you are modifying its contentOffset, and the scroll view sends its delegate a message every time the content offset changes. Make your controller the delegate of the scroll view, and implement the scrollViewDidScroll(_:) delegate method. In here, check the current content offset, and if it's at the value you're looking for, reload the table view:
class MyViewController: UIViewController, UIScrollViewDelegate {
// var scrollView = ...
// var tableView = ...
override viewDidLoad() {
super.viewDidLoad()
scrollView.delegate = self
}
func scrollViewDidScroll(scrollView: UIScrollView) {
// var scrollThreshold = (calculate some scroll threshold based on your paragraph text)
if scrollView.contentOffset.y > scrollThreshold {
tableView.reloadData()
}
}
}

Resources