Disable UIScrollView scrolling when UITextField becomes first responder - ios

When a UITextField embedded in a UIScrollView becomes the first responder (for example, by the user typing in some character), the UIScrollView scrolls to that Field automatically. Is there any way to disable that?
Duplicate rdar://16538222

Building on Moshe's answer... Subclass UIScrollView and override the following method:
- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated
Leave it empty. Job done!
In Swift:
class CustomScrollView: UIScrollView {
override func scrollRectToVisible(_ rect: CGRect, animated: Bool) { }
}

I've been struggling with the same problem, and at last I've found a solution.
I've investigated how the auto-scroll is done by tracking the call-trace, and found that an internal [UIFieldEditor scrollSelectionToVisible] is called when a letter is typed into the UITextField. This method seems to act on the UIScrollView of the nearest ancestor of the UITextField.
So, on textFieldDidBeginEditing, by wrapping the UITextField with a new UIScrollView with the same size of it (that is, inserting the view in between the UITextField and it's superview), this will block the auto-scroll. Finally remove this wrapper on textFieldDidEndEditing.
The code goes like:
- (void)textFieldDidBeginEditing:(UITextField*)textField {
UIScrollView *wrap = [[[UIScrollView alloc] initWithFrame:textField.frame] autorelease];
[textField.superview addSubview:wrap];
[textField setFrame:CGRectMake(0, 0, textField.frame.size.width, textField.frame.size.height)];
[wrap addSubview: textField];
}
- (void)textFieldDidEndEditing:(UITextField*)textField {
UIScrollView *wrap = (UIScrollView *)textField.superview;
[textField setFrame:CGRectMake(wrap.frame.origin.x, wrap.frame.origin.y, wrap.frame.size.width, textField.frame.size.height)];
[wrap.superview addSubview:textField];
[wrap removeFromSuperview];
}
hope this helps!

I had the same issue with disabling auto-scrolling of a UITextView being a cell of UITableView. I was able to resolve it using the following approach:
#interface MyTableViewController : UITableViewController<UITextViewDelegate>
#implementation MyTableViewController {
BOOL preventScrolling;
// ...
}
// ... set self as the delegate of the text view
- (void)textViewDidBeginEditing:(UITextView *)textView {
preventScrolling = YES;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (preventScrolling) {
[self.tableView setContentOffset:CGPointMake(0, -self.tableView.contentInset.top) animated:NO];
}
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
preventScrolling = NO;
}
Defining scrollViewWillBeginDragging is used for restoring the default scrolling behaviour, when the user himself initiates scrolling.

As Taketo mentioned, when a UITextField is made first responder, its first parent view that is of type UIScrollView (if one exists) is scrolled to make that UITextField visible. The easiest hack is to simply wrap each UITextField in a UIScrollView (or ideally, wrap all of them in a single dummy UIScrollView). This is very similar to Taketo's solution, but it should give you slightly better performance, and it will keep your code (or your interface in Interface Builder) much cleaner in my opinion.

