I am new to iOS development. I am stuck in a problem where my use case is to get callback each time when the rootviewcontroller of UIWindow changes. I know there is a rootviewcontroller.tansiondelegate delegate property in rootviewcontroller but i am unable to get callback after using this delegate.
you can use the KVO to observe the change of property. like:
[[UIApplication sharedApplication].keyWindow addObserver:self forKeyPath:#"rootViewController" options:NSKeyValueObservingOptionNew context:#"rootViewControllerChange"];
and when the rootViewController is changed, will call method:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
if ([(__bridge NSString *)context isEqualToString:#"rootViewControllerChange"] ) {
// code what you want to do...
}
}
NOTE:
Don't forget to remove this observer when the instance of [self class] dealloc.
[[UIApplication sharedApplication].keyWindow removeObserver:self forKeyPath:#"rootViewController" context:#"rootViewControllerChange"];
if it is not working:
There is a more stupid method:☔️
you can use NSNotificationCenter, when you present a controller, you can
[[NSNotificationCenter defaultCenter] postNotificationName:#"PushNotificationPresentedController" object:nil userInfo:nil];
and addObserver to receive this Notification where you want to callback:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didPresentedController) name:#"PushNotificationPresentedController" object:nil];
(if i have another better method, i will update my answer.)
Got it working. I used method swizzling for this. Very useful concept.
Please refer this for method swizzling technique.
Refer this for similar question.
Related
I am tracking a progress with a UIProgressView and I set an observer to the property I'm tracking.
I add the observer in the viewWillAppear, like this:
-(void)viewWillAppear:(BOOL)animated
{
[self addObserver:self forKeyPath:#"progress" options:0 context:nil];
}
And when I remove the observer in the viewWillDisappear, like this:
-(void)viewWillDisappear:(BOOL)animated
{
[self removeObserver:self forKeyPath:#"progress"];
}
And in the observeValueForKeyPath method I updated the progress view:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
if([keyPath isEqualToString:#"progress"])
{
[self.progressView setProgress:self.progress animated:YES];
}
}
Now when I leave this viewController and I return the observeValueForKeyPath is not being called and I don't get the progressView continuously updated.
The behavior of the progressView according to the code above is shown in this video:
https://youtu.be/PVRT_Jjtdh4
Key-value coding (KVC) is a mechanism for accessing an object’s properties indirectly. In your case "progress" is the property of UIProgressView. But you have registered observer for the UIViewController object which doesn't have 'progress' as property.
Hence replace self with self.progressView
[self addObserver:self.progressView forKeyPath:#"progress" options:0 context:nil];
into
[self addObserver:self.progressView forKeyPath:#"progress" options:0 context:nil];
Also, do the same for detaching observer :
[self removeObserver:self.progressView forKeyPath:#"progress"];
Find updated answer with example. This will help you :
Here you have to register as an observer of the value at a key path relative to the receiver. Also you need to set option.
Use :
-(void)viewWillAppear:(BOOL)animated
{
[[Receiver Instance] addObserver:self forKeyPath:#"progress" options:NSKeyValueObservingOptionNew
context:nil];
}
-(void)viewWillDisappear:(BOOL)animated
{
[[Receiver Instance] removeObserver:self forKeyPath:#"progress"];
}
Rest is as it is.
Here, [Receiver instance] is the instance of class where you value for defined object progress is changing.
Better to remove observer only after class is deallocated. So intsead of placing in viewWillDisappear:animated: you can do the same below :
-(void) dealloc {
[self removeObserver:self forKeyPath:#"progress"]
[super dealloc];
}
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 stuck with following problem. I have a UIScrollView, _myScrollView, and I want to have another UIScrollView following it's movements. So I'm using key-value observing for the properties "zoomScale" and "contentOffset", but the observeValueForKeyPath:ofObject:change:context: method only report changes in "contentOffset", not in "zoomScale", though the zooming workes fine. (See code snippet below.) What could be the reason for this?
-(void)viewDidLoad {
...
[_myScrollView addObserver:self forKeyPath:#"contentOffset" options:NSKeyValueObservingOptionNew context:NULL];
[_myScrollView addObserver:self forKeyPath:#"zoomScale" options:NSKeyValueObservingOptionNew context:NULL];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:#"zoomScale"]) {
NSLog(#"zoomScale: %#", change); // Never gets called
}
...
}
The zoomScale property isn't KVO compliant. But UIScrollViewDelegate has a scrollViewDidZoom method that you can use to track changes to zoomScale.
UIKit actually doesn't support KVO.
From the docs:
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.
It does work sometimes (as you've seen), but support for it is undocumented and inconsistent. Use the delegate methods instead.
#import "USST_Test.h"
#implementation USST_Test
-(void) observeValueForKeyPath:(NSString *)KeyPath ofObject:(id)object change:(NSDictionary *)
change context:(void *) context{
NSLog(#"yes i have been changed",nil);
NSLog(#"%#",[change objectForKey:#"new"]);
}
-(void)setFirstName:(NSString *)firstName{
[self addObserver:self forKeyPath:#"firstName" options:NSKeyValueObservingOptionNew context:nil];
}
#end
Here is my code and I always get NSNull printed.
I am new to Objective-c and any help is appreciated.
I'm not sure that you understand the purpose of KVO. You're having an object observe itself every time the first name is changed. Typically, another object would add an observer once, using a call like this:
[object addObserver:self forKeyPath:#"firstName" options:NSKeyValueObservingOptionNew context:nil];
This means that self would like to be informed when object changes its firstName key. Calling [object setFirstName:name] will automatically trigger the KVO update, so no extra code is needed in it.
You don't need to implement custom setter for this.
Somewhere when you init the UUST_Test class call
[self addObserver:self forKeyPath:#"firstName" options:NSKeyValueObservingOptionNew context:nil];
And remove
-(void)setFirstName:(NSString *)firstName{
[self addObserver:self forKeyPath:#"firstName" options:NSKeyValueObservingOptionNew context:nil];
}
But be careful to remove the observer when the UUST_Test object will be destroyed. For that you can implement -(void)dealloc method but do NOT call [super dealloc] if you are using ARC which I think you use.
I had a property named myName in my class, like:
#property (nonatomic, strong) NSString *myName;
I need to send a notification when the myName property's value is changed.
Now I'm doing something like:
- (void)setMyName:(NSString *)name
{
_myName = name;
[[NSNotificationCenter defaultCenter] postNotificationName:CHANGE_NOTIFICATION object:nil];
}
I know there is something like Key-Value Observing in iOS. But I don't know how to implement it, I read the entire document, but couldn't get a good understanding.
Please help me to understand how to implement the same without using custom setter.
Try this:
MyClass *var = [MyClass new];
[var addObserver:self forKeyPath:#"myName" options:NSKeyValueChangeOldKey context:nil];
and implement
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
}
this method will be called anytime when myName property changes
In - (void)setMyName:(NSString *)name do this instead
[self willChangeValueForKey:#"myName"];
_myName = name;
[self didChangeValueForKey:#"myName"];
//this generates the KVO's
And where you want to listen (the viewController), there in viewDidLoad add this line:
[w addObserver:self forKeyPath:#"myName"
options:NSKeyValueObservingOptionNew context:nil];
//By doing this, you register the viewController for listening to KVO.
and also implement this method:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([[change objectForKey:NSKeyValueChangeNewKey] isEqual:[NSNull null]]) {
return;
} else {
//read the change dictionary, and have fun :)
}
}
//this method is invoked, whenever the property's value is changed.
To do this without the customer setter, just synthesize the property setter. This will create all the supporting calls to willChangeValueForKey / didChangeValueForKey.
#synthesize myName;
Then set property values with dot-syntax:
self.myName = #"Inigo Montoya"
Then the observers will receive the KVO notification automatically.
(You will need to remove the observer before you release the observed object.)