Got a keyboard will show notification, but keyboard window is nil - ios

I am a little bit confused. I am creating an App where user can swipe a scrollview horizontally between pages and every page has a textfield or a textview. I subscribe to UIKeyboardWillShowNotification and UIKeyboardWillHideNotification notifications and cards decrease their height when keyboard pops up and increase it back when keyboard hides.
Also when scrollView is dragged all textViews/textField resign first responder. That is how it looks like:
-(void)keyboardWillHide:(NSNotification*)notification{
// NSLog(#"notification user info = %#",notification.userInfo);
auto userInfo=notification.userInfo;
auto curve=[userInfo[UIKeyboardAnimationCurveUserInfoKey] unsignedIntegerValue];
auto duration=[userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
auto startFrame=[userInfo[UIKeyboardFrameBeginUserInfoKey] CGRectValue];
auto endFrame=[userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
auto dY=endFrame.origin.y-startFrame.origin.y;
[UIView beginAnimations:nil context:nil];
[UIView setAnimationCurve:(UIViewAnimationCurve)curve];
[UIView setAnimationDuration:duration];
_feedbackTextView.height+=dY;
_feedbackUserDataView.height+=dY;
[UIView commitAnimations];
}
-(void)keyboardWillShow:(NSNotification *)notification{
// NSLog(#"notification user info = %#",notification.userInfo);
auto userInfo=notification.userInfo;
auto curve=[userInfo[UIKeyboardAnimationCurveUserInfoKey] unsignedIntegerValue];
auto duration=[userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
auto startFrame=[userInfo[UIKeyboardFrameBeginUserInfoKey] CGRectValue];
auto endFrame=[userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
auto dY=endFrame.origin.y-startFrame.origin.y;
[UIView beginAnimations:nil context:nil];
[UIView setAnimationCurve:(UIViewAnimationCurve)curve];
[UIView setAnimationDuration:duration];
_feedbackTextView.height+=dY;
_feedbackUserDataView.height+=dY;
[UIView commitAnimations];
}
-(void)scrollViewDidScroll:(UIScrollView *)scrollView{
[_feedbackTextView resignFirstResponder];
[_feedbackUserDataView resignFirstResponder];
}
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
// NSLog(#"%s",sel_getName(_cmd));
CGFloat width = scrollView.frame.size.width;
NSInteger page = (scrollView.contentOffset.x + (0.5f * width)) / width;
switch(page){
case 0:{
[_feedbackTextView becomeFirstResponder];
}break;
case 1:{
[_feedbackUserDataView becomeFirstResponder];
}break;
}
}
-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
// NSLog(#"decelerate = %d",decelerate);
if(decelerate==NO){
CGFloat width = scrollView.frame.size.width;
NSInteger page = (scrollView.contentOffset.x + (0.5f * width)) / width;
switch(page){
case 0:{
[_feedbackTextView becomeFirstResponder];
}break;
case 1:{
[_feedbackUserDataView becomeFirstResponder];
}break;
}
}
}
_feedbackTextView is a subview on the first page of scrollView, _feedbackUserDataView is a subview on the second page of scrollView.
I tested my App. I scrolled, tapped textViews/textFields chaotically. And I got warning in my logs
|warning| Got a keyboard will show notification, but keyboard window is nil.
To my surprise there is nothing about this in google so I posted a question here. Sorry for ugly formatting.

This warning seems to be generated when the keyboard is dismissed immediately after being presented, before the animation has been completed.
In this case scrollViewDidEndDecelerating: can get called when the user starts panning again while the scroll view is still decelerating. Then immediately afterwards scrollViewDidScroll: gets called, which results in the keyboard being presented and immediately dismissed.
One fix would be to check the scrollView's panGestureRecogniser state and avoid making the text field first responder if it's in UIGestureRecognizerStateBegan
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
if ([scrollView panGestureRecognizer].state == UIGestureRecognizerStateBegan) {
return;
}
// NSLog(#"%s",sel_getName(_cmd));
CGFloat width = scrollView.frame.size.width;
NSInteger page = (scrollView.contentOffset.x + (0.5f * width)) / width;
switch(page){
case 0:{
[_feedbackTextView becomeFirstResponder];
}break;
case 1:{
[_feedbackUserDataView becomeFirstResponder];
}break;
}
}
This should remove most of the warnings. There may still be some if the user starts panning just as the keyboard is being presented. These would be harder to deal with - you'd need to make sure the keyboard has finished it's presentation animation before dismissing it by also subscribing to the UIKeyboardDidShowNotification and dismissing it there if the user is panning.

Related

How to move bottom textFields up when key board appears?

fields in my view, i want to move my text-view up when keyboard appears when i started entering text in 6th, 7th, 8th. text-Fields only.
I tried solution for you question and I simply got.
#define kOFFSET_FOR_KEYBOARD 80.0
#interface ViewController ()
{
NSInteger tagTF;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
-(void)setMovedUp:(BOOL)movedUP
{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.3];
CGRect rect = self.view.frame;
if(movedUP)
{
rect.origin.y -= kOFFSET_FOR_KEYBOARD;
rect.size.height += kOFFSET_FOR_KEYBOARD;
}
else{
rect.origin.y += kOFFSET_FOR_KEYBOARD;
rect.size.height -= kOFFSET_FOR_KEYBOARD;
}
self.view.frame = rect;
[UIView commitAnimations];
}
#pragma mark - UITextFieldDelegate Methods
-(BOOL)textFieldShouldBeginEditing:(UITextField *)textField
{
tagTF = textField.tag;
if(textField.tag>=6)
{
if(self.view.frame.origin.y >= 0)
{
[self setMovedUp:YES];
}
}
return YES;
}
-(BOOL)textFieldShouldReturn:(UITextField *)textField
{
if(textField.tag>=6)
{
[self setMovedUp:NO];
}
[textField resignFirstResponder];
return YES;
}
From my above answer I set the textField tag from 1 to 8.It works perfectly what you want.Please set the delegate for textField.
I have an extensive blog post explaining how to do this:
Shifting views to make room for the keyboard
The gist of it is that you add notification handlers for the keyboard will show and keyboard will hide notifications. In those handlers, you get the height of the keyboard, then do some math to figure out how much you need to move the field up so the keyboard doesn't cover it. You then move your view controller's view up just enough to keep the field exposed.
It gets more complicated than that, but that's all covered in the blog post (which is part of a larger blog post on animating random shapes that includes a working example of the keyboard technique.)
here is the official documentation by Apple regarding handling the keyboard.
There are lots of options for that.
I was using TPKeyboardAvoidingScrollView for a long time. Right now, i am using IQKeyboardManager much more simple and easy to integrate.
IQKeyboardManager
- (BOOL) textFieldShouldBeginEditing:(UITextField *)textField {
CGRect frame = self.view.frame;
Float y = frame.origin.y - 70;
frame.origin.y = y;
self.view.frame = frame;
return YES;
}
- (void) textFieldDidEndEditing:(UITextField *)textField{
CGRect frame = self.view.frame;
Float y = frame.origin.y + 70;
frame.origin.y = y;
self.view.frame = frame;
}
if you have change y value(like 70, 80, ...)in textFieldShouldBeginEditing method, and add same value to y in textFieldDidEndEditing method.
try this code.

Why view moves down while displaying keyboard?

I have a tab controller based app, where I have a uitextview on selecting which I m moving view up as keyboard appears , the entire thing works well until I switch tab , when I come back to this tab and try editing in uitextview view moves down and then back to normal position on keyboard appearing instead of moving up . here is the entire code
- (void)textViewDidBeginEditing:(UITextView *)textField
{
[self keyboardWillShow];
}
-(void)textViewDidEndEditing:(UITextView *)textField
{
[self keyboardWillHide];
}
-(void)keyboardWillShow {
// Animate the current view out of the way
if (self.view.frame.origin.y >= 0)
{
[self setViewMovedUp:YES];
}
else if (self.view.frame.origin.y < 0)
{
[self setViewMovedUp:NO];
}
}
-(void)keyboardWillHide {
if (self.view.frame.origin.y >= 0)
{
[self setViewMovedUp:YES];
}
else if (self.view.frame.origin.y < 0)
{
[self setViewMovedUp:NO];
}
}
//method to move the view up/down whenever the keyboard is shown/dismissed
-(void)setViewMovedUp:(BOOL)movedUp
{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.5]; // if you want to slide up the view
CGRect rect = self.view.frame;
if (movedUp)
{
// 1. move the view's origin up so that the text field that will be hidden come above the keyboard
// 2. increase the size of the view so that the area behind the keyboard is covered up.
rect.origin.y -= kOFFSET_FOR_KEYBOARD;
rect.size.height += kOFFSET_FOR_KEYBOARD;
}
else
{
// revert back to the normal state.
rect.origin.y += kOFFSET_FOR_KEYBOARD;
rect.size.height -= kOFFSET_FOR_KEYBOARD;
}
self.view.frame = rect;
[UIView commitAnimations];
}
Well I had similar issues and figured it out. You can check my recent post:
https://stackoverflow.com/a/28177645/2989374

