Strange Behavior in Scrolling UIScrollView to UITextView hidden by keyboard - ios

I have looked at a number of posts here on scrolling and unhiding a UITextField and believed that the same code should work for a UITextView, but that seems not to be the case. The first issue I encountered was that the sample app I have is an iPad app supporting landscape orientation only. The keyboard size returned from the notification had the height and width of the keyboard reversed.
Next, while I can get the scrollview to scroll the textview, it does not reveal all of it and in fact the amount of the textview that is shown is dependent on where I tap in the textview. It is more like it is scrolling to where the cursor will be which is not what I want.
Here is the code I am using. It was taken from an example, the only real change is that a UITextView is used instead of a UITextField. If the only thing I do is to replace the textview with a textfield it works fine.
- (void)keyboardWasShown:(NSNotification*)aNotification
{
NSDictionary* info = [aNotification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.width, 0.0);
_myscrollview.contentInset = contentInsets;
_myscrollview.scrollIndicatorInsets = contentInsets;
CGRect aRect = self.view.frame;
aRect.size.height -= kbSize.width;
DebugLog(#"textview = %#", _textview);
if (!CGRectContainsPoint(aRect, _textview.frame.origin) ) {
CGPoint scrollPoint = CGPointMake(0.0, _textview.frame.origin.y-kbSize.width);
[_myscrollview setContentOffset:scrollPoint animated:YES];
}
}

If you are just wanting to scroll to the top of your UITextView you can do that by
[textview scrollRectToVisible:CGRectMake(0,0,1,1) animated:YES];
Try messing with the below method. Perhaps set it to the height of the textView. Can you post a screenshot of the issue?
- (void)textViewDidChangeSelection:(UITextView *)textView
{
[textView setSelectedRange:NSMakeRange(0, 0)];
}

Apple has provided the correct method here (check the Listing 4-1). It works for both UITextField and UITextView. We do not need to edit the range or anything else. It should be the scrollview that needs handling. The one marked as 'Answered' may work, but we better follow the creator's method!

Related

The height on my soft keyboard changes when triggered multiple times

I have an observed method that trigger when the soft keyboard is shown. It works fine, but for some reason the height of the soft keyboard changes after it has been hidden, then presented for the second time. I can't find a reason for this and there doesn't seem to be anything in the hide-delegate that changes its value. What causes this? I have worked around the problem by storing the height then using it the second time, but I would like to know the cause of this issue.
- (void)keyboardWasShown:(NSNotification*)aNotification
{
NSDictionary* info = [aNotification userInfo];
CGSize keyboardSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
CGRect visibleRect = self.view.frame;
if (_storedKeyboardHeight.size.height == 0) {
_storedKeyboardHeight.size.height = keyboardSize.height;
}
visibleRect.size.height = _storedKeyboardHeight.size.height;
visibleRect.origin.y = self.view.height - visibleRect.size.height;
CGRect rectOfCellInTableView = [self.loginTableView rectForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:1]];
//This changes the second time
NSLog(#"🦁 %f", keyboardSize.height);
//so I store the original value here
NSLog(#"🦁 %#", CGRectCreateDictionaryRepresentation(_storedKeyboardHeight));
if ((rectOfCellInTableView.origin.y + rectOfCellInTableView.size.height) > visibleRect.origin.y){
CGPoint scrollPoint = CGPointMake(0.0, (rectOfCellInTableView.origin.y + rectOfCellInTableView.size.height) - visibleRect.origin.y + 50);
[self.loginTableView setContentOffset:scrollPoint animated:YES];
}
}
First time the height is 291, second time it's 233.
The problem is that you are examining the wrong frame:
UIKeyboardFrameBeginUserInfoKey
When the keyboard is shown, what its frame height is at the beginning of the showing process is of no interest to you. What you want to know is the frame at the end of the showing process:
UIKeyboardFrameEndUserInfoKey
Also, it looks like you are getting notified of the wrong thing. You have not shown what notification you are registered for, but the name of your method, keyboardWasShown, suggests that your are getting notified when the keyboard did show. That's too late; this notification is almost never of any interest. You want to know when the keyboard will show.

Autoscrolling tableview content based on keyboard appearance

In my main view controller, I present a popup which is a UITableviewcontroller class and has a resizable textview as one of the cells. Now as the content grows and text view expands the typing content goes beyond the keyboard and it's not visible on the screen. In order to resolve this issue, I calculated the cursor position and keyboard position and based on that adjusted the tableviews content offset so that when typing starts the offset adjust to show the typing content above the keyboard. It seems to work but as per my logic now the issue is when there's a large content and if the cursor is at the bottom and if you scroll back to top and start typing while the cursor remains at bottom, it doesn't scroll to there right away as I have just adjusted a 20pt space to content offset. I'm not sure how to calculate the content offset of tableview based on the cursor point. Below is my code so far. Any help is appreciated.
-(void)adjustTextScroll:(UITextView *)textView
{
UITextRange *selectedTextRange = textView.selectedTextRange;
CGRect windowRect = CGRectZero;
if (selectedTextRange != nil)
{
CGRect caretRect = [textView caretRectForPosition:selectedTextRange.end];
windowRect = [textView convertRect:caretRect toView:nil];
}
//Checks if current cursor position is behind keyboard position
if (CGRectGetMinY(windowRect) > (keyboardYpos - 50)) // 50 added for space difference margin from keyboard
{
CGPoint contentOffset = self.tableView.contentOffset;
contentOffset.y += 20 ;
self.tableView.contentOffset = contentOffset;
}
}
//Keyboard notification
- (void)keyboardWasShown:(NSNotification *)notification
{
// Get the size of the keyboard.
CGSize keyboardSize = [[[notification userInfo] objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
CGFloat height = MIN(keyboardSize.height,keyboardSize.width);
CGFloat mainViewHeight = MAX([[UIScreen mainScreen]bounds].size.width,[[UIScreen mainScreen] bounds].size.height);
keyboardYpos = mainViewHeight - height;
}
- (void)textViewDidChange:(UITextView *)textView{
[self adjustTextScroll:textView]
}
Use TPKeyboardAvoiding and let this take care of all scroll issues. I myself have worked on quite a number of apps which uses this. Works like a charm.

HPGrowingTextView - "textInputView.frame.origin.y" is correct, but the text field actually not moved - sometimes

I'm debugging someone else's code - sorry I have very limited Objective-C experience.
#pragma mark - Keyboard events
- (void)keyboardWasShown:(NSNotification*)aNotification
{
NSDictionary* info = [aNotification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
NSLog(#"Keyboard a Notification user Info %#", info);
printf("keyboardWasShown \\\\\\\\\\\\\\\\\\\\\n");
NSLog(#"textInputView: y: (%.f), height: (%.f), kbHeight: (%.f)",
textInputView.frame.origin.y, textInputView.frame.size.height, kbSize.height);
[UIView animateWithDuration:2.0f animations:^{
if (keyboardNewLine){
bubbleTableFrame = originalBubbleTableFrame;
}
CGRect frame = originalTextViewFrame;
int difference = 0;
if (IsIphone5){
difference = -42;
frame.origin.y -= kbSize.height + difference;
} else {
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0f) {
difference = 42;
frame.origin.y -= kbSize.height + difference;
} else {
frame.origin.y -= kbSize.height + 110;
difference = 42;
}
}
textInputView.frame = frame;
frame = bubbleTableFrame;
frame.size.height -= kbSize.height + difference;
bubbleTable.frame = frame;
}];
NSLog(#"textInputView: y: (%.f), height: (%.f), kbHeight: (%.f)",
textInputView.frame.origin.y, textInputView.frame.size.height, kbSize.height);
printf("keyboardWasShown //////////\n");
[bubbleTable scrollBubbleViewToBottomAnimated:YES];
}
When I tap the text input field to bring up the keyboard, the field should move up to just above the keyboard - sometimes. It all depends on the which keyboard is loading first.
The text field without keyboard:
After tapping the text field, I expect:
However the text field stays at the bottom of the screen, leaving only the placeholder:
Strange enough, if I drag down the QuickType row, the text field suddenly appears!
But if I exit from this view and come back afterwards, once again the text field is stuck at the bottom (and guess what, it jumps up if I drag UP the QuickType row):
Switching to another keyboard (input method) by tapping the globe icon always helps to bring up the text field:
It seems that third-party keyboards are not affected:
You can see that I keep monitoring textInputView.frame.origin.y to see what goes wrong. It changes from 472 (no keyboard) to 261 (English keyboard) but the text field simply doesn't go up.
I don't think it is behind the placeholder (if any) because when I tap the white area beside the bubbles, the keyboard moves down and unveils the text field stuck at the bottom.
Someone suggested adding setNeedsDisplay, setNeedsLayout, etc. but none helped.
Thanks in advance.
Eventually I turned off Auto Layout as suggested by someone:
https://www.facebook.com/groups/cocoahk/permalink/1104922892867587/
It worked. Though I needed to adjust the positions of all other objects.

Move UITextField up without moving whole view up

I have a UITableView inside a UIScrollView. The tableView does not span the length of the view as I have a UITextView at the bottom of the view (to enter messages) so the table ends at the textview. However, The scrollView does span the entire length of the view. When the keyboard comes up, I move the entire view up with it,(so nothing is hidden) but that makes the top cells of the table inaccessible (as in, I don't need to see the cells, but when i try to scroll to them I can't get to them) as it is pushing the whole view up. I am wondering if instead, I can push the scrollview underneath up, in turn having it push the keyboard up with it, and then have the tableview stay in place and scroll to the proper cell (for me in this case it is the most recent message, or last cell in the table). Having the tableview stay in place will allow scrolling access to all the cells in the table. Am I going about this the right way or is there an easier and better way to accomplish this? I'm working on a messaging app, and I want the UI to behave in a similar way to the native messages app. When the keyboard comes up, it brings the last message up above the top of the keyboard, but when the keyboard is visible you can still scroll to the top of the table. I will post my code for the movement of the view.
//call move view method and scroll to last cell in table
-(void) keyboardWasShown:(NSNotification*)aNotification
{
NSLog(#"Keyboard was shown");
[self moveView:[aNotification userInfo] up:YES];
[self.chatTable scrollRectToVisible:CGRectMake(0, self.chatTable.contentSize.height - self.chatTable.bounds.size.height, self.chatTable.bounds.size.width, self.chatTable.bounds.size.height) animated:YES];
}
-(void) keyboardWillHide:(NSNotification*)aNotification
{
NSLog(#"Keyboard will hide");
[self moveView:[aNotification userInfo] up:NO];
}
- (void)moveView:(NSDictionary*)userInfo up:(BOOL)up
{
CGRect keyboardEndFrame;
[[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey]getValue:&keyboardEndFrame];
UIViewAnimationCurve animationCurve;
[[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey]
getValue:&animationCurve];
NSTimeInterval animationDuration;
[[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey]
getValue:&animationDuration];
// Get the correct keyboard size to we slide the right amount.
[UIView beginAnimations:nil context:nil];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:animationDuration];
[UIView setAnimationCurve:animationCurve];
CGRect keyboardFrame = [self.view convertRect:keyboardEndFrame toView:nil];
int y = keyboardFrame.size.height * (up ? -1 : 1);
self.view.frame = CGRectOffset(self.view.frame, 0, y);
[UIView commitAnimations];
}
What I would do is NOT move the whole view up, but just shrink the scrollView's height by y amount.
So instead of
self.view.frame = CGRectOffset(self.view.frame, 0, y);
you could
CGRect scrlRect = self.myScrollView.frame;
self.myScrollView.frame = CGMakeRect(scrlRect.origin.x, scrlRect.origin.y, scrlRect.size.width, scrlRect.size.height+y);
The first step in dealing with this is modify the insets on the table. This gist would help you ensure that the tableView (or scrollview) scrollable area is no longer covered by the keyboard: https://gist.github.com/TimMedcalf/9505416
It may reveal further issues, but that's always my first port of call.
In your keyboarWasShown method wright below code:
- (void)keyboardWillShow:(NSNotification *)notification
{
NSDictionary *info = [notification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
float kbHeight = kbSize.width > kbSize.height ? kbSize.height : kbSize.width;
self.keyboardHeight.constant = kbHeight+5;
}
Now in your textFieldShouldBeginEditing delegate method of UITextField write below code:
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField
{
CGPoint point = [textField.superview convertPoint:textField.frame.origin toView:yourTableViewHere];
NSIndexPath *indexPath = [tblAsset indexPathForRowAtPoint:point];
CGRect tableViewVisibleRect = [tblAsset rectForRowAtIndexPath:indexPath];
if (point.y > (tableViewVisibleRect.size.height - self.keyboardHeight.constant))
{
CGPoint contentOffsetPoint = tblAsset.contentOffset;
contentOffsetPoint.y -= self.keyboardHeight.constant;
CGSize contectSize = tblAsset.contentSize;
contectSize.height += self.keyboardHeight.constant;
[tblAsset setContentSize:contectSize];
[tblAsset setContentOffset:contentOffsetPoint animated:NO];
}
}
Above is from my current project and its working somewhat like what you want. Using this way may help you achive your task.

Take CGRect out of UITextView

This must sound really weird but I want to take the keyboard's CGRect and take it out of the UITextView. Here is a picture to help:
I basically want the UITextView to become smaller so when I add text it scrolls down. If you need more info ask me.
EDIT: When I use this code:
- (void)keyboardWasShown:(NSNotification*)notification {
NSDictionary* info = [notification userInfo];
kbSIZE = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue];
CGRect newTextViewFrame = self.notesTextView.frame;
newTextViewFrame.size.height -= kbSIZE.size.height;
self.notesTextView.frame = newTextViewFrame;
}
this happens:
Not that weird at all. The same thing often happens with tableviews when you are bringing up a keyboard. This bit of code should help.
CGRect newTextViewFrame = textView.frame;
newTextFrame.size.height -= keyboardSize.frame.size.height;
textView.frame = newScrollFrame;
This is not weird at all :)
You can do this by registering as an observer to the UIKeyboardWillShowNotification (it would be a good idea to also register for the UIKeyboardWillHideNotification in the same process).
When the registered selector will be called, the notification parameter will contain the keyboard's size inside the UIKeyboardFrameEndUserInfoKey value of the userInfo dictionnary :
[[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size
All you need to do now is to set the frame of your UITextView to your desired size.
Be warned : the CGRect you get does not take into account the device orientation. This means, if the device is in fact in landscape mode, you'll need to switch the width and height of the CGRect for it to make sense in your context.

Resources