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
Related
I need to find a way to create some class that is always alive in the app. This class doesn't know anything about the other classes in the project. It will be able to "follow" all UIViews on screen-so every moment I can check(loop) over the views and get a pointer to each of them.
It has to run live, and always check positions of views (is it a memory problem?)
Why a pointer? because I need to know everything about it, so if its some kind of moving animation, or maybe it has some meta data like tags, etc. so only knowing there is some view at a certain position is not enough.
Is it possible in iOS ?
The whole idea sounds like an antipattern. However, …
Simply traverse the view tree and add a KVO handler to every view for every interesting property.
- (void)traverseSubviewsOfParentView:(UIView*)view
for( UIView* subview in view.subviews )
{
[view addObserver:self forKeyPath:#"frame" options: NSKeyValueObservingOptionNew];
…
[self traverseSubviewsOfParentView:subview context:NULL];
}
Then implement the observation method:
-(void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if( [#"frame" isEqualToString:keyPath] )
{
// Do what you want to do
}
…
}
Additionally you have to observe the subviews property of every view to get notified, when a view is inserted or removed.
I've looked at some other SO answers in regards to this and I thought I was implementing my code correctly but I am not getting results.
I have a mutable array property - arrLocations. In my .m file, in viewDidLoad I set up an observer for it and then add an item:
self.arrLocations = [[NSMutableArray alloc] init];
//add an observer to know when geocoding loops are updated
[self addObserver:self forKeyPath:#"arrLocations" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
[self insertObject:#"test" inArrLocationsAtIndex:0];
and then I have the KVO method:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if([keyPath isEqualToString:#"arrLocations"]) {
NSLog(#"Showing contents of self.arrLocations\n%#", self.arrLocations);
}
}
But the observer method never gets called.
The observer never gets called because the pointer to your array stays the same when you change the contents of your array.
You would have to add an observer to the array itself and observe a key of the array. Something like count. But you cannot do that because NSMutableArray is not KVO compliant.
So, to make this work you have to find another way. My first idea would be to create a wrapper class for NSMutableArray that fires a notification each time you add or remove items to your array.
I'm starting to learn objective-c and my first app would be simple. I created a class to handle a timer. It has a property currentTime, a startAndPause method and a stopTimer method.
I'm initialising my Timer in the viewDidLoad method of my ViewController as :
_minu = [[KUUMinuteur alloc] initWithDuration:#70];
Then, I want my ViewController to observe my _minu.currentTime property changes. So I did this :
[_minu addObserver:self forKeyPath:#"currentTime" options:NSKeyValueObservingOptionNew context:NULL];
And in my viewController, I wrote this method but it never triggers :
-(void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSLog(#"Changing !!");
}
I don't know what am I doing wrong. :(
(My App is a single View app.)
EDIT : forgot to translate tempsPublic to currentTime
You probably are never changing the currentTime property of KUUMinuteur.
I wonder if you are trying to directly update _currentTime ivar that backs your currentTime property. When changing the value of a property you do not want to use the ivar (other than the init, dealloc, and custom setter methods, if any):
_currentTime = ...; // wrong
You want to use the setter, e.g.
self.currentTime = ...; // right
or
[self setCurrentTime:...]; // right
Make sure you use the setter or else the key-value notification will not take place.
See the Use Accessor Methods to Set Property Values section of the Advanced Memory Management Programming Guide. Or see the Automatic Change Notification section of the Key-Value Observing Programming Guide.
[_minu addObserver:self forKeyPath:#"currentTime" options:NSKeyValueObservingOptionNew context:NULL];
It should be currentTime instead of tempsPublic.
I have a container view that holds a view controller. I need to set a non-UI property in this view controller before awakeFromNib is called. However, the prepareForSegue method for the embed segue isn't called until after awakeFromNib happens.
Is there any way to pass this information to the contained view controller before awakeFromNib?
I have a similar issue in one of my apps.
Basically, I have a ViewController that has a property for the data model, but I am never sure when in my lifecycle the data model is actually set. My solution was to use Key-Value Observing to receive a callback when it's set.
Somewhere before the value can be set:
[self addObserver:self forKeyPath:#"propertyName" options: 0 context: nil];
Callback:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:#"propertyName"]) {
//do something
}
}
remember to unregister (I do it in my dealloc)
[self removeObserver:self forKeyPath:#"propertyName"];
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.