dispatch_async crashes app when re-used - ios

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){
…

Related

KVO lost when entering background, trying to fix

I have this method, adding an observer in myViewController:
-(void) observeSpeakerVolume{
[[AVAudioSession sharedInstance] setActive:YES error:nil];
[[AVAudioSession sharedInstance] addObserver:self forKeyPath:#"outputVolume" options:NSKeyValueObservingOptionNew context:nil];
}
I invoke this method in the viewdidLoad of myViewController:
- (void)viewDidLoad {
[self performSelectorInBackground:#selector(observeSpeakerVolume) withObject:nil];
[super viewDidLoad];
}
and finally I monitor the observer as follows:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
if ([keyPath isEqualToString:#"outputVolume"]) {
if ([[change valueForKey:#"new"] isEqualToNumber:#0]){
...
} else {
...
}
}
}
This all works fine, but as soon as my app is send to the background, eg by hitting the home button, the observer seems to be automatically removed.
To fix that, I do this in my AppDelegate:
- (void)applicationWillEnterForeground:(UIApplication *)application {
myViewController * wfc = [[myViewController alloc]init];
[wfc observeSpeakerVolume];
}
That seems to work, however, switching back and forth several times between back- and foreground, eventually changing the volume of my iPhone crashes the app.
I tried to manually remove the observer when entering the background, but that also crashes the app, supporting the suggestion that the OS removes the observer automatically when entering the background.
So, I assume, that I'm adding the observer several times, but I don't see where, since ViewDidLoad is not called when the app enters the foreground.
What is wrong in my logic here?
thanks for your insights.
Bit late, but I had similar issue.
If you call
[[AVAudioSession sharedInstance] setActive:YES error:nil];
after your app is back from background e.g. in
- (void)applicationWillEnterForeground:(UIApplication *)application;
then the observer will work again.

Remove Observer 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.

iOS: Key-Value Observing does not dismiss modal view

I have the following key value observer method in a modal view:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:#"uploadComplete"]) {
NSLog(#"UploadVC hears upload complete");
[self dismissViewControllerAnimated:YES completion:nil];
}
}
I use this to watch a photo object and know when it is finished uploading. When I run this it behaves as expected, and the console logs "UploadVC hears upload complete" - but then the following line is not executed -- the modal does not get dismissed.
There are no errors or anything else, the view just sits there and the modal is never dismissed. What's going on here?
That may happen when you receive KVO notification on background thread and so attempts to update UI may result in any unexpected behaviour (UI not changed, changed after some delay, app crash etc etc). Make sure you call all updating UI code on main thread:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:#"uploadComplete"]) {
NSLog(#"UploadVC hears upload complete");
dispatch_async(dispatch_get_main_queue(), ^{
[self dismissViewControllerAnimated:YES completion:nil];
});
}
}

Observing custom cell

I am trying to add observer (KVO) to observe my custom cell. Once the cell is selected I should receive a notification of the event.
My code:
[colMain addObserver:self forKeyPath:#"colMain" options:0 context:NULL];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (keyPath == #"colMain") {
NSLog(#"cell Selected");
[self performSelector:#selector(deleteCell) withObject:nil];
}
}
colMain stands for collectionView. I am not quite sure how to do it cause I don't have customCell as a property otherwise it does not compile.
Any ideas?
Why not just set a delegate on your collection view and then implement one of these two methods?
[– collectionView:shouldSelectItemAtIndexPath:]
[– collectionView:didSelectItemAtIndexPath:]

KVO on NSMutableString not working

I'm trying to observe changes to an NSMutableString isDetailView:
-(void)viewDidLoad {
[self addObserver:self forKeyPath:#"isDetailView" options:NSKeyValueObservingOptionNew context:nil];
[isDetailView setString:#"YES"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
NSLog(#"obersedValueFOrKeyPath:%#", keyPath);
}
But the observeValueForKeyPath method never gets called. Any ideas?
You are not changing the property, only the content of the object it points to. If you make isDetailView a normal string and do
[self setIsDetailView: #"YES"]
it will work.
By the way, properties that start "is" are conventionally supposed to be boolean and that looks like a more appropriate type in this case too.

Resources