Multiple NSNotifications when screen rotates - ios

I’m getting multiple UIKeyboardDidShowNotification and UIKeyboardDidHideNotification notifications when I rotate the device and I can’t figure out why.
I have an app that lets you edit the text for each picture. (It’s in a UITextView.) Most of the time I need to slide the text up so you can see it above the keyboard, then I slide it down when you are done editing. Then I do a database update to save the new text. I use notifications to tell me when the keyboard is displayed and when it goes away. It works fine on iPad when the user taps the keyboard close icon on the keyboard. It also works fine if the user swipes to the next page and iOS closes the keyboard. Since iPhones and iPods don’t have a keyboard close key, I wrote a method to close the keyboard when the picture or background is tapped. It works fine there too. However, when I rotate the device, I get multiple hide and show notifications. And I don’t know why.
Instead of getting one UIKeyboardDidHideNotification notification, I get a hide, a show, a hide, and then a show.
2:39:44.200 Picts for SLPs[16533:907] keyboardDidHide called. Keyboard showing flag is YES.
2:39:51.751 Picts for SLPs[16533:907] keyboardDidShow called. Keyboard showing flag is NO.
2:39:55.224 Picts for SLPs[16533:907] keyboardDidHide called. Keyboard showing flag is YES.
2:39:56.124 Picts for SLPs[16533:907] keyboardDidShow called. Keyboard showing flag is NO.
I posted the relevant code below. It is taken mostly from StackOverflow posts (Thanks guys).
In my class that displays the pictures I start notifications when it is initialized.
- (id)initWithParentView:(UIView *)parentview {
self = [super init];
if (self) {
_parentView = parentview;
if (ALLOW_DATABASE_EDITING) [self startNotifications];
}
return self;
}
- (void)startNotifications {
// Listen for keyboard appearances and disappearances
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardDidShow:)
name:UIKeyboardDidShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardDidHide:)
name:UIKeyboardDidHideNotification
object:nil];
}
The View Controller calls the hideKeyboard method in the View when the user taps on the picture.
- (void)dismissKeyboard {
if (self.showArtic.keyBoardIsShowing) {
[self.showArtic hideTheKeyboard];
}
}
resignFirstResponder sends a notification that closes the keyboard
- (void)hideTheKeyboard {
id <ShowArticDelegate> SA_delegate = _delegate;
// Don't update the database when there is no text.
if ( ![self.editableTextView.text isEqualToString:#""] ) {
[SA_delegate updateTextInDatabase:self.editableTextView.text];
}
[self.editableTextView resignFirstResponder];
}
These methods respond to the notifications.
- (void)keyboardDidHide:(NSNotification *)notification {
NSLog(#"keyboardDidHide called. Keyboard showing flag is %#.", self.keyBoardIsShowing ? #"YES" : #"NO");
self.keyBoardIsShowing = NO;
// Move the text, update the database
}
- (void)keyboardDidShow:(NSNotification *)notification {
NSLog(#"keyboardDidShow called. Keyboard showing flag is %#.", self.keyBoardIsShowing ? #"YES" : #"NO");
self.keyBoardIsShowing = YES;
// Move the text
}

any chance you could manually dismiss the keyboard and clean up your database from within this method:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
and then if you want the keyboard brought back up once the rotation completes, manually call your method to display it from within:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
That may or may not fit your needs, but it's a pretty common and clean technique to "get out of the way" while Apple handles the rotation, and then get back to business once everything is back on solid ground again.

Related

Enable UIButton after UIImagePicker is presented

I have tried everything or at least I think I have.
I am presenting a custom UIImagePicker with a shutter UIButton disable.
This UIButton is only enable when my view controller receives a notification telling that the camera is ready.
This method is being called; however, when I set the UIButton to enable nothing happen. The button is there because I can touch it but I cannot see it.
Here is a piece of code:
- (void)viewDidLoad
{
...
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(cameraIsReady:) name:AVCaptureSessionDidStartRunningNotification object:nil];
}
- (void)cameraIsReady:(NSNotificationCenter *)notigivation{
NSLog(#"Camera is ready");
[self.shutterButton setEnable:YES];
[self.shutterButton setNeedsDisplay]; //I tried with and without this method.
}

Why is UIKeyboardWillShowNotification called every time another TextField is selected?

I have a project that contains a UIScrollView and many UITextField inside it.
For the first time I select a UITextField, UIKeyboardWillShowNotification is called, which is fine. But whenever I select new UITextField (THE KEYBOARD IS STILL THERE), UIKeyboardWillShowNotification is called again !!!, which is weird.
I also set a symbolic breakpoint for [UIResponder resignFirstResponder] and I see that it is hit before and after UIKeyboardWillShowNotification is called !!!
The other thing is that UIKeyboardWillHideNotification is only called when I hit the "Done" button on the keyboard
I'm sure to not call any resignFirstResponder, becomeFirstResponder, endEditing anywhere. (I mean not call wrongly)
What can cause this problem ?
Here is the stacktrace
To workaround the problem, I used the following code to cancel the UIKeyboardWillShowNotification callback if the keyboard's frame is not changing.
func keyboardWillShow(notification: NSNotification) {
let beginFrame = notification.userInfo![UIKeyboardFrameBeginUserInfoKey]!.CGRectValue()
let endFrame = notification.userInfo![UIKeyboardFrameEndUserInfoKey]!.CGRectValue()
// Return early if the keyboard's frame isn't changing.
guard CGRectEqualToRect(beginFrame, endFrame) == false else {
return
}
...
}
For Swift 3/4:
func keyboardWillShow(notification: Notification) {
let userInfo = notification.userInfo!
let beginFrameValue = (userInfo[UIKeyboardFrameBeginUserInfoKey] as? NSValue)!
let beginFrame = beginFrameValue.cgRectValue
let endFrameValue = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)!
let endFrame = endFrameValue.cgRectValue
if beginFrame.equalTo(endFrame) {
return
}
// Do something with 'will show' event
...
}
The problem is I set inputAccessoryView for the UITextField, and this cause UIKeyboardWillShowNotification being called again when new UITextField is selected
This article Working With Keyboard on iOS explains this well
Additional changes take place when we connect an external keyboard to
the iPad. In this particular case, the notification behavior depends
on the inputAccessoryView property of the control which was the reason
for displaying the keyboard.
If inputAccessoryView is not present or its height is equal to 0
points, no keyboard notifications are sent. My guess is that this is
because in this case, no visual changes take place in application.
Otherwise, all notifications behave as expected – which means they are
being sent as in the majority of cases when the keyboard is displayed
or hidden in a normal (not undocked or split) state.
Whenever new UITextField is selected, the OS needs to compute the frame for the keyboard again, and the following notifications are posted
UIKeyboardWillChangeFrameNotification
UIKeyboardWillShowNotification
UIKeyboardDidChangeFrameNotification
UIKeyboardDidShowNotification
The same applies for when the TextField loses its first responder status
Note that using the same View for inputAccessoryView will cause UIKeyboardWillShowNotification only called once
In general I find that many things can cause spurious UIKeyboardWillShow and UIKeyboardWillHide notifications. My solution is to use a property to track whether the keyboard is already showing:
func keyboardShow(_ n:Notification) {
if self.keyboardShowing {
return
}
self.keyboardShowing = true
// ... other stuff
}
func keyboardHide(_ n:Notification) {
if !self.keyboardShowing {
return
}
self.keyboardShowing = false
// ... other stuff
}
Those guards block exactly the spurious notifications, and all is well after that. And the keyboardShowing property can be useful for other reasons, so that it is something worth tracking anyway.
The best approach is to Add notification & remove it once your purpose is solve.
like this .
- (void)viewWillAppear:(BOOL)animated
{
// register for keyboard notifications
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillShow)
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillHide)
name:UIKeyboardWillHideNotification
object:nil];
}
Now write your code for movement of views & textField in keyboardWillShow & revert them back to position in keyboardWillHide methods.
Also remove the observers
- (void)viewWillDisappear:(BOOL)animated
{
// unregister for keyboard notifications while not visible.
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIKeyboardWillHideNotification
object:nil];
}
You can also resign the responder when you press return key.
-(BOOL)textFieldShouldReturn:(UITextField *)textField {
[_txtFieldEmail resignFirstResponder];
[_txtFieldPassword resignFirstResponder];
return YES;
}
That should solve your issue.
For those not using inputAccessoryView but are still having problems, it may be due to using sensitive (password) fields. See this Stack Overflow post and answer: keyboardWillShow in IOS8 with UIKeyboardWillShowNotification
I have struggled with this, after half a day of searching around and experimenting I think this is the shortest and most reliable code. It is a mix of a number of answers most of which I forget where I found (parts of which are mentioned here).
My problem was a WKWebView which (when the user changed fields) would spawn a load of WillShow, WillHide, etc notifications. Plus I had problem with the external keyboard which still has the onscreen touch bar thing.
This solution uses the same animation code to "Open" and "Close" the keyboard, it will also cope with external keyboard being attached and custom keyboard views.
First register for the UIKeyboardWillChangeFrameNotification.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil];
Then you simply need to map the changes to your view in however you do it (change a hight or bottom constraint constant).
- (void)keyboardWillChangeFrame:(NSNotification *)notification
{
NSDictionary *userInfo = notification.userInfo;
CGRect keyboardEnd = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGRect convertedEnd = [self.view convertRect:keyboardEnd fromView:nil];
// Convert the Keyboard Animation to an Option, note the << 16 in the option
UIViewAnimationCurve keyAnimation = [userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue];
// Change the Height or Y Contraint to the new value.
self.keyboardHeightConstraint.constant = self.view.bounds.size.height - convertedEnd.origin.y;
[UIView animateWithDuration:[userInfo[UIKeyboardAnimationDurationUserInfoKey] floatValue]
delay:0.0
options:keyAnimation << 16
animations:^{
[self.view layoutIfNeeded];
} completion:nil];
}
The Animation to Option conversion seems to work (I can only find examples of it being used and not how/why), however, I am not convinced it will stay that way so it may be wise to use a "stock" option. It seems that the Keyboard uses some none specified Animation.

