How to respond to programmatic changes of a TextView text - ios

The TextView delegate is set:
textView.delegate = self //self being a UITextViewDelegate
but the delegate method doesn't get called when the text is set programmatically
func textViewDidChange(_ textView: UITextView) {
print(textView.text)
}
How to respond to text changes without going reactive?

This is how UITextField (and most of UIKit controls) behave- doesn't trigger event when set programatically. It makes sense- lets you avoid recurring, infinite calls.
If you really want to be notify when text is changed programatically, you have to subclass UITextField and override text property (probably attributedText also). Then in didSet block call delegate method.
Don't forget that UITextField inherits from UIControl- I would also call sendActions(for:) to make target-action mechanism fire.

I just tried KVO on UITextView,
self.textView1.text = "You are working, but I will change you in 5 seconds"
Add your observer
self.textView1.addObserver(self, forKeyPath: "text", options: NSKeyValueObservingOptions(rawValue: 0), context: nil)
Trigger text change programmatically, just an example do it the way you want.
DispatchQueue.main.asyncAfter(deadline:.now() + 5) {
self.textView1.text = "Changed after some time"
}
Override the KVO method.
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if object == self.textView1{
//do your thing here...
}
}
FYI from Apple docs below
Note: Although the classes of the UIKit framework generally do not
support KVO, you can still implement it in the custom objects of your
application, including custom views.
https://developer.apple.com/library/content/documentation/General/Conceptual/DevPedia-CocoaCore/KVO.html

declare your text view variable with #objc dynamic
declare and hold a variable with type NSKeyValueObservation
use function observe(_:changeHandler:) bind your text view's text property, hold the return value with variable declared in step 2
observe changes in changeHandler
example:
#objc dynamic private var textView: UITextView!
private var observation: NSKeyValueObservation?
func bind() {
observation = observe(\.textView.text, options: [.old, .new]) { object, change in
print(object, change)
}
}

You have misspelled the delegate with textView with _textView
func textViewDidChange(textView: UITextView) { //Handle the text changes here
print(textView.text); //the textView parameter is the textView where text was changed
}
Put this in viewDidLoad
textView!.delegate = self

Related

Overriding 'isSelected' or 'isEnabled' on UIButton does not work

