I have a UITextField which is updated from a different module (I'm passing my UITextField to the above mentioned module to fill it). I need a way to identify when text value of the UITextField is changed inside the module which the UITextField originally is. UITextField Delegate methods or UIControlEvents will not work as the text is fill programmatically.
Put the following somewhere, like -viewDidLoad:
[textField addObserver:self forKeyPath:#"text" options:NSKeyValueObservingOptionNew context:nil];
And then add a -observeValueForKeyPath like:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (object == textField)
{
// Do whatever with your text field here
}
}
This will only be called when the text is set programmatically though, not when the user is typing in the field.
Or just subclass UITextField and pass your subclass in. You can override setText: as needed
I don't quite understand what you mean by a "different module", but you can try subscribing to UITextFieldTextDidChangeNotification.
Another way to get notified is by using Key-Value Observing. I don't know if it works for the text property, though.
Related
I am trying to implement a KVO example for clipsToBounds property of all subviews in my UIView. I do not quite understand how to change the value in observeValueForKeyPath method. I am using this code:
-(void)ViewDidLoad{
[self.navigationController.view addObserver:self forKeyPath:#"clipsToBounds" options:NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld context:nil];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSLog(#"Triggered...")
}
It is triggered when ever i change the property clipToBounds of a subview that exists in the UIView i have. I need to change the value back to false for every trigger that happens. What should i write inside the observeValueForKeyPath to change the clipsToBounds property? Any help appreciated.
of course adding the Observer must be done before it works.
Guessing your typo in "ViewDidLoad" would just never be called because it should be "viewDidLoad".
Apart from that your KVO pattern could look like..
static void *kvoHelperClipsToBounds = &kvoHelperClipsToBounds;
-(void)viewDidLoad {
[self.navigationController.view addObserver:self forKeyPath:#"clipsToBounds" options:NSKeyValueObservingOptionNew context:&kvoHelperClipsToBounds];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context == kvoHelperClipsToBounds) {
NSLog(#"context compared successful...");
//be careful what you cast to.. i dont check isKindOf here.
UINavigationBar* navbar = (UINavigationBar*)object;
if (navbar.subviews.count > 1) {
__kindof UIView *sub = navbar.subviews[1];
if (sub.clipsToBounds) {
dispatch_async(dispatch_get_main_queue(),^{
sub.clipsToBounds = NO;
[self.navigationItem.titleView layoutIfNeeded];
});
}
}
}
// or compare against the keyPath
else if ([keyPath isEqualToString:#"clipsToBounds"]) {
NSLog(#"classic Key compare Triggered...");
}
else
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
[super observeValueForKeyPath...] passes not recognized keyPath to super to let super's class KVO work, otherwise those would be ignored if super's implementation would rely on observing them. Which should also explain how you could observe all subviews if needed. But think about that there could be potentially hundrets of subviews triggering observeValueForKeyPath if a subclass of UIView would implement it and all subviews (or the ones you like) would be inherited also from this special subclass.
When you change clipsToBounds inside the KVO where you observe it, you possibly invoke a loop, specially when you watch both - old and new values. you would change the property, the property triggers kvo, kvo changes the property, the property triggers kvo and on and on.
set [self.navigationController.view setClipsToBounds:YES] to change the property. But if done inside KVO it will trigger KVO again as explained.
Usually you would set clipsToBounds in -initWithFrame: or in -initWithCoder: or via Interface Builder and maybe just observe if it gets changed to adapt some other code.
Sidenote: the context just needs to be unique to distinguish it from other KVO.. it could also be reference to a real objects pointer.
Don't forget added Observers must be removed before deallocation.
I was wandering if it was possible to use didSet for a property inside a property.
Let's say I have a function resize() that I want to call every time the text property of a UILabel is set to a new value. I tried using the label's own didSet, but it didn't work, as I was expecting. Is there a way to do this?
it didn't work, as I was expecting
What do you mean by that? didSet works properly for overridden properties. Try this:
class MyLabel : UILabel {
override var text : String? {
didSet {
resize()
}
}
private func resize() {
print("text is now \(text). resizing...")
frame = CGRectMake(...
}
}
Properties modification can be tracked using KVO. After your view, which has reference on target label will be initialised, you need to add add observer like this:
[self.label addObserver:self forKeyPath:#"text"
options:NSKeyValueObservingOptionNew context:nil];
In your view (same from which you called method above) you should have handler method like this:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary<NSString *,id> *)change
context:(void *)context {
// If you subscribed only for one value, you can resize from here or
// add conditions.
}
When you will leave view, don't forget to unsubscribe like this:
[self.label removeObserver:self forKeyPath:#"text" context:nil];
Warning: addObserver and removeObserver should be balanced. If you will call removeObserver extra time, your application will crash.
I really need your help here. I have a UITextView in a UiCollectionViewCell. The problem i am facing here is when i select the cell to edit it , neither the textview methods nor the collectionview methods are called. For ex. neither the didSelectItemAtIndexPath nor the textViewDidBeginEditing methods are invoked.
As a result, I am not able to capture the edits made by the user the cell and save it .
Any inputs would be highly appreciated.
Thanks
I had faced similar problem in custom cell what i did was I had added following code to capture the events
[textField addObserver:self forKeyPath:#"contentSize" options:(NSKeyValueObservingOptionNew) context:NULL];
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
UITextView *tv = object;
//Your code using tv
}
I've got a set of UITextFields which are enabled or disabled based on an if function (basically only allows 2 to be filled in, then the others are disabled).
I've got 2 methods to fade the background of the UItextFieldFade in and Fade out.
I want the animation to run every time the enabled property of the UITextField changes but don't really know how to track the actual change.
Any help would be great.
Thanks
Add an observer to the text field:
[_textField addObserver: self forKeyPath: #"enabled" options: NSKeyValueObservingOptionNew context:NULL];
You should implement this method:
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *) context;
Where you handle the value change of the "enabled" property.
Documentation: Key value observing.
I have a view controller with a view that changes (for example), and I would like to observe the frame of any view that self.view is set to.
Is there any difference between:
[self.view addObserver:self forKeyPath:#"frame" options:0 context:nil];
and
[self addObserver:self forKeyPath:#"view.frame" options:0 context:nil];
For the second one, if the view changes will messages still be recieved when the new view's frame changes, or will it only send messages if the frame of the view that was set when the observer was added?
Is there any way to observe changes to the frame property even if the view of the view controller changes after adding the observer?
Use the second path. #"view.frame" will notify you about the frame changes even when the "view" itself is changed. Cocoa will add observers for every object in the keyPath "chain" for you automatically (which means every item in the keyPath must be KVO-compatible).
You asked if there is a difference between the two, The answer is yes, there is a difference between them:
The first one
says "me as a view", I add an observer named self (aka) viewControllerObject, if you invoked this in viewController.m whenever my property named "frame" is changed.
The Second one
Says "me as ViewController" I'm adding myselfAsAnObserver whenever theKeyPath named "view.frame" is changed.
Since every observer should implement
-(void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
For this case you won't notice much difference because you added a viewController as an observer in either of the method above, but it will make a difference when you are dealing with different objects. But the rule is simple, each added observer should implement the
-(void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
One more thing:
Its a good idea to create a context for observation
e.g
//In MyViewController.m
//..
static int observingViewFrameContext
// In ...
[self addObserver:self
forKeyPath:#"view.frame"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
context:&observingViewFrameContext];
// .. don' forget to remove an observer ! too