iOS scrollView setContentOffset "shimmy" - ios

I have a scrollView with multiple textFields, which tracks the active field and makes sure it is visible when the keyboard pops up. It all works well, but when I tab from the 3rd to 4th textField, I get a little up and down "shimmy" before the textField ends up in the right place. Any suggestions?
-(void)keyboardDidShow:(NSNotification *)notification
{
if (keyboardIsShown)return;
NSDictionary* info=[notification userInfo];
// get keyboard size
CGSize keyboardSize=[[info objectForKey:UIKeyboardFrameBeginUserInfoKey]CGRectValue].size;
//Set scrollview insets to make room for keyboard
UIEdgeInsets contentInsets=UIEdgeInsetsMake(0.0, 0.0, keyboardSize.height, 0.0);
scrollView.contentInset=contentInsets;
scrollView.scrollIndicatorInsets=contentInsets;
//scroll the active text field into view
CGRect viewFrame=self.view.frame;
viewFrame.size.height-=keyboardSize.height;
int fieldHeight=self.currentTextField.bounds.size.height;
CGFloat navHeight=self.navigationController.navigationBar.frame.size.height;
CGPoint viewPoint=CGPointMake(0.0, self.currentTextField.frame.origin.y+fieldHeight);
if (!CGRectContainsPoint(viewFrame, viewPoint)) {
//scroll to make sure active field is showing
CGPoint scrollPoint=CGPointMake(0.0, viewPoint.y-keyboardSize.height+navHeight);//+navHeight
[scrollView setContentOffset:scrollPoint animated:YES];
}
}
-(void)showActiveField
{
//this makes sure that activeField shows when selecting another field after initial keyboard show
int fieldHeight=self.currentTextField.bounds.size.height;
CGPoint viewPoint=CGPointMake(0.0, self.currentTextField.frame.origin.y+fieldHeight);
CGRect viewFrame=self.view.frame;
int inset=scrollView.contentInset.bottom;
if (!CGRectContainsPoint(viewFrame, viewPoint)) {
//scroll to make sure active field is showing
CGPoint scrollPoint=CGPointMake(0.0, viewPoint.y-inset);
[scrollView setContentOffset:scrollPoint animated:YES];
}
}

Where do you set keyboardIsShown? Don't you want to do that Right after you check if it is already set?
And then: is the 4th field near the end of the scrollview and you have bounce scroll set?

Related

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.

textfield scrolls with code but returns immediately to original position

Using iPad split view controller:
(IBAction) textfieldEditingDidBeginAction:(id)sender{
CGPoint scrollPoint = CGPointMake(0.0, 40);
[_scroller setContentOffset:scrollPoint animated:YES];
}
The text field scrolls 40 but after the keyboard appears it returns to previous position. This does not happen with same code in iPhone portrait view. ?? Also this seems to only happen for the first text field, the next one works and returning to the first it then works.
The problem is with iPad in landscape view it auto adjusts for textfield after the above scrolling done. In order to scroll with differing amounts in iPad landscape orientation I used the following which works:
Boolean keyboardIsShown;
UITextField *currentTextField;
- (void)keyboardWasShown:(NSNotification*)aNotification){
if(keyboardIsShown)
return;
[self scrollipadTextField];
keyboardIsShown = yes;
}
-(IBAction) textfieldAEditingDidBegin:(id)sender{
currentTextField=sender;
if(keyboardIsShown)
[self scrollipadTextField];
}
-(void)scrollipadTextField{
int amount;
if([[UIDevice currentDevice] userInterfaceIdiom]==
userInterfaceIdiomPad &&([[UIApplication sharedApplication]
statusBarOrientation]==UIInterfaceOrientationLandscapeRight
||[[UIApplication sharedApplication] statusBarOrientation]==
UIInterfaceOrientationLandscapeLeft)){
if(currentTextField==_TextFieldA)
amount=70;
if(currentTextField==_TextFieldB)
amount=90;//etc.
CGPoint scrollPoint = CGPointMake(0.0, amount);
[_scroller setContentOffset:scrollPoint animated:YES];
}
}

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.

UITableView Not Scrolling after contentInset