iOS animate button while scrolling list

I have view with UITableView. Above this list I have UIButton. I need to implement an animation what will move button vertically according to scrolling on list and return to base location when scrolling is finished. I show scrolling down in pictures bellow:
This is not scrolled list:
This is list scrolled to half:
This is list fully scrolled:
For now I detect start and stop scrolling list events and do animation like that:
-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
CGRect screenRect = [[UIScreen mainScreen] bounds];
CGFloat screenWidth = screenRect.size.width;
CGRect newFrame = button.frame;
newFrame.origin.y = screenHeight - (newFrame.size.height/2.0);
[UIView animateWithDuration:2
animations:^{
button.frame = newFrame;
}];
}
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
CGRect newFrame = storedButtonFrame;
[UIView animateWithDuration:2
animations:^{
button.frame = newFrame;
}];
}
I have few issues with this First is that when I scrolling down my animation is animating to then end and I want to animate based on scrolled list eg. when I scroll down list for 10px I would like to move button only for 10px not to the end of list. Second is that I get stop scroll event when begin scroll animation is still running the start animation is blink to the end and the end animation is start animating what is definitly what I dont want. I dont have to much experiance in iOS and I really dont have any ide how can I do this. Have any idea how can I do something like this?
Instead of using scrollViewWillBeginDragging: you could use scrollViewDidScroll: like so:
/// Animate the button position as the scroll view scrolls
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGFloat percentage = scrollView.contentOffset.y / scrollView.contentSize.height;
CGRect frame = _button.frame;
frame.origin.y = scrollView.contentOffset.y + percentage * self.tableView.frame.size.height;
_button.frame = frame;
}
/// Animate the button back to the top-right corner
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
CGRect frame = _button.frame;
frame.origin.y = scrollView.contentOffset.y;
[UIView animateWithDuration:0.4f
animations:^{
_button.frame = frame;
}];
}
This makes the button follow the scroll indicator as you scroll the list. scrollViewDidScroll: fires very often so the movement is smooth.
When the list stops, it goes back to its default position at the top-right, which you get from contentOffset.y.