Building on Luke's answer, to handle the issue that his solution completely disables auto-scroll, you can disable it selectively as follows:
// TextFieldScrollView
#import <UIKit/UIKit.h>
#interface TextFieldScrollView : UIScrollView
#property (assign, nonatomic) IBInspectable BOOL preventAutoScroll;
#end
#implementation TextFieldScrollView
- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated {
if (self.preventAutoScroll == NO) {
[super scrollRectToVisible:rect animated:animated];
}
}
#end
This way, you can completely set it up in Interface Builder to disable the auto-scroll, but have full control at any time to re-enable it (though why you'd want to is beyond me).

It looks like UIScrollview which contains UITextfield, auto adjusts its content offset; when textfield is going to become first responder.
This can be solved by adding textfield in scrollview of same size first, and then adding in to main scroll view. instead of directly adding in to main scrollview
// Swift
let rect = CGRect(x: 0, y: 0, width: 200, height: 50)
let txtfld = UITextField()
txtfld.frame = CGRect(x: 0, y: 0, width: rect.width, height: rect.height)
let txtFieldContainerScrollView = UIScrollView()
txtFieldContainerScrollView.frame = rect
txtFieldContainerScrollView.addSubview(txtfld)
// Now add this txtFieldContainerScrollView in desired UITableViewCell, UISCrollView.. etc
self.mainScrollView.addSubview(txtFieldContainerScrollView)
// Am33T

This is the way I do it:
It is very simple, you get to return your own contentOffset for any scrollRectToVisible.
This way you are not harming the normal behaviour and flow of things - just providing the same functionality in the same channel, with your own improvements.
#import <UIKit/UIKit.h>
#protocol ExtendedScrollViewDelegate <NSObject>
- (CGPoint)scrollView:(UIScrollView*)scrollView offsetForScrollingToVisibleRect:(CGRect)rect;
#end
#interface ExtendedScrollView : UIScrollView
#property (nonatomic, unsafe_unretained) id<ExtendedScrollViewDelegate> scrollToVisibleDelegate;
#end
#import "ExtendedScrollView.h"
#implementation ExtendedScrollView
- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated
{
if (_scrollToVisibleDelegate && [_scrollToVisibleDelegate respondsToSelector:#selector(scrollView:offsetForScrollingToVisibleRect:)])
{
[self setContentOffset:[_scrollToVisibleDelegate scrollView:self offsetForScrollingToVisibleRect:rect] animated:animated];
}
else
{
[super scrollRectToVisible:rect animated:animated];
}
}
#end

I've tried #TaketoSano's answer, but seems not works.. My case is that I don't have a scrollview, just a view with several text fields.
And finally, I got a workaround. There're two default notification names for keyboard that I need:
UIKeyboardDidShowNotification when the keyboard did show;
UIKeyboardWillHideNotification when the keyboard will hide.
Here's the sample code I used:
- (void)viewDidLoad {
[super viewDidLoad];
...
NSNotificationCenter * notificationCetner = [NSNotificationCenter defaultCenter];
[notificationCetner addObserver:self
selector:#selector(_keyboardWasShown:)
name:UIKeyboardDidShowNotification
object:nil];
[notificationCetner addObserver:self
selector:#selector(_keyboardWillHide:)
name:UIKeyboardWillHideNotification
object:nil];
}
- (void)_keyboardWasShown:(NSNotification *)note {
[self.view setFrame:(CGRect){{272.f, 55.f}, {480.f, 315.f}}];
}
- (void)_keyboardWillHide:(NSNotification *)note {
[self.view setFrame:(CGRect){{272.f, 226.5f}, {480.f, 315.f}}];
}
Here, the (CGRect){{272.f, 226.5f}, {480.f, 315.f}} is view's default frame when keyboard is hidden. And (CGRect){{272.f, 55.f}, {480.f, 315.f}} is view's frame when keyboard did show.
And b.t.w., the view's frame changing will be applied animation automatically, this's really perfect!

I have a collection view with a text field at the very top, mimicking the UITableView.tableHeaderView. This text field is located in the negative content offset space so that it doesn't interfere with the rest of the collection view. I basically am detecting whether or not the user is performing the scrolling in the scroll view and whether or not the text field is first responder and if the scroll view is being scrolled beyond the top of the scroll view's content inset. This exact code won't necessarily help anyone but they could manipulate it to fit their case.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
// This is solving the issue where making the text field first responder
// automatically scrolls the scrollview down by the height of the search bar.
if (!scrollView.isDragging && !scrollView.isDecelerating &&
self.searchField.isFirstResponder &&
(scrollView.contentOffset.y < -scrollView.contentInset.top)) {
[scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x, -scrollView.contentInset.top) animated:NO];
}
}

I don't know of any property of UIScrollView that would allow that. It would be poor user experience to be able to disable that, IMHO.
That said, it may be possible to subclass UIScrollView and override some of its methods to check that the UITextfield is not a first responder before scrolling.

An easier way to stop the scrollview scrolling when you select a textField is in your viewController::viewWillAppear() DO NOT call [super viewWillAppear];
You can then control the scroll as you wish.

Related

How can I do this with UIScrollView?

I want the effect like this:
When I scroll the word from "image1" to "image2" then the background also change, I just don't know how to set the word detection(the line).
#interface myViewController : UIViewController<UIScrollViewDelegate>
#end
- (void)viewDidLoad
{
[super viewDidLoad];
UIScrollView *myscrollView=[[UIScrollView alloc] initWithFrame: CGRectMake(0,0,320,460)];
myscrollView.delegate = self;
CGPoint position = CGPointMake(100, 150);
[myscrollView setContentOffset:position animated:YES];
}
-(void) scrollViewDidScroll:(UIScrollView *) scrollView{
}
-(void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
}
Use the scrollViewDidScroll method, and inside call yourScrollView.contentOffset to check how much you scrolled; then make the necessary changes.
Another way, is by checking if the frame of the scrollView intersects the frame of the label, using CGRect's intersects function inside of scrollViewDidScroll. This might be the easier one.
Check the documentation for scrollViewDidScroll if you need more info.

TextField attach to keyboard while editing - how to make the ScrollView appear same as before editing?

I created new Xcode single view project. In interface builder, I added a UIScrollView to cover complete view. Over this scrollview, I added a UITextField. The resulting UI looked like this:
Note that the scrollview does not scroll at this point as the content takes only the size of view and not greater than it.
Now for bringing UITextField on top of keyboard while editing, I followed the way described by Apple on Managing The Keyboard page. After doing this, it gave me expected behavior which brought the text field right above keyboard on editing begin as shown in following screenshot:
Now, after calling [textfield endEditing:YES], the keyboard hides itself, but the textfield does not return to its original place. It return to the place just little above its original place and now the scroll view becomes scrollable as if little height was added to it:
Notice the scroll bar in the above screenshot!
I want help in bring back the original behavior of the view after editing ends (when keyboard hides) i.e. the textField to return to exact same place and scroll should be happening as it was not happening before editing begin.
Project URL: - https://github.com/therohansanap/keyboard.git
You need adjust scrollview contentOffset textFieldDidBeginEditing and textFieldDidEndEditing.
or
One controller is available for scrollview auto scroll.
https://github.com/simonbs/BSKeyboardControls
I think the official way specified by Apple here is easiest and best way to keep this functionality working.
You can do something similar without Using Keyboard notifications as well. As you may know that we have TextField delegate methods , we can use those to set scrollView contentOffset and acquire the same behaviour
- (void)textFieldDidBeginEditing:(UITextField *)textField{
scrollView.contentOffset = CGPointMake(0, textField.center.y-80); // you can change 80 to whatever which fits your needs
}
the above method sets contentOffset Value of scroll view and your textFiled moves up, while the textField resignFirstResponder the below delegate method gets called, where you can set back the contentOffset value
- (void)textFieldDidEndEditing:(UITextField *)textField{
scrollView.contentOffset = CGPointMake(0,-80);
}
Note: you need to make every text field in your view to have their delegate as your UIViewController instance. You also need need your UIViewController to adopt UITextFieldDelegate
Took a look at your code, you dont need to change the content insets etc when trying to position the scroll view. You just need to modify the content offset property.
Here is the modified code :
#interface ViewController () {
UITextField *activeField;
CGPoint scrollViewOldPosition;
}
Modify the keyboardWasShow as follows :
// Called when the UIKeyboardDidShowNotification is sent.
- (void)keyboardWasShown:(NSNotification*)aNotification
{
NSDictionary* info = [aNotification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
CGFloat someSpaceBetweenKeyBoardAndField = 20.0;
scrollViewOldPosition = self.scrollView.contentOffset;
self.scrollView.contentOffset = CGPointMake(0, kbSize.height - (self.view.frame.size.height - activeField.frame.origin.y - activeField.frame.size.height) + someSpaceBetweenKeyBoardAndField);
}
Keyboard will be hidden method :
// Called when the UIKeyboardWillHideNotification is sent
- (void)keyboardWillBeHidden:(NSNotification*)aNotification
{
self.scrollView.contentOffset = scrollViewOldPosition;
}
Not the best code ever but it have some more features u can use, anything with _ is global variable
//Handle notification when keyboard appear
- (void)keyboardOnScreen:(NSNotification *)notification
{
if (_isKeyboardShow) {
return; //If keyboard is showing then return
}
_keyboardHeight = [[notification.userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height;
[self animateTextFieldUp: YES];
_isKeyboardShow = YES;
}
//Handle notification when keyboard hide
- (void)keyboardOffScreen:(NSNotification *)notification
{
if(!_isKeyboardShow) return;
[self animateTextFieldUp: NO];
_isKeyboardShow = NO;//Missed this line
}
//Push view up with animation when keyboard show
- (void) animateTextFieldUp: (BOOL) up
{
UITextField *textfield = [UIResponder currentFirstResponder];
CGPoint windowPoint = [textfield convertPoint:textfield.bounds.origin toView:self.view];
int movementDistance;
CGPoint point = [_mainScrollView contentOffset];
//Push up only when blocked by keyboard
if (windowPoint.y + textfield.frame.size.height >= self.view.frame.size.height - _keyboardHeight) {
movementDistance = windowPoint.y - (self.view.frame.size.height - _keyboardHeight) + textfield.frame.size.height + 10;
_oldMovementDistance = movementDistance;
int movement = (up ? -movementDistance : movementDistance);
[_mainScrollView setContentOffset:CGPointMake(0, point.y - movement) animated:YES];
}
else { //Push view down the same amount
int movement = (up ? -movementDistance : _oldMovementDistance);
[_mainScrollView setContentOffset:CGPointMake(0, point.y - movement) animated:YES];
_oldMovementDistance = 0;
}
}
move UITextField and UITextView out of the way of the keyboard during editing:
For non-UITableViewControllers, drop the TPKeyboardAvoidingScrollView.m and TPKeyboardAvoidingScrollView.h source files into your project, pop a UIScrollView into your view controller's xib or storyboard, set the scroll view's class to TPKeyboardAvoidingScrollView, and put all your controls within that scroll view. You can also create it programmatically, without using a xib - just use the TPKeyboardAvoidingScrollView as your top-level view.
For use with UITableViewController classes, drop TPKeyboardAvoidingTableView.m and TPKeyboardAvoidingTableView.h into your project, and make your UITableView a TPKeyboardAvoidingTableView in the xib. If you're not using a xib with your controller, I know of no easy way to make its UITableView a custom class: The path of least resistance is to create a xib for it.
You can get reference from here.
Hope this helps.

How to reset my UIScrollView's position after returning from a modal transition?

I have a simple view containing a long view with many buttons, with the whole thing being in a UIScrollView. The scroller works well, and I can scroll to the bottom and click a button. Every button triggers a modal seque to another view. That new view is then dismissed by user interaction, causing the original UIScrollView's view to load again.
Here's the problem: If I click on a button toward the top of the UIScrollView, I enter the modal segue, dismiss the new view, and return to the UIScrollView's view without a problem. But, if I click on one of the buttons toward the bottom of the UIScrollView, when I return seque out and then transition back, my scrolling is all messed up. I can only see the area beneath my scroller, and can't scroll back up to the top anymore!
I'm pretty sure there must be some way to reset the UIScrollView's starting and ending points upon ViewWillAppear, but I can't figure it out. Any help is appreciated!
Also, FYI, I simply added the UIScrollView through interface builder, and haven't implemented or synthesized it anywhere yet.
try this code:
- (void)viewWillAppear:(BOOL)animated
{
[yourscrollview setContentOffset:CGPointZero animated:YES];
}
Please note: the bug this question and answer is about appears to be fixed in iOS 7. The rest of this answer is only relevant to iOS 6 (and probably earlier).
The behaviour being exhibited here is a bug in the UIScrollView class. As noted by the OP, after returning from a modally presented UIViewController to a scene containing a UIScrollView, the UIScrollView takes whatever point it's currently scrolled to and starts behaving as though that is its origin. That means that if you'd scrolled down your scroll view before modally presenting another View Controller, you can't scroll back up upon returning to the scene with the scroll view.
The same thing happens when you remove the Scroll View from the view hierarchy and re-add it, even without changing its window.
You can work around this by setting the contentOffset of the scroll view back to {0,0} before it gets displayed again after dismissing the modal View Controller. If you actually want to preserve the point the user had scrolled to before they triggered the modal, then after the UIScrollView is redisplayed you can set the contentOffset back to whatever it was before you reset it.
Here's a UIScrollView subclass that fixes the bug without resetting the scroll view to the top whenever you return from a modal:
#interface NonBuggedScrollView : UIScrollView
#end
#implementation NonBuggedScrollView {
CGPoint oldOffset;
}
-(void)willMoveToWindow:(UIWindow *)newWindow {
oldOffset = self.contentOffset;
self.contentOffset = CGPointMake(0,0);
}
-(void)willMoveToSuperview:(UIView *)newSuperview {
oldOffset = self.contentOffset;
self.contentOffset = CGPointMake(0,0);
}
-(void)didMoveToWindow {
self.contentOffset = oldOffset;
}
-(void)didMoveToSuperview {
self.contentOffset = oldOffset;
}
#end
If you'd rather do this in a UIViewController than in a UIScrollView subclass, change the content offset in the viewWillAppear: and viewDidAppear methods.
If you don't want to preserve where the user's scroll position when they return from a modal, and just want to scroll the UIScrollView back to the top, as the OP asked for, then all you need is the even simpler:
#interface NonBuggedScrollView : UIScrollView
#end
#implementation NonBuggedScrollView
-(void)willMoveToWindow:(UIWindow *)newWindow {
self.contentOffset = CGPointMake(0,0);
}
-(void)willMoveToSuperview:(UIView *)newSuperview {
self.contentOffset = CGPointMake(0,0);
}
#end
First, thanks for the approved answer above. Someone mentioned that it was no longer applicable but I have a scrolling view inside of table view cell and it needs to be reset when the cell is reused.
Here is the solution in Swift.
#IBOutlet var scrollView: UIScrollView!
// many lines of code later inside a function of some sort...
scrollView.setContentOffset(CGPointMake(0.0, 0.0), animated: false)
To solve this problem i use this code:
-(void) viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[self.scrollview scrollRectToVisible:CGRectMake(0, 0, 1, 1)
animated:NO];
}
You can change the starting and ending points by calling scrollRectToVisible:animated:. But I'm not sure if this fixes your problem.
Use below code snippet to restore the scroll position for a UIScrollview
Declare "scrollPosition" variable as CGPoint type.
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
//get the current offset
scrollPosition = scrollView.contentOffset;
//set current view to the beginning point
self.scrollView.contentOffset = CGPointZero;
}
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
//retrieve the previous offset
self.scrollView.contentOffset = scrollPosition;
}

