I have an observer on an UIView to adjust a UIWebView when the size is changed (e.g. on rotation of the device):
[[self view] addObserver:self forKeyPath:#"frame" options:0 context:nil];
Naturally I need to remove this observer when the view is dismissed (otherwise there is an exception). But where would I do this? I tried in dealloc, or in viewDidDisappear, but I get a SIGABRT when I do this.
- (void) viewDidDisappear:(BOOL)animated {
NSLog(#"Is moving %d - %d", self.isMovingFromParentViewController, self.isBeingDismissed);
[[self view] removeObserver:self forKeyPath:#"frame"];
}
I tried to move it in earlier on ViewWillDisappear, but the same result.
Moreover, I am not completely happy with this later solution (even if it would work), as it will potentially also unload when the view is temporary out of view (i.e. with pageViewController), although that is not that big a deal for me at the moment.
Tracking the problem down to this item and trying to solve it has already cost me a day, so any suggestions are really appreciated!
Related
- (void)setTableView:(UITableView *)tableView {
_tableView = tableView;
[_tableView addObserver:self
forKeyPath:#"frame"
options:0
context:nil];
[self updateFrame];
}
The exc_bad_access occurs when trying to add an observer.
In the assembly code, the error code is 'NSKeyValueObserverRegistrationLock'.
Have no idea what's causing the error.
I'm running XCTest so there might be a possibility that error was cause by injecting the test code into application code.
Anybody helps?
A couple of things.
You should define your options parameter. The NSKeyValueObservingOptions struct doesn't have an entry for 0. If you are after the new value then use NSKeyValueObservingOptionNew.
Next I assume the function you have listed resides in a UIViewController? UIViewController doesn't have a frame property. It's view does though (so does your tableView). I'm not sure which frame you're trying to observe, but you can try:
[_tableView addObserver:self.view
forKeyPath:#"frame"
options:NSKeyValueObservingOptionNew
context:NULL];
Or
[_tableView addObserver:tableView
forKeyPath:#"frame"
options:NSKeyValueObservingOptionNew
context:NULL];
I'm trying subclass a UIView in which I want to add more subviews. Since I'm changing frame property of my view in code, I want my subviews to resize when this view is resized, so I've added the following code in my initWithCoder: method:
[self addObserver:self forKeyPath:#"frame" options:NSKeyValueObservingOptionNew context:nil];
And in the observation method, I'll reset view's frame manually. Everything works fine, but when I push some other view controller and move back, I got this message:
Observation info was leaked, and may even become mistakenly attached to some other object.
So, how can I fix this? I know for sure that the observer is not removed, but there is not any viewDidDisappear stuff here. What should I do?
Thanks!
You should remove the observer for all added observers, So do like following code,
- (void)dealloc {
[self removeObserver:self forKeyPath:#"frame" context:NULL];
}
I have an iOS app with a UIScrollView that basically looks like the Messages.app: content on the screen and on the bottom a text view and a button to add more content. When the keyboard appears the text view and button move up correctly.
I've set keyboardDismissMode so that dragging the keyboard down makes it disappear but during the process of the dragging, as the keyboard is moving down, how can I update my views' locations on screen to stay attached to it? It seems that the keyboard will change frame notification isn't fired during this process.
What's the "right" way of doing this?
Edit: I have a hunch it might be doable using an input view/accessory view, but not sure that's the right direction to go.
Pretty sure this behavior requires SPI. BUT: You could walk the view/window hierarchy, find the keyboard window, and apply a transform to it to move it during your interaction. (window.transform=)
Not sure what to do when your interaction ends however--maybe manually animate the keyboard away (using the above technique) to finish the keyboard hide, then when it's hidden, resign first responder without animating.
EDIT:
Sorry, I thought you wanted to move the keyboard, but if you want to observe the keyboards position, you can use KVO for that (as #KudoCC suggested)
static void * __keyboardCenterKVOContext = & __keyboardCenterKVOContext ;
-(void)setUpKeyboardObserver
{
UIView * keyboardView = ...?
[ keyboardView addObserver:self forKeyPath:#"center" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial context:__keyboardCenterKVOContext ] ;
}
Then implement the KVO observer:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ( context == __keyboardCenterKVOContext )
{
CGPoint newKeyboardCenter = [ [ change valueForKey:NSKeyValueChangeNewKey ] pointValue ] ;
// .. handle new keyboard position here ...
}
else
{
[ super observeValueForKeyPath:keyPath ofObject:object change:change context:context ] ;
}
}
Your KVO observer will be run every time the keyboard view changes it's center property. You might also try observing the keyboard window's frame and/or transform properties.
I have some code to help with KVO observing here, which might help you: https://gist.github.com/nielsbot/6873377
(The reason you might want to use my helper code is because it automatically removes KVO observation from objects that are being deallocated. This requires some run time fiddling, but in your case you don't really know when the keyboard window will be deallocated. Although maybe you can tear down KVO in your keyboardWillHide handler)
You can try notification to move your content when keyboard disappear and appear:
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:#selector(keyboardDisappeared) name:UIKeyboardWillHideNotification object:nil];
[center addObserver:self selector:#selector(keyboardAppeared) name:UIKeyboardWillShowNotification object:nil];
-(void) keyboardDisappeared
{
[UIView animateWithDuration:1.0 delay:0 options:yourAnimationOption animations:^
{
self.view.frame = CGRectMake(self.view.frame.origin.x, self.view.frame.origin.y+100(same as you do in keyboardAppeared), self.view.frame.size.width, self.view.frame.size.height);
} completion::^(BOOL finished)
{
}];
}
-(void) keyboardAppeared
{
[UIView animateWithDuration:1.0 delay:0 options:yourAnimationOption animations:^
{
self.view.frame = CGRectMake(self.view.frame.origin.x, self.view.frame.origin.y-(as much you want), self.view.frame.size.width, self.view.frame.size.height);
} completion::^(BOOL finished)
{
}];
}
I am setting up a NOTIFICATION to get KEYBOARD information to adjust the view so that the selected text field is not covered by the KEYBOARD.
#property CGSize keyboard;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillShow:)
name:#"UIKeyboardWillShowNotification"
object:nil];
...
}
- (void) keyboardWillShow:(NSNotification *)note
{
NSDictionary *userInfo = [note userInfo];
self.keyboard = [[userInfo objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
}
I have set up the UITextFieldDelegate so I can use the KEYBOARD information in
- (void)textFieldDidBeginEditing:(UITextField *)textField
Everything works perfectly ...
except for the very first time.
The NOTIFICATION that sets up the KEYBOARD property is called after textFieldDidBeginEditing so the very first time the KEYBOARD property has not been set up so it displays incorrectly.
After that things are fine since the KEYBOARD property has already been set up and the values don't change.
How can I get the KEYBOARD information before the first execution of textFieldDidBeginEditing?
EDIT:
Did find something of a solution, and it does seem to work, but it feels a little hackish to me and I'm still wondering if there is a way to get the keyboard information.
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.txtField becomeFirstResponder];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self.txtField resignFirstResponder];
}
Does give a brief flash at the very bottom of the screen as it displays and hides the keyboard, which I don't like, but it's probably not very obvious or noticeable to someone that doesn't know to look for it.
I have to break up the becomeFirstResponder and resignFirstResponder into different methods because if they get called from the same method then keyboardWillShow does not get called.
Also, can not place becomeFirstResponder in viewDidLoad because keyboardWillShow is not called in that situation either.
If anyone has an improvement, or a better way, I'd love to hear it.
One way you can do this is that instead of setting the text field's frame in the begin editing, you can iterate through your text fields checking the isFirstResponderproperty, and move the frame of the one that is first responder. You can do all that in your method where you get the keyboards frame.
How do you remove an observer from an object under ARC? Do we just add the observer and forget about removing it? If we no longer manage memory manually where do we resign from observing?
For example, on a view controller:
[self.view addObserver:self
forKeyPath:#"self.frame"
options:NSKeyValueObservingOptionNew
context:nil];
Previously, I would call removeObserver: in the view controller's dealloc method.
You still can implement -dealloc under ARC, which appears to be the appropriate place to remove the observation of key values. You just don't call [super dealloc] from within this method any more.
If you were overriding -release before, you were doing things the wrong way.
I do it with this code
- (void)dealloc
{
#try{
[self.uAvatarImage removeObserver:self forKeyPath:#"image" context:nil];
} #catch(id anException) {
//do nothing, obviously it wasn't attached because an exception was thrown
}
}
Elsewhere on stack overflow, Chris Hanson advises using the finalize method for this purpose and implementing a separate invalidate method so that owners can tell objects that they are done. In the past I have found Hanson's solutions to be well-thought-out, so I'll be going with that.