I am trying to make a custom UIButton subclass that has different colors in the normal, selected, and disabled states. My button lives in a framework that is then imported into an app, but every code snippet I place here, I have tried in both the main app and the framework--I know it shouldn't make any difference, but I wanted to cover my bases. I can't get it to work to save my life.
class BrokenButton: UIButton {
override var isEnabled: Bool {
didSet {
print("This is never called no matter what I do")
}
}
}
I've tried using KVO to watch the value of isEnabled since overriding the setter did not work:
class BrokenButton2: UIButton {
required init() {
super.init(frame: .zero)
addObserver(self, forKeyPath: #keyPath(isEnabled), options: [.new], context: nil)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
print("Never called")
}
}
I'm at my wit's end here. What am I getting wrong about this?
#Daniel As the BrokenButton class is inside the Framework, you need to use open keyword for accessing from outside the other modules. So just add the open keyword before the BrokenButton class and isEnabled property.
open class BrokenButton: UIButton {
override open var isEnabled: Bool {
didSet {
print("This is never called no matter what I do")
}
}
}
An open class is accessible and subclassable outside of the defining
module. An open class member is accessible and overridable outside of
the defining module.
for further info regarding open keyword..read this stackoverflow answer
I figured the same as meaning-matters. Here are some steps you can take to reproduce a way in which it works. It's possible you missed any one of these steps.
Create BrokenButton class which is a subclass of UIButton. Just as you did in your question above.
Open up storyboard or xib and drag a UIButton into your storyboard or xib
Select the UIButton you just dragged into the storyboard/xib, and in the identify inspector, make sure you make the class BrokenButton
Create an Outlet in your ViewController something like this:
#IBOutlet weak var button: BrokenButton?
In your storyboard/xib, connections inspector, connect the button to your IBOutlet
Then in your viewcontroller, set the button to be enabled or disabled like this:
button?.isEnabled = true
Here it is in the works.

How to prevent editing of text in a UITextField without disabling other events using Swift 3?

I'm trying to modify the text rendered in the UITextField based on certain events such as Touch Down or Touch Down Repeat. I want the UITextField to be responsive only to events but prevent users from modifying the actual value in the UITextField.
I have tried unchecking the Enabled state of the UITextField but that causes it to not respond to touch events either.
How do I prevent users from changing the contents of the UITextField without affecting the response to touch events, specifically using Swift 3?
So, I was finally able to get this working in the expected manner. What I did was -
1 - Extend the UITextFieldDelegate in the ViewController class
class ViewController: UIViewController, UITextFieldDelegate {
2 - Inside the function viewDidLoad(), assign the textfield delegate to the controller
override func viewDidLoad() {
super.viewDidLoad()
textfield.delegate = self
}
(you will have assign each UITextField's delegate that you want to be prevented from editing)
3 - Lastly, implement your custom behavior in the textFieldShouldBeginEditing function
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
return false
}
For reference, check out this API Reference on Apple Developer
Override textFieldShouldBeginEditing , and return false.
func textFieldShouldBeginEditing(state: UITextField) -> Bool {
return false
}
you can also disable for specific text field.
func textFieldShouldBeginEditing(textField: UITextField) -> Bool {
if(textField == self.myTextField){
return false
}
return true;
}

How to get the UITextField's delegates events while subclassing it, without losing the delegation if the user of it set itself as the delegate?

For simplicity, let's say I want to create a custom UITextField and I want to add a simple behaviour to it; Which is, if the textfield becomes the first responder, the background color would be changed to green.
To do so, in my custom class I have to set the class as the delegate to receive the event of becoming first responder. But the thing is that if the user of this custom textfield set itself as the delegate the events are not sent to the custom textfield(Since only one object can be the delegate of another object)
I can manually forward all the events, but I'm looking for a cleaner and more scalable solution.
Here's a sketch of the situation:
class MyTextField: UITextField {
override func awakeFromNib() {
super.awakeFromNib()
delegate = self
}
}
extension MyTextField: UITextFieldDelegate {
func textFieldDidBeginEditing(textField: UITextField) {
backgroundColor = UIColor.greenColor()
}
}
but if the user of MyTextField do this:
class ViewController: UIViewController {
#IBOutlet weak var myTextField: MyTextField!
override func viewDidLoad() {
super.viewDidLoad()
myTextField.delegate = self
}
}
the behaviour won't work; because the delegation relationship to MyTextField is gone.
NOTE: I'm not only interested in becoming first responder problem, rather it's about using any methods of the delegate, with capability of the user of my custom UITextField setting itself as the delegate, at the same time.
Thanks, in advance.
As you say, most delegation is restricted to a single object as the delegate.
Since a text field is a responder, you should be able to override func becomeFirstResponder() -> Bool to change the color, while letting the user of the object handle the delegation as it expects.
UIResponder docs: "Subclasses can override this method to update state or perform some action such as highlighting the selection."

How to clear multiple textviews upon Editing?

I am trying to clear multiple textviews on editing. I know how do so with one textView (IE):
class ViewController: UIViewController, UITextViewDelegate {
#IBOutlet weak var myTextView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
myTextView.delegate = self
}
func textViewDidBeginEditing(textView: UITextView) {
myTextView.text = ""
}
How would I use the same concept for multiple textviews?
func textViewDidBeginEditing(textView: UITextView) {
myTextView.text = ""
}
The above delegate method will get called when a text view begins editing. And this method holds the reference to the textView that called in the textView object. You can use that reference to clear the text instead of using a separate reference/outlet to the textView.
So the method would be:
func textViewDidBeginEditing(textView: UITextView) {
textView.text = ""
}
From the Documentation:
Description:
Tells the delegate that editing began in the specified text field.
This method notifies the delegate that the specified text field just
became the first responder. Use this method to update state
information or perform other tasks. For example, you might use this
method to show overlay views that are visible only while editing.
Implementation of this method by the delegate is optional.
Parameters:
textView
The text view in which an editing session began.

How to trigger textViewdelegate on programmatical UITextView edits

If I am typing with the keyboard, then textViewDidChange and shouldChangeTextInRange are always called. But when I change the textView programmatically, the delegate methods aren't called. How do I get a programmatic change in textView to trigger delegate methods?
Update
Is there some other way for simulating keyboard input programmatically?
I solve this issue in my code using the insert method instead to change the value of the text string.
textView.insertText("Your Text")
Just ran into this question a few years later but had a hard time finding other good answers, so I want to demonstrate Satheesh's technique answered here with greater detail that worked for me for future readers and people that experience a similar issue.
Technique
First, add an observer to the textView:
let property = "text" //can also be attributedText for attributed strings
self.textView.addObserver(self, forKeyPath: property, options: NSKeyValueObservingOptions(rawValue: 0), context: nil)
Next, override the observeValue function:
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if object as? NSObject == self.textView {
//your code here
}
}
The observeValue function will run after the property value of the textView you added the observer to has been changed. Note that when you programmatically set the text of the textView like:
textView.text = "programmatically assigned text"
The shouldChangeTextIn delegate function is ran before the observer is called. As a side note, if you are using attributedText as the property, I would also recommend setting the property in that function and returning false:
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
let newString = NSString(string: textView.text!).replacingCharacters(in: range, with: text)
textView.attributedText = createAttributedString(text: newString)
return false
}
This ensures that you do not duplicate the text you are inputting into the text and attributedText properties. You can also use the delegate function to reject input, such as new lines, and it will not call the observeValue function. To re-iterate, that function is only called after the shouldChangeTextIn function returns true or false.
If there are any mistakes here or anything else someone else would like to add, feel free to let me know.
Happy programming!

Resources