UITextView inside a UIScrollView inside a Popover is not fully visible when keyboard is displayed

I have a UIPopover with a UIScrollViewinside it that contains a UITextView at the bottom. When the keyboard shows, the popover is resized as the text view begins to be edited. I want the code below to ensure the text view is visible:
- (void)textViewDidBeginEditing:(UITextView *)textView {
CGRect visRect = textView.frame;
[self.scrollView scrollRectToVisible:visRect animated:NO];
}
The problem is that the code does not make the entire text view visible. Instead, only the text view up to the bottom of the cursor is shown, as shown below:
How can I show the entire text view/ scroll the scrollview to the bottom? I have tried this:
CGPoint bottomOffset = CGPointMake(0, self.scrollView.contentSize.height - self.scrollView.bounds.size.height);
[self.scrollView setContentOffset:bottomOffset animated:YES];
as explained in this answer but nothing works.
In addition, my scrollview is scrolled to the position shown AFTER the keyboard is moved into place. Ideally I'd like the scrolling to happen before or during the keyboard movement.
Any help would be great.
#hey Dave
This By default Case in of UIPopOverController, whenever we used UIPopOverControler to display any PopUpView and in suppose that PopUpView height as large as can be covered by KeyBoard then in that case that PopOverView Automatically gets Shrink itself.as you resign keyboard that PopUpView will expand itself automatically.I have faced same case.
This is just my opinion , you can change the origin of CurrentView(parentView of PopUpView) as keyboard going to display/hide so that PopUpView could display itself properly from and could get the appropriate space.
See Below Are the Delegate methods of UITextView Responds Back as editing start and end.
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView
{
//chnage the OriginY of PopUpView's SUperView
}
- (void)textViewDidBeginEditing:(UITextView *)textView
{
//re Adjust the OriginY of PopUpView's SUperView
}
I hope it may helpful to you.
I found the solution:
- (void)keyboardDidShow:(NSNotification *)notification {
NSLog(#"Notification: %s", __PRETTY_FUNCTION__ );
//
CGFloat textviewBottom = CGRectGetMaxY(self.commentsTextView.frame);
CGRect belowTextViewRect = CGRectMake(0, textviewBottom, 350.f, self.scrollView.contentSize.height - textviewBottom);
// NB! This works ONLY: 1) keyboardDidShow 2) Non-animated;
// Does NOT work: 1) animated, 2) keyboardWillShow, 3) textViewDidBeginEditing
[self.scrollView scrollRectToVisible:belowTextViewRect animated:NO];
}

Hiding a toolbar element when UITableView scrolls (similar to Facebook's app?)

How I can achieve this effect?
This isn't immediately noticeable from your screenshots, but I believe you want the that header toolbar to slide up as the user scrolls, right? (I'd suggest clarifying on that part)
You can do this a few ways, and in all of them you will have to implement your own scrolling logic, meaning how much the header toolbar slides up depending on where you have scrolled. That said, here's how to do it:
1. If you're using UITableView, I assume you've got your view controller set as its delegate. Since UITableView is a subclass of UIScrollView already, just add the UIScrollViewDelegate to your view controller. That will give us scroll events as they happen. You'll want to do your logic in scrollViewDidScroll:.
2.. If you're simply using UIScrollView, just set your view controller as its delegate, implement UIScrollViewDelegate, and do your logic in scrollViewDidScroll:.
That said, your code might look something like this:
- (void) scrollViewDidScroll:(UIScrollView *)scrollView {
CGPoint scrollPos = scrollView.contentOffset;
if(scrollPos.y >= 40 /* or CGRectGetHeight(yourToolbar.frame) */){
// Fully hide your toolbar
} else {
// Slide it up incrementally, etc.
}
}
Anyway, hope I helped.
If you have properly set the delegate, your table will call scrollViewDidScroll: when scrolled.
So in your controller, you can add something like :
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView.contentOffset.y >0) //means that the user began to scroll down the table
{
[UIView animateWithDuration:0.4 animations:^{
//animations you want to perform
}];
}
}
Here i implemented code for UIView Hide / Show when tableview scrolling. When tableview scrolling down then UIView is hidden and when scrolling up then UIView show. I hope it's working for you...!
Step 1:- Make one property in .h file
#property (nonatomic) CGFloat previousContentOffset;
Step 2:- Write down this code in scrollViewDidScroll Method.
-(void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat currentContentOffset = scrollView.contentOffset.y;
if (currentContentOffset > self.previousContentOffset) {
// scrolling towards the bottom
[self.subButtonView setHidden:YES];
} else if (currentContentOffset < self.previousContentOffset) {
// scrolling towards the top
[self.subButtonView setHidden:NO];
}
self.previousContentOffset = currentContentOffset;
}
I create simple class for this effect:
UIHidingView is an iOS class that displays UIView element on top UITableView which is hiding when Table View is scrolling.
This will answer your question :
iPhone: Hide UITableView search bar by default
same concept, different control. You can put a UIView on top row of tableview or any other relevant control such as button.
Good luck.

Resources