Scroll UITableView so that the header isn't visible - iphone

I've got a UITableView with a UISearchBar as the tableViews.tableHeaderView. Just like the new Mail.app, Notes.app, etc. in 3.0. I want to hide the SearchBar until the user drags it in his sight.
My attempt only works when there're a couple of items in the tableView, so that the tableView actually wants to scroll. I call this in loadView:
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[self._tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:NO];
Nevertheless it seems that Apple handles such a serachbar differently. After draging out the searchbar it doesn't seem to be bounded to the tablecells anymore (in Notes.app, not in Mail.app).
But perhaps Apple has a distinct method for that new 3.0 behaviour, and I just can't find it?

Maybe you can try it this way...
[self.tableView setContentOffset:CGPointMake(0,40)];

Worked for me too. I used the following:
[self.tableView setContentOffset:CGPointMake(0, self.searchDisplayController.searchBar.frame.size.height) animated:NO];
to query the height of the search bar.

This one gets you the exact same behavior as iPod.app:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
CGFloat searchBarHeight = CGRectGetHeight([[[self searchDisplayController] searchBar] frame]);
if ([[self tableView] contentOffset].y < searchBarHeight)
[[self tableView] setContentOffset:CGPointMake(0, searchBarHeight)];
}

This works for me.
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.bounces = YES;
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.tableView setContentOffset:CGPointMake(0, 44)];
}

I had to scroll first to top then setContentOffset to 0, Then searchBar will be visible :
self.tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: 0, inSection: 0), atScrollPosition: UITableViewScrollPosition.Top, animated: false)
self.tableView.setContentOffset(CGPointMake(0, 0), animated: false)

I kind of like doing it this way:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// Hide the table view header by default.
NSIndexPath *index = [NSIndexPath indexPathForRow:0 inSection:0];
[self.tableView scrollToRowAtIndexPath:index atScrollPosition:UITableViewScrollPositionTop animated:NO];
}
This way you don't really need to worry about how tall your header is. It just works!

Related

SearchBar disappears from headerview in iOS 7

