Remove Observer iOS - ios

I want to remove this observer in my code :
[self.noteLabel addObserver:self forKeyPath:#"contentSize" options:(NSKeyValueObservingOptionNew) context:NULL];
What is the good practice to remove it ?
Thanks
EDIT
I want to remove this observer because I need to remove its parentView. Actually it crashes because of this observer. What would be the good practice to remove a subview with an observer ? Thanks for your help.

Whenever you want to remove an observer, you just have to use removeObserverwith the right parameters.
[self.noteLabel removeObserver:self forKeyPath:#"contentSize"];

This is not completely safe what you've presented. You can use such code:
#pragma mark - KVO
static void *_myContextPointer = &_myContextPointer;
- (void)enableObserver:(BOOL)enable onObject:(id)object selector:(SEL)selector {
NSString *selectorKeyPath = NSStringFromSelector(selector);
if (enable) {
[object addObserver:self forKeyPath:selectorKeyPath options:0 context:&_myContextPointer];
} else {
#try {
[object removeObserver:self forKeyPath:selectorKeyPath context:&_myContextPointer];
} #catch (NSException *__unused exception) {}
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context != _myContextPointer) {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
return;
}
[self observerActionForKeyPath:keyPath ofObject:object change:change];
}
and ofc such code to handle your observer:
- (void)observerActionForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change {
NSString *contentSizeKeyPath = NSStringFromSelector(#selector(contentSize));
if ([keyPath isEqualToString:contentSizeKeyPath]) {
// do something
}
}
Then you just call it eg:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self enableObserver:YES onObject:self selector:#selector(contentSize)];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self enableObserver:NO onObject:self selector:#selector(contentSize)];
}
Via using such code, your app won't crash because you remove observers few times in a row. Also you can't make a typo in your property name and it will dynamically change when you refactor your code. All KVO is in one place.
You can read more about safe KVO on NSHipster page.

Typically you start observing some key path in -init and stop doing so in -dealloc.
I would unregister yourself in -dealloc.

Related

dispatch_async crashes app when re-used

I'm using a solution for here to make titleView clipsToBounds always true.
I have this in my ViewController and it works well, however, if I leave the ViewController by pressing the back button and then come back, it app crashes at the dispatch_async line.
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if([object isEqual:[[self.navigationController.navigationBar subviews] objectAtIndex:2]]) {
dispatch_async(dispatch_get_main_queue(), ^{
[[self.navigationController.navigationBar subviews] objectAtIndex:2].clipsToBounds = NO;
[self.navigationItem.titleView layoutIfNeeded];
});
}
}
Edit:
The only error I get is: Thread 1: EXC_BAD_ACCESS (code=1, address=0x102d8860)
The console doesn't provide any information other than (lldb)
You must removeObserver if you go out from viewController.
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.navigationController.navigationBar.subviews[2]
removeObserver:self
forKeyPath:#"clipsToBounds"];
}
Check to your block
if (self.navigationController.navigationBar.subviews.count > 1){
…

observeValueForKeyPath not being called when returning to a UIViewController

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];
}

How should I be watching changes to UILabel's text when set in Interface Builder?

I'm building a UILabel library that is mostly intended to be used programmatically but I want it to work with Interface Builder as well. When the user calls setText on my UILabel subclass, I watch it there and update the text as per the label's use, but it doesn't appear to be called with IB.
Should I just be watching awakeFromNib?
Question should read: Using KVO in UIView subclass
If so, -initWithCoder: is the preferred location while -awakeFromNib still works as mentioned by #KhanhNguyen.
The problem becomes How do I find my UILabel? and here is a possible answer using -viewWithTag:
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if(self = [super initWithCoder:aDecoder]) {
[self registerKVO:[self viewWithTag:1]
forKeyPath:NSStringFromSelector(#selector(text))];
}
return self;
}
Answering the general UIViewController question
Note - (instancetype)initWithCoder:(NSCoder *)aDecoder is not the correct place to install the KVO in a UIViewController since the view hasn't loaded and neither has the label.
Register KVO using a typical Referencing Outlet
- (void)viewDidLoad {
[super viewDidLoad];
[self registerKVO:self.label
forKeyPath:NSStringFromSelector(#selector(text))];
}
Regardless of your approach, responding to KVO looks like this:
#pragma mark - NSObject(NSKeyValueObserving)
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if ([keyPath isEqualToString:NSStringFromSelector(#selector(text))]) {
NSLog(#"%#",change[NSKeyValueChangeNewKey]);
}
}
...and setup + teardown like that:
- (void)registerKVO:(NSObject *)object forKeyPath:(NSString *)keypath {
[object addObserver:self
forKeyPath:keypath
options:NSKeyValueObservingOptionNew
context:nil];
}
- (void)dealloc {
[self removeObserver:self forKeyPath:NSStringFromSelector(#selector(text))];
#if !__has_feature(objc_arc)
[super dealloc];
#endif
}

failed to get the new value in KVO observer method

#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.

Send Notification When a Property is Changed Using KVO

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.)

Resources