In iOS, how do I get keyboard information to prevent keyboard overlay of text fields?

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.

UIButton does not respond to removeFromSuperView when returning to VC

I have a UIButton that I implement programmatically as it is only required when I rotate the device.
The button needs to disappear when I go rotate and appear when I go landscape.
As long as I stay in the same ViewController I have no issues. I can rotate the device in anyway and the button appears and disappears as expected.
The app is a TabController based app and when I go to another tab the same behavior happens.
THIS IS THE PROBLEM
When I go back to the original view, the button appears, but then never disappears. It is almost like the removeFromSuperView is not being called, but even if it is, the button is not removed.
Any ideas why this is?
-(void)autoRotationDetection
{
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter]
addObserver:self selector:#selector(orientationChanged:)
name:UIDeviceOrientationDidChangeNotification
object:[UIDevice currentDevice]];
}
- (void) orientationChanged:(NSNotification *)note
{
UIDevice * device = note.object;
switch(device.orientation)
{
case UIDeviceOrientationPortrait:
/* start special animation */
[_menuButton removeFromSuperview];
break;
case UIDeviceOrientationPortraitUpsideDown:
/* start special animation */
break;
default:
break;
};
}
Then I call
-(void)viewWillAppear:(BOOL)animated
{
[self autoRotationDetection];
}
Sorry should have added that.
I've seen this kind of behavior (behaving differently after going to another tab and coming back again) when a call to [super viewWillAppear] is left out. Try adding that, and see if it fixes it.
After Edit:
I think an easier way to do this is to just look at the bounds of the view in viewWillLayoutSubviews, which is called every time there's a rotation (and other times as well, but for a simple thing like this, that shouldn't matter). In this example I'm hiding or showing rather than removing, but the concept should work for both.
-(void)viewWillLayoutSubviews {
BOOL portrait = self.view.bounds.size.height > self.view.bounds.size.width;
if (portrait) {
self.button.hidden = YES;
}else{
self.button.hidden = NO;
}
}
You could make this more efficient by keeping track of the value of portrait the last time this method was called, and only change the button's status if the portrait has changed.

Can UIWebView catche the event caused by pressing the MediaPlayer's done button

I want to use UIWebView to load URL and play vedios. When I press done button in MediaPlayer control on the UIWebView, I want to do something.
My Question is, in this case can it be OK, or does the UIWwebView has a delegate method to do after pressing done button?
The "Done" button only shows up in full screen mode. You can detect the end of full screen mode by observing the #"UIMoviePlayerControllerDidExitFullscreenNotification" mode:
- (void)viewDidLoad
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(moviePlayerDidExitFullscreen:)
name:#"UIMoviePlayerControllerDidExitFullscreenNotification"
object:nil];
}
- (void)viewDidUnload
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)moviePlayerDidExitFullScreen:(NSNotification *)notification
{
// This is where you do whatever you want because the user pressed "Done".
}
The UIMoviePlayerControllerDidExitFullscreenNotification is not documented, so I don't know if it will pass App Store review. If you're not planning to distribute via the App Store, it shouldn't matter.

Resources