I have a tableview for showing a list of devices in my application. When viewWillAppear is called, I add the self.searchDisplayController.searchBar as a subview to a headerView. I then assign self.tableView.tableHeaderView = headerView. It looks like this:
I scroll the tableview down so that headerview goes out of view and then go to some other view controller by tapping on a cell. When I come back to this tableView, scroll up to the headerView, the searchBar becomes invisible, however on tapping the invisible area the searchDisplayController gets activated and the cancel button doesn't work. This happens for iOS 7 only. Why is this happening?
Note: It happens only if the headerView is out of the view when I come back to the tableViewController.
I've just had the same issue. When I go to debug into the delegate method of UISearchDisplayController at the end search state, the searchBar becomes a subview of an UIView, not the UITableView. Please see below code:
- (void)searchDisplayControllerDidEndSearch:(UISearchDisplayController *)controller
{
//My Solution: remove the searchBar away from current super view,
//then add it as subview again to the tableView
UISearchBar *searchBar = controller.searchBar;
UIView *superView = searchBar.superview;
if (![superView isKindOfClass:[UITableView class]]) {
NSLog(#"Error here");
[searchBar removeFromSuperview];
[self.tableView addSubview:searchBar];
}
NSLog(#"%#", NSStringFromClass([superView class]));
}
My solution is remove the searchBar away from current super view, then add it as subview again to the tableView. I've already tested successfully.
Hope that help!
Regards
I have the exact same problem. the search bar is still there and can receive touch events. it is however not rendered. I believe the problem is in UISearchDisplaycontroller because it renders fine if I don't use UISearchDisplayController. I ended up writing a custom SearchDisplaycontroller to replace it. it is very basic and only does what I need.
use it is the same way as you would the normal UISearchDisplayController but self.searchDisplayController will not return anything. you will have to use another pointer to refer to the custom search display controller.
looks like a big ugly work around, but the only one that worked for me. keen to hear of alternatives.
#protocol SearchDisplayDelegate;
#interface SearchDisplayController : NSObject<UISearchBarDelegate>
- (id)initWithSearchBar:(UISearchBar *)searchBar contentsController:(UIViewController *)viewController;
#property(nonatomic,assign) id<SearchDisplayDelegate> delegate;
#property(nonatomic,getter=isActive) BOOL active; // configure the view controller for searching. default is NO. animated is NO
- (void)setActive:(BOOL)visible animated:(BOOL)animated; // animate the view controller for searching
#property(nonatomic,readonly) UISearchBar *searchBar;
#property(nonatomic,readonly) UIViewController *searchContentsController; // the view we are searching (often a UITableViewController)
#property(nonatomic,readonly) UITableView *searchResultsTableView; // will return non-nil. create if requested
#property(nonatomic,assign) id<UITableViewDataSource> searchResultsDataSource; // default is nil. delegate can provide
#property(nonatomic,assign) id<UITableViewDelegate> searchResultsDelegate;
#end
#protocol SearchDisplayDelegate <NSObject>
// implement the protocols you need
#optional
#end
the implementation
#implementation SearchDisplayController {
UISearchBar *_searchBar;
UIViewController *_viewController;
UITableView *_searchResultsTableView;
UIView *_overLay;
}
- (void)dealloc {
[_searchBar release];
[_searchResultsTableView release];
[_overLay release];
[super dealloc];
}
- (UIViewController *)searchContentsController {
return _viewController;
}
- (UITableView *)searchResultsTableView {
return _searchResultsTableView;
}
- (id)initWithSearchBar:(UISearchBar *)searchBar contentsController:(UIViewController *)viewController {
self = [super init];
if (self) {
_searchBar = [searchBar retain];
_searchBar.delegate = self;
_viewController = viewController;
_searchResultsTableView = [[UITableView alloc]initWithFrame:CGRectMake(0, CGRectGetMaxY(_searchBar.frame), _viewController.view.frame.size.width, _viewController.view.frame.size.height - CGRectGetMaxY(_searchBar.frame))];
_overLay = [[UIView alloc]initWithFrame:_searchResultsTableView.frame];
_overLay.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(overLayTapped)];
[_overLay addGestureRecognizer:tap];
[tap release];
}
return self;
}
- (void)setSearchResultsDataSource:(id<UITableViewDataSource>)searchResultsDataSource {
_searchResultsTableView.dataSource = searchResultsDataSource;
}
- (void)setSearchResultsDelegate:(id<UITableViewDelegate>)searchResultsDelegate {
_searchResultsTableView.delegate = searchResultsDelegate;
}
- (void)overLayTapped {
[self setActive:NO animated:YES];
[_searchBar resignFirstResponder];
_searchBar.text = nil;
_searchBar.showsCancelButton = NO;
}
- (void)setActive:(BOOL)visible animated:(BOOL)animated {
UIView *viewToAdd = nil;
if (!_searchBar.text.length) {
viewToAdd = _overLay;
} else {
viewToAdd = _searchResultsTableView;
}
float a = 0;
if (visible) {
[_viewController.view addSubview:viewToAdd];
a = 1.0;
}
if ([_viewController.view respondsToSelector:#selectore(scrollEnabled)]) {
((UIScrollView *)_viewController.view).scrollEnabled = !visible;
}
if (animated) {
[UIView animateWithDuration:0.2 animations:^{
_overLay.alpha = a;
_searchResultsTableView.alpha = a;
}];
} else {
_overLay.alpha = a;
_searchResultsTableView.alpha = a;
}
}
- (void)setActive:(BOOL)active {
[self setActive:active animated:YES];
}
#pragma mark - UISearchBar delegate protocols
- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar {
[self setActive:YES animated:YES];
searchBar.showsCancelButton = YES;
[_searchResultsTableView reloadData];
}
- (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar {
}
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
[_searchResultsTableView reloadData];
}
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
[self overLayTapped];
}
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
if (searchText.length) {
[_overLay removeFromSuperview];
[_viewController.view addSubview:_searchResultsTableView];
} else {
[_searchResultsTableView removeFromSuperview];
[_viewController.view addSubview:_overLay];
}
[_searchResultsTableView reloadData];
}
#end
Update: on how to use this progammatically
declare an ivar
SearchDisplayController *mySearchDisplayController;
initialize it programmatically
mySearchDisplayController = [[SearchDisplayController alloc]initWithSearchBar:mySearchBar contentsController:self];
adding the searchbar to your tableview
self.tableView.headerView = mySearchBar;
use mySearchDisplayController as reference to the custon class instead on self.searchDisplayController.
In my case, the table view that held the search display controller's search bar in its header view was being reloaded almost as soon as the view appeared. It was at this point that the search bar would cease to render. When I scrolled the table, it would reappear. It's also worth mentioning that my table contained a UIRefreshControl and was not a UITableViewController subclass.
My fix involved setting the search display controller active and then inactive very quickly just before after loading the table (and ending the refresh control refreshing):
[self.tableView reloadData];
[self.refreshControl endRefreshing];
[self.searchDisplayController setActive:YES animated:NO];
[self.searchDisplayController setActive:NO];
A bit of a hack but it works for me.
Using the debugger, I've found that the UISearchBar is initially a child view of the tableHeaderView - but when it disappears, it has become a child of the tableView itself. This has probably been done by UISearchDisplayController somehow... So I did the following hack to simply return the UISearchBar to the header view:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if(!self.searchDisplayController.isActive && self.searchBar.superview != self.tableView.tableHeaderView) {
[self.tableView.tableHeaderView addSubview:self.searchBar];
}
}
Seems to work fine on iOS 7 as well as 6 :)
(checking that the searchDisplayController isn't active is necessary, otherwise the sarch bar disappears during search)
I endorse Phien Tram's answer. Please upvote it. I don't have enough points myself.
I had a similar problem where a search bar loaded from storyboard would disappear when I repeatedly tapped it, invoking and dismissing search. His solution repairs the problem.
There seems to be a bug where repeated invocation and dismissal of the search display controller doesn't always give the search bar back to the table view.
I will say I'm uncomfortable with the solution's dependence on the existing view hierarchy. Apple seems to reshuffle it with every major release. This code may break with iOS 8.
I think a permanent solution will require a fix by Apple.
I had the same issue and I could fix it calling next line after creating the UISearchDisplayController
[self performSelector:#selector(setSearchDisplayController:) withObject:displayController];
My viewDidLoad function look like this:
UISearchBar *searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, self.tableView.frame.size.width, 44)];
searchBar.placeholder = NSLocalizedString(#"Search", #"Search");
UISearchDisplayController *displayController = [[UISearchDisplayController alloc] initWithSearchBar:searchBar contentsController:self];
displayController.delegate = self;
displayController.searchResultsDataSource = self;
displayController.searchResultsDelegate = self;
[self performSelector:#selector(setSearchDisplayController:) withObject:displayController];
self.tableView.contentOffset = CGPointMake(0, 44);
self.tableView.tableHeaderView = searchBar;
Thanks to this answer: https://stackoverflow.com/a/17324921/1070393
Use UISearchBar above UITableView,Then make IBOutlet for and connect them with file's owner to UISearchbar
Example- .h file
#import <UIKit/UIKit.h>
#interface LocationViewController : UIViewController<UISearchBarDelegate>
{
BOOL IsSearchOn;
}
#property (strong, nonatomic) IBOutlet UISearchBar *searchBar;
#property (strong, nonatomic) IBOutlet UITableView *TBVLocation;
.m file
#pragma mark -
#pragma mark UISearchBar Delegate Methods
-(void) searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
[self.searchResult removeAllObjects];
if(searchText.length == 0)
{
IsSearchOn=NO;
// [filteredTableData removeAllObjects];
[self.searchBar resignFirstResponder];
// [self .tblView reloadData];
}
else
{
IsSearchOn=YES;
if(searchText != nil && ![searchText isEqualToString:#""])
{
/* NSPredicate *resultPredicate = [NSPredicate predicateWithFormat:#"SELF contains[c] %#", searchText];
self.searchResult = [NSMutableArray arrayWithArray: [searchArray filteredArrayUsingPredicate:resultPredicate]];*/
for(int i=0;i<[[arrCountryList valueForKey:#"country_name"] count];i++)
{
NSRange titleRange = [[[[arrCountryList valueForKey:#"country_name"] objectAtIndex:i] lowercaseString] rangeOfString:[searchText lowercaseString]];
if(titleRange.location != NSNotFound)
[self.searchResult addObject:[arrCountryList objectAtIndex:i]];
}
[TBVLocation reloadData];
}
}
}
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
{
[searchBar resignFirstResponder];
}
-(void)searchBarCancelButtonClicked:(UISearchBar *) searchBar
{
[searchBar resignFirstResponder];
IsSearchOn=NO;
searchBar.text = nil;
[TBVLocation reloadData];
}
- (BOOL)searchBarShouldEndEditing:(UISearchBar *)searchBar
{
[searchBar resignFirstResponder];
return YES;
}
- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar
{
searchBar.showsCancelButton = YES;
searchBar.autocorrectionType = UITextAutocorrectionTypeNo;
// IsSearchOn=YES;
}
- (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar
{
IsSearchOn=NO;
searchBar.showsCancelButton = NO;
[TBVLocation reloadData];
[searchBar resignFirstResponder];
}
It will work like charm.
I've faced similar problem and after some digging, I've found that this is a bug in UISearchBar hierarchy. This hacky solution worked for me in iOS 7, but be aware that this may break in future iOS versions:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
UIView *buggyView = [self.searchDisplayController.searchBar.subviews firstObject];
// buggyView bounds and center are incorrect after returning from controller, so adjust them.
buggyView.bounds = self.searchDisplayController.searchBar.bounds;
buggyView.center = CGPointMake(CGRectGetWidth(buggyView.bounds)/2, CGRectGetHeight(buggyView.bounds)/2);
}
I had the same problem and tested some of the solutions proposed here in this thread, but they didn't solve the problem for me.
Previously, I added and configured the UISearchBar in the
- (void)viewDidLoad
method of my ViewController in code.
UISearchBar *searchBar = [[UISearchBar alloc] initWithFrame:searchBarView.frame];
searchBar.delegate = self;
searchBar.autocapitalizationType = UITextAutocapitalizationTypeNone;
ect...
What solved this issue for me was that I added a UISearchbar in the InterfaceBuilder, created an outlet in my ViewController and added this UISearchBar to my UISearchDisplayController.
self.searchDisplayController = [[UISearchDisplayController alloc] initWithSearchBar:searchBar(<--outlet) contentsController:self];
hope this might also help some people

scrollToRowAtIndexPath not scrolling to inactive/unloaded cells

I've noticed that scrollToRowAtIndexPath:atScrollPosition:animated: doesn't scroll to cell that are not currently in view, so If I have 100 cells and I need to get to the one at 70, the call to that selector will do nothing.
Is there a way I can get that cell into memory? I already have the cell's index path...
I need to scroll to that position in my app when the user would want to go there.
Thanks for any thoughts!
EDIT: #dasblinkenlight
- (void)viewDidLoad
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillShow) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillHide) name:UIKeyboardWillHideNotification object:nil];
}
- (void)keyboardWillHide
{
//Load remote cell here then scroll
// :( dont know how to load remote cell yet
}
- (void)keyboardWillShow
{
//Load remote cell here then scroll
// :( dont know how to load remote cell yet
//_cellIndexPath gets assigned on didSelectRowAtIndexPath:
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:_cellIndexPath.row inSection:_cellIndexPath.section] atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
EDIT2:
- (void)keyboardWillShow
{
//Load remote cell here then scroll
[NSThread detachNewThreadSelector:#selector(keyboardWillShowThreaded) toTarget:self withObject:nil];
}
- (void)keyboardWillShowThreaded
{
[NSThread sleepForTimeInterval:2.0];
[self performSelectorOnMainThread:#selector(keyboardWillShowMainThread) withObject:nil waitUntilDone:YES];
}
- (void)keyboardWillShowMainThread
{
//Get the cell
//_textFieldThatHasFirstResponder is a subview in the cell
//This returns null, probably because the cell is not loaded into memory
UITableViewCell *cell = [_textFieldThatHasFirstResponder superview];
NSLog(#"CELL TO SCROLL TO: %#",cell);
NSIndexPath *indexPathForCell = [self.tableView indexPathForCell:cell];
[self.tableView scrollToRowAtIndexPath:indexPathForCell atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
}
OK, I've got the cause of this, see, you have:
NSIndexPath *indexPathForCell = [self.tableView indexPathForCell:cell]; // nil here
[self.tableView scrollToRowAtIndexPath:indexPathForCell atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
When you send indexPathForCell: for an out-of-view cell it returns nil, so tableView doesn't know where to scroll to.
you can implement a delegate so that you can call it from the class where you are in, so that is can update the position
"I've noticed that scrollToRowAtIndexPath:atScrollPosition:animated:
doesn't scroll to cell that are not currently in view, so If I have
100 cells and I need to get to the one at 70, the call to that
selector will do nothing. "
No. it is not true. I have just tried with a tableview with 100 rows. The following code works well.
[tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:78 inSection:0]
atScrollPosition:UITableViewScrollPositionNone animated:YES];
I don't think adding sleep is changing anything. It just delays execution but does not affect the order. Can you check if the index your are passing to scrollToRowAtIndexPath is valid? I remember seeing the same problem myself but it was related to invisible cell. It was impossible to retrieve invisible cell (tableView returned nil) and therefore its index path was nil and thus scrolling failed.
You could store locations of all cells or compute it on the fly and then pass it to
- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated;
It's the only solution I can imagine.

animation issues when editing a uitableview

This is really simple, though still driving my nuts. I have a uitableview where I am trying to animate transition in and out of editing mode. This is what I took from an example that I have seen. It does do the job, but without the animation.
Any thoughts?
- (IBAction) EditTable:(id)sender
{
if(self.editing)
{
[super setEditing:NO animated:YES];
[tblSimpleTable setEditing:NO animated:YES];
[tblSimpleTable reloadData];
[self.navigationItem.leftBarButtonItem setTitle:#"Edit"];
[self.navigationItem.leftBarButtonItem setStyle:UIBarButtonItemStylePlain];
}
else
{
[super setEditing:YES animated:YES];
[tblSimpleTable setEditing:YES animated:YES];
[tblSimpleTable reloadData];
[self.navigationItem.leftBarButtonItem setTitle:#"Done"];
[self.navigationItem.leftBarButtonItem setStyle:UIBarButtonItemStyleDone];
}
}
PS: I am also not sure why I need this line: [super setEditing:NO animated:YES]; but things just dont seem to work at all without it. I just saw a few examples online that dont do that.
Thanks!
Maybe you should not reloadData when set editing property.
BTW, What's your "super" class? Normally you don't have to invoke [super setEditing:YES animated:YES];
Is it only the button that isn't animating properly? Either way you should probably be using super.editButtonItem instead of your own; it's animated and just setting the text and style like that (I believe) isn't. As far as calling the super, are you overriding one of the editing methods and not calling the super method from within there? And xuzhes's answer about the reloadData is, I believe, correct as well.
Try this:
#Implementation YourViewController // This can (should) be a subclass of UITableViewController to make your life easier
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationItem.rightBarButtonItem = self.editButtonItem; // Automatically calls setEditing:animated: and changes itself to "Edit"/"Done" between presses
}
- (void)setEditing:(BOOL)editing animated:(BOOL)animated {
[super setEditing:editing animated:animated];
if (editing == YES) {
// Do stuff here
} else {
// Do stuff here
}
// Reload all sections of the table view
// THIS IS THE PART YOU'RE INTERESTED IN
NSRange range = NSMakeRange(0,[self.tableView numberOfSections]);
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:range];
[self.tableView reloadSections:indexSet withRowAnimation:UITableViewRowAnimationAutomatic];
}
Check out the documentation for comments on the methods from Apple :)

iPad not resigning responder

I have a table with 3 UITextFields added to the content views of cells (1 row per section). I have a 4th section that I insert so that I can scroll the particular field to be above the keyboard.
The problem I have (on the iPad only) is that when one of the text fields has firstResponder, it does not relinquish it when user taps on another field. Are there differences in the responder chain on an ipad? In the bellow code for my view controllers UITextFieldDelegate - textFieldShouldEndEditing does not get called when touching another field. Pressing the done button works as expected (unless another field has been touched).
Anything wrong with the code bellow?
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
if (!_editing++) {
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:4] withRowAnimation:UITableViewRowAnimationNone];
}
// scroll to section number in the text fields tag
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:textField.tag] atScrollPosition:UITableViewScrollPositionTop animated:YES];
return YES;
}
- (void) textFieldDidBeginEditing:(UITextField *)textField {
self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:textField action:#selector(resignFirstResponder)] autorelease];
}
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField {
if (_editing-- == 1) {
//
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:4] withRowAnimation:UITableViewRowAnimationNone];
}
self.navigationItem.rightBarButtonItem = nil;
// do something with the text here...
return YES;
}
OK, i have found a satisfactory workaround, it seems allowing the run loop to execute before the request to animate removing a section fixes the problem. I guess this is an apple bug? For anyone else worried about this issue - i was running iOS3.2.1 on the iPad.
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField {
if (_editing == 1) {
[self performSelector:#selector(hideFinalSection) withObject:nil afterDelay:0.0f];
}
else {
_editing--;
}
self.navigationItem.rightBarButtonItem = nil;
// do something with the text here...
return YES;
}
- (void) hideFinalSection {
if (!(--_editing)) {
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:4] withRowAnimation:UITableViewRowAnimationNone];
}
}

How to scroll a UITableView to the tableFooterView

I have a UITableView whose contents are dynamically changing, like a FIFO stack. Cells are added to the bottom and removed from the top.
This works beautifully, and I can scroll to the indexPath so that the newest message always scrolls down to the bottom (Like a chat application).
Now.. I want to add a footer to that table section. Instead of using
SrollToRowAtIndexPath
I would like to be able to scroll to the tableFooterView.
Any ideas how I can do that would be appreciated.
I am using this to scroll to the footer view of a tableView:
[self.tableView scrollRectToVisible:[self.tableView convertRect:self.tableView.tableFooterView.bounds fromView:self.tableView.tableFooterView] animated:YES];
The best way to scroll a UITableView to the bottom of it's footerView is to simply set the content offset. You can calculate the bottom using the contentSize and the current bounds
Here is the way I do it.
CGPoint newContentOffset = CGPointMake(0, [self.instanceOfATableView contentSize].height - self.instanceOfATableView.bounds.size.height);
[self.instanceOfATableView setContentOffset:newContentOffset animated:YES];
Thanks to iphone_developer here is what I did :
[tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:[tableView numberOfRowsInSection:0]-1 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES];
Then while I'm adding rows, I'm calling this and my tableView's footer view keeps being visible
Swift Version:
tableView.scrollToRow(at: IndexPath(row: self.tblview.numberOfRows(inSection: 0) - 1, section: 0), at: .top, animated: true)
So many poor answers. :-)
The BEST way:
[self.tableView scrollRectToVisible:
self.tableView.tableFooterView.frame animated:YES
];
Maybe something like:
[tableView setContentOffset:CGPointMake(0, tableView.contentSize.height) animated:YES];
Since UITableView is a subclass of UIScrollView, you can scroll to wherever you like using the UIScrollView method
- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated
Just set the rect so that when it's visible the footer is visible, and you'll have your solution (you can use the footer's rect or something else, just so long as you get the right behavior all the time).
My late answer is for developer, who need to show footer when the keyboard is shown.
The right solution is to consider contentInset property (which can be changed after keyboard is shown), so it's super easy:
- (void)scrollToFooter {
UIEdgeInsets tableInsets = self.tableView.contentInset;
CGFloat tableHeight = self.tableView.frame.size.height - tableInsets.bottom - tableInsets.top;
CGFloat bottom = CGRectGetMaxY(self.tableView.tableFooterView.frame);
CGFloat offset = bottom - tableHeight;
if(offset > 0.f) {
[self.tableView setContentOffset:CGPointMake(0, offset) animated:YES];
}
}
I must notice, that in my case tableView was added to my own ViewController and one of cells have UITextField which become first responder. To move show footer when keyboard is shown you need to register keyboard did shown notification and (on iOS7) perform this method in the end of current run loop, coz in this case iOS7 automatically perform scrollToRowAtIndexPath after our method and footer will not be shown.
-(void)registerKeyboardNotification {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardDidShown:)
name:UIKeyboardDidShowNotification object:nil];
}
- (void)keyboardDidShown:(id)notification {
//move to the end of run loop
[self performSelector:#selector(scrollToFooter) withObject:nil afterDelay:.0];
}
The easiest way to do this is to use UITableViewScrollPositionTop on the LAST ROW in the LAST SECTION. This works very well for me...
[tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:LAST_ROW inSection:LAST_SECTION] atScrollPosition:UITableViewScrollPositionTop animated:YES];
Make sure your Table Footer View is well spaced out at the bottom and it should sit nicely animated inside the view...
Hope this helps...
This good work for me!
CGRect footerBounds = [addCommentContainer bounds];
CGRect footerRectInTable = [tableview convertRect:footerBounds fromView:addCommentContainer];
[tableview scrollRectToVisible:footerRectInTable animated:YES];
This works for me in Swift 4
func addRow() {
tableView.beginUpdates()
tableView.insertRows(at: [IndexPath(row: array.count - 1, section: 0)], with: .automatic)
tableView.endUpdates()
DispatchQueue.main.async {
self.scrollToBottom()
}
}
func scrollToBottom() {
let footerBounds = tableView.tableFooterView?.bounds
let footerRectInTable = tableView.convert(footerBounds!, from: tableView.tableFooterView!)
tableView.scrollRectToVisible(footerRectInTable, animated: true)
}
Great idea, was looking for this myself :) Here's sample code, which would do the trick:
[tableView reloadData];
NSIndexPath *index = [NSIndexPath indexPathForRow:0 inSection:1];
[tableView scrollToRowAtIndexPath:index
atScrollPosition:UITableViewScrollPositionBottom animated:YES];
You MUST have at least one cell in your tableView footer, the problem is that it's going to visible. Didn't have time to test, but I'd guess you could make it really small?
Additionally you must implement correct stuff inside numberOfSectionsInTableView (at least one for table and one for footer), numberOfRowsInSection (at least one for footer, your last section), viewForHeaderInSection (nil except for your cell), heightForHeaderInSection (maybe if you set this as zero), cellForRowAtIndexPath (add special case for your cell in footer)...
That should be enough.
I'm using this:
- (void)scrollToBottom{
[self.myTable scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:[self.fetchedResultsController.fetchedObjects count] -1 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
This work to me:
- (void)tableViewScrollToBottomAnimated:(BOOL)animated {
NSInteger numberOfRows = [self.tableView numberOfRowsInSection:0];
if (numberOfRows) {
if (self.tableView.tableFooterView) {
[self.tableView scrollRectToVisible:
self.tableView.tableFooterView.frame animated:YES
];
} else {
[self.tableView scrollToRowAtIndexPath:
[NSIndexPath indexPathForRow:numberOfRows-1 inSection:0]
atScrollPosition:UITableViewScrollPositionBottom
animated:animated
];
}
}
}
I haven't tried, but what happens when you scroll to the last row+1?
Another idea would be to always add a dummy entry at the end and make it have a different look so it's seen as a footer. Then you can always scroll to that.