i'm working on a project where i have a tableview and a uitextfield.
I'm applying the following method when the uitextfield gain/loose the focus :
-(void)enableInset {
CGFloat offSet = -30.0f;
UIEdgeInsets inset = UIEdgeInsetsMake(placesMapView.frame.size.height - offSet, 0.0f, 0.0f, 00.f);
// Updating the tableView position.
placesTableView.contentInset = inset;
placesTableView.contentOffset = CGPointMake(0.0f, -(placesMapView.frame.size.height - offSet));
placesTableView.scrollIndicatorInsets = inset;
}
and
- (void)disableInset {
CGFloat offset = self.navigationController.navigationBar.frame.size.height + [UIApplication sharedApplication].statusBarFrame.size.height;
UIEdgeInsets inset = UIEdgeInsetsMake(offset, 0.0f, 0.0f, 00.f);
placesTableView.contentInset = inset;
placesTableView.contentOffset = CGPointMake(0.0f, -offset);
placesTableView.scrollIndicatorInsets = inset;
}
The enableInset method is called in viewDidLayoutSubviews.
then when i call disableInset and enableInset, the UITableView can not be scrolled anymore.
What did i did wrong ? Any idea where i can look for some answer ?
EDIT :
If it can help, i added the project on github :
https://github.com/Loadex/PlaceViewer
To re-produce the bug :
Scroll the list, tap on the search bar, hit cancel, try to scroll again the list.
Weirdly click on the filter button, when the UIActionSheet is dismissed, the scroll is working again.
While looking for a solution to your problem i noticed that the bottom part of the contentInset of your placesTableView kept changing through the different states. It was 0 when in the initial state where you could see the map, and the tableView was behaving as expected. It got set to 216 when the keyboard came up after tapping the search field. I figured this was some automated communication between the tableView and the keyboard (through Notifications or something you did in PlacesQueryTableViewController). This is fine because we want the bottom inset to be set to the top of the keyboard when it appears. Now, here comes the buggy part. When I tapped the cancel button, the contentInset.bottom got set to -216.
I can't quite explain why this happens, but I suspect it has something to do with how that automatic change of the inset is implemented. I suspect that it does something like tableView.contentInset.bottom -= heightOfKeyboard, and that probably happens when the animation is finished, and not before. The source of your problem is that you change that bottom of contentInset before the animation is done, and thus before that automatic change has happened. So you're setting the bottom to 0 as soon as the user taps cancel. Then the system comes in and reduces it by the height of the keyboard, which turns out to be 216. That's what I think is happening anyway.
To fix this problem, avoid changing the bottom part of the contentInset and just change the top part. placesTableView.contentInset.top is readOnly, but if you do it like in the code below, you can get around that. I have just changed two lines of code in each method, the ones that have to do with the inset. Hopefully you see what I did.
-(void)enableInset {
NSLog(#"Enabling insets");
// Setting the tableView to overlay the map view
CGFloat offSet = [placestableViewController tableView:placestableViewController.tableView heightForRowAtIndexPath:nil] - 30.0f;
UIEdgeInsets inset = placesTableView.contentInset; // UIEdgeInsetsMake(placesMapView.frame.size.height - offSet, 0.0f, 0.0f, 0.0f);
inset.top = placesMapView.frame.size.height - offSet;
// Updating the tableView position.
placesTableView.contentInset = inset;
placesTableView.contentOffset = CGPointMake(0.0f, -(placesMapView.frame.size.height - offSet));
placesTableView.scrollIndicatorInsets = inset;
placesMapView.hidden = NO;
[placestableViewController loadObjects];}
- (void)disableInset {
NSLog(#"Disable insets");
CGFloat offset = self.navigationController.navigationBar.frame.size.height + [UIApplication sharedApplication].statusBarFrame.size.height;
UIEdgeInsets inset = placesTableView.contentInset;// UIEdgeInsetsMake(offset, 0.0f, 0.0f, 0.0f);
inset.top = offset;
placesTableView.contentInset = inset;
placesTableView.contentOffset = CGPointMake(0.0f, -offset);
placesTableView.scrollIndicatorInsets = inset;
// Hidding the map while in search
placesMapView.hidden = YES;}
.
BTW, if you want to know how I found the contentInset values at the different states, it's quite simple. What I did was to set myself as the delegate of placesTableView in - (void)viewDidLoad like this placesTableView.delegate = self;. I also had to change the #interfacestatement to #interface KrackMapViewController () <UITableViewDelegate> to say that we conform to the UITableViewDelegate. Now, here's the trick: UITableViewDelegate conforms to UIScrollViewDelegate. That means we can implement methods of the scroll view delegate. The one that is particularly interesting is this one:
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
NSLog(#"Did scroll with insetTop: %f, insetBottom: %f, contentOffset: %f", placesTableView.contentInset.top,
placesTableView.contentInset.bottom,
placesTableView.contentOffset.y);
}
That lets us know when you start dragging the tableView, and in there simply NSLog out the different parameters.
I hope this was helpful. Let me know if this works for you.
After a few research here is what I noticed:
The TableView when the keyboard is released is not scrolling because the tableview seems to believe that it is displayed on the entire screen. I tried to add more data in the tableview and we can see that the view is scrolling a little.
What I believe happened is that when the keyboard is hidden, some automatic calls are done and messing with what I set in my enableInset method. Here is my working solution:
I registered for the hideKeyboard event:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardDidHide:)
name:UIKeyboardDidHideNotification
object:nil];
and in the callback I called enableInset:
- (void)keyboardDidHide: (NSNotification *) notif{
[self enableInset];
}
And the view is scrolling back again.
Any explanation about this are welcome.

Strange Behavior in Scrolling UIScrollView to UITextView hidden by keyboard

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!

Resources