How to scroll to a certain point in a UIScrollView

Below I have a line of code that scrolls to a certain point in a UIScrollView. However once it has scrolled there it does not allow me to scroll back up.
[scroller setContentOffset:CGPointMake(0,500) animated:NO];
Any help will be appreciated, thank you.
did you try using UIScrollView method scrollRectToVisible:
Here is an example
- (void)goToPage:(int)page animated:(BOOL)animated
{
CGRect frame;
frame.origin.x = self.scroller.frame.size.width * page;
frame.origin.y = 0;
frame.size = self.scroller.frame.size;
[self.scroller scrollRectToVisible:frame animated:animated];
// update the scroll view to the appropriate page
}
I use this. The button calls this method. Should work well.
- (void)downExecute:(id)sender {
NSLog(#"scroll down");
CGFloat currentOffset = _scrollView.contentOffset.y;
CGFloat newOffset;
newOffset = currentOffset + self.view.bounds.size.height;
[UIScrollView animateWithDuration:0.3 animations:^(void) {
[_scrollView setContentOffset:CGPointMake(0.0, newOffset)];
} completion:^(BOOL finished) {
}];
}
I had this kind of problem because I called this method from viewDidLayoutSubviews(). Check where are you calling it from. When I moved the logic to viewDidAppear() everything worked as expected. Hope it helps, cheers.

UIScrollView issue on tapping UITextView or UITextField on keyboard presentation

I have a UIScrollView, which contains UITextFields and UITextViews. I have registered for the UIKeyboardDidChangeFrameNotification. When I tap on a text field or text view, the did change frame notification action is triggered and I adjust the contentOffset of the scroll view as shown below
- (void)keyboardDidChangeFrame:(NSNotification *)notification
{
CGRect keyboardEndFrame;
[[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] getValue:&keyboardEndFrame];
CGRect screenRect = [[UIScreen mainScreen] bounds];
CGRect intersection;
UIView *theFirstResponder = [[UIApplication sharedApplication].keyWindow findFirstResponder];
if ([theFirstResponder isKindOfClass:[UITextView class]]) {
if ([UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationLandscapeLeft) {
keyboardEndFrame = CGRectMake(keyboardEndFrame.origin.y, 416, keyboardEndFrame.size.height, keyboardEndFrame.size.width);
}
else if ([UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationLandscapeRight)
{
keyboardEndFrame = CGRectMake(keyboardEndFrame.origin.y, 0, keyboardEndFrame.size.height, keyboardEndFrame.size.width);
}
}
else
keyboardEndFrame = CGRectMake(keyboardEndFrame.origin.y, keyboardEndFrame.origin.x, keyboardEndFrame.size.height, keyboardEndFrame.size.width);
screenRect = CGRectMake(screenRect.origin.y, screenRect.origin.x, screenRect.size.height, screenRect.size.width);
if(CGRectEqualToRect(lastKBDRect, keyboardEndFrame)) {
return;
}
lastKBDRect = keyboardEndFrame;
if (CGRectIntersectsRect(keyboardEndFrame, screenRect)) {
// Keyboard is visible
//Convert Frame of the first responder, in context of the view that needs to be shifted
UIView *firstResponder = [[UIApplication sharedApplication].keyWindow findFirstResponder];
CGRect theRect = [firstResponder convertRect:firstResponder.frame toView:[UIApplication sharedApplication].keyWindow];
theRect = CGRectMake(theRect.origin.y, theRect.origin.x > 768 ? 750 : theRect.origin.x, theRect.size.height, theRect.size.width);
intersection = CGRectIntersection(keyboardEndFrame, theRect);
//If intersection is null, then no need to shift anything. Simply return.
if(CGRectIsNull(intersection)) {
return;
}
//Shift the view so that the first responder view is completely visible, keeping the constraint that the origin of the first responder view is also visible.
//Remember the current offset, so when we shift the view back, we shift it to the proper position.
if (!wasContentViewShifted) {
lastContentOffset = contentScrollView.contentOffset;
lastContentSize = contentScrollView.contentSize;
wasContentViewShifted = YES;
}
CGFloat offset = theRect.origin.y + theRect.size.height - keyboardEndFrame.origin.y;
if((theRect.origin.y - offset) < 40) {
offset += 42;
}
[UIView animateWithDuration:0.3f animations:^{
contentScrollView.contentOffset = CGPointMake(0, contentScrollView.contentOffset.y + offset);
contentScrollView.contentSize = CGSizeMake(0, lastContentSize.height + (600 - theRect.size.height));
}];
} else {
// Keyboard is hidden. Move the view back only if it was shifted.
if(wasContentViewShifted) {
wasContentViewShifted = NO;
[UIView animateWithDuration:0.3f animations:^{
contentScrollView.contentOffset = lastContentOffset;
contentScrollView.contentSize = lastContentSize;
}];
}
}
}
The application supports only landscape orientation.
The problems I'm facing here are
Tapping on textView presents the keyboard and textview is scrolled to the top if it is hidden by keyboard. Now, changing the orientation (landscape left to landscape Right) makes the textView scroll further to top and hence invisible.
The scrolling of TextView sometimes works for landscape left orientation but not for landscape right orientation and vice versa. This is because of the keyboardEndFrame values I'm using. Shouldn't I use the keyboardEndFrame origin values ? If not, what would be the alternative ?
Sometimes the it works for the textField but not for the textView.
Instead of having such complex solution you can try using this.
One simpler solution can also be found here.
Also it is not advisable to hard code the frames as you are doing. Refer to the apple documentation for more details.

Resources