24

In viewWillAppear, I have added UISearchBar as my headerview of UITableView. When view loads, I hides UISearchbar under UINavigationBar using contentOffSet of UITableView. When user pull down the tableview the searchbar gets displayed.

After adding headerview I hides it using below code.

self.tableView.contentOffset = CGPointMake(0, 40); //My searhbar height is 40

But at times contentOffSet is not hiding headerview. What can be the mistake.

Dhara
  • 4,093
  • 2
  • 36
  • 69
JiteshW
  • 2,195
  • 4
  • 32
  • 61

15 Answers15

38

This worked for me:

// contentOffset will not change before the main runloop ends without queueing it, for iPad that is
dispatch_async(dispatch_get_main_queue(), ^{
    // The search bar is hidden when the view becomes visible the first time
    self.tableView.contentOffset = CGPointMake(0, CGRectGetHeight(self.searchBar.bounds));
});

Put it in your -viewDidLoad or -viewWillAppear

maz
  • 8,056
  • 4
  • 26
  • 25
19

Not sure what the reason is (don't have time to research it right now), but I solved this issue by using performSelector after a delay of 0. Example:

- (void)viewWillAppear:(BOOL)animated {
    ...
    [self performSelector:@selector(hideSearchBar) withObject:nil afterDelay:0.0f];
}

- (void)hideSearchBar {
    self.tableView.contentOffset = CGPointMake(0, 44);
}
deRonbrown
  • 635
  • 7
  • 14
  • Thanks, works great. I used this in viewDidLoad so that the tableview didn't change when users popped back to it. – deepwinter Oct 21 '13 at 22:38
  • 1
    Were you ever able to diagnose this issue? This fix solved it for me, too, but I would love to know what the underlying issue is. This seems more like a workaround than anything. Thanks! – Scott Lieberman Dec 16 '13 at 21:15
  • 2
    Really neat answer that. I'm going to hazard a guess that the tableview sets it's own content offset during creation but before placing it on the view and therefore overrides your content offset. By performingSelector it stacks it after the internal method call and it therefore works (just a guess though) – Rambatino Sep 22 '14 at 13:40
19

Dispatch the change to the content offset on the main thread. This allows the UI call to be rescheduled on the queue which allows the table to finish the current layout pass and correctly calculate sizes..

Objective C

dispatch_async(dispatch_get_main_queue(), ^{
    CGPoint offset = CGPointMake(0, self.searchBar.bounds.height)
    [self.tableView setContentOffset:offset animated:NO];
});

Swift 3

DispatchQueue.main.async {
            let offset = CGPoint.init(x: 0, y: self.searchBar.bounds.height)
            self.tableView.setContentOffset(offset, animated: false)
        }
Matthew Cawley
  • 2,828
  • 1
  • 31
  • 42
  • many thanks, finally after so many seemingly logic tweaks to get my table header to disappear as required, this simple bit of code works, cheer!! – drew.. Sep 09 '17 at 14:29
  • thanks man....but why? I never changed offset like this before, I don't understand the real reason to do that. I mean it works but I'm not sure why – Massimo Polimeni Oct 31 '18 at 16:51
  • In earlier versions of the iOS SDK, methods such as `viewWillAppear` ran on the main thread, they now run on a background thread which is why the issue now occurs. Most callbacks tend to fire on a background thread so always check for thread safety when doing UI calls. – Matthew Cawley Nov 05 '18 at 13:30
  • 11
    Reasoning here is that "viewWillAppear runs on background thread", this is false statement. All UIKit is main thread only. And using dispatch on main thread is not really helping because of that (because it is already on main thread) but it is helping because of changed timing. – Juraj Antas Nov 30 '19 at 20:27
6

This worked for me.

self.tableView.beginUpdates()
self.tableView.setContentOffset( CGPoint(x: 0.0, y: 0.0), animated: false)
self.tableView.endUpdates()

Objective-C :

[_tableView beginUpdates];
[_tableView setContentOffset:CGPointMake(0, 0)];
[_tableView endUpdates];
Sunil Targe
  • 7,251
  • 5
  • 49
  • 80
Kishor
  • 376
  • 4
  • 14
  • Works for me. IOS 11. Just type [yourSuperbTableView beginUpdates]; and so on, for Obj-C. – Chrysotribax Feb 26 '18 at 13:38
  • 1
    Note that the beginUpdates call is designed to prepare the table for content changes such as removing or adding rows and not simple UI manipulations. You are getting away with it here as these kinds of update calls will be assuming UI changes after the endUpdates (e.g. row animations). Don't rely on this call for your own custom UI changes. – Matthew Cawley Feb 17 '19 at 22:52
5

I fixed it with layoutIfNeeded()

    self.articlesCollectionView.reloadData()
    self.articlesCollectionView.layoutIfNeeded()
    self.articlesCollectionView.setContentOffset(CGPoint(x: 0, y: 40), animated: false)
Blank
  • 4,872
  • 2
  • 28
  • 25
3

In my case the problem was that I called

 [_myGreatTableView reloadData];

before getting the _tableView.contentOffset.y. This always returned '0'.

So I switched the order of these methods and now it works.

This is quite a strange behavior in my opinion, because offset returned 0 but the UI still kept the scroll position of the table.

Mexx
  • 359
  • 3
  • 17
2

It may be because of the "Adjust Scroll View Insets" attribute on the View Controller. See this: https://stackoverflow.com/a/22022366

EDIT: Be sure to check the value of 'contentInset' to see what's happening, since this also has an effect on the scroll view. This value is changed after viewWillAppear: when "Adjust Scroll View Insets" is set, which seems to be what others are trying to avoid by using dispatch queues and the like.

Community
  • 1
  • 1
techniao
  • 151
  • 1
  • 9
2

After one hour of tests the only way that works 100% is this one:

-(void)hideSearchBar
{
    if([self.tableSearchBar.text length]<=0 && !self.tableSearchBar.isFirstResponder)
    {
        self.tableView.contentOffset = CGPointMake(0, self.tableSearchBar.bounds.size.height);
        self.edgesForExtendedLayout = UIRectEdgeBottom;
    }
}

-(void)viewDidLayoutSubviews
{
    [self hideSearchBar];
}

with this approach you can always hide the search bar if is empty

Kappe
  • 9,217
  • 2
  • 29
  • 41
0

You need to hide the search bar by yourself when you scroll the table. So don't put it as a UITableView header. You could hide it by setting its height to zero. That way if your table view is set to auto resize, it will expand.

Abdurrahman Mubeen Ali
  • 1,331
  • 1
  • 13
  • 19
parilogic
  • 1,147
  • 9
  • 26
0

The problem is auto masking. Select your table and make sure you have selected all the lines, as in the image below.

enter image description here

Abdurrahman Mubeen Ali
  • 1,331
  • 1
  • 13
  • 19
Dhara
  • 4,093
  • 2
  • 36
  • 69
0

The problem may be with the search bar size. Try the following line of code.

[searchBar sizeToFit];
Abdurrahman Mubeen Ali
  • 1,331
  • 1
  • 13
  • 19
uranpro
  • 85
  • 1
  • 7
0

The line of code is working as it should.

Explaination of contentOffset of a UITableView:

For example, if you have a table view of height 100. Its content may be more than it's view, say 150. contentOffset will tell the table from where in the content to start from. Say contentOffset = (0, 40), the content will be shown after 40 of it's height.

Note: Once the content is scrolled, there is no affect of the previously set contentOffset.

Abdurrahman Mubeen Ali
  • 1,331
  • 1
  • 13
  • 19
0

If your table will always have at least one row, just scroll to the first row of the table and the search bar will be hidden automatically.

let firstIndexPath = NSIndexPath(forRow: 0, inSection: 0)

self.tableView.selectRowAtIndexPath(firstIndexPath, animated: false, scrollPosition: .Top)

If you put the above code on viewDidLoad, it will throw an error because the tableView hasn't loaded yet, so you have to put it in viewDidAppear because by this point the tableView has already loaded.

If you put it on viewDidAppear, everytime you open the tableView it will scroll to the top.

Maybe you don't want this behaviour if the tableView remains open, like when it is a UITabBar View Controller or when you do a segue and then come back. If you just want it to scroll to the top on the initial load, you can create a variable to check if it is an initial load so that it scrolls to the top just once.

First define a variable called isInitialLoad in the view controller class and set it to "true":

var isInitialLoad = true

And then check if isInitialLoad is true on viewDidAppear and if it is true, scroll to the top and set the isInitialLoad variable to false:

if isInitialLoad {
    let firstIndexPath = NSIndexPath(forRow: 0, inSection: 0)
    self.tableView.selectRowAtIndexPath(firstIndexPath, animated: false, scrollPosition: .Top)
    isInitialLoad = false
}
Jeremy Fox
  • 2,668
  • 1
  • 25
  • 26
drv
  • 788
  • 9
  • 16
0

In iOS 7/8/9 simple self.automaticallyAdjustsScrollViewInsets = NO; solved the problem in my case

Saumil Shah
  • 2,299
  • 1
  • 22
  • 27
  • 1
    Any idea why this is marked "deprecated" in https://developer.apple.com/documentation/uikit/uiviewcontroller/1621372-automaticallyadjustsscrollviewin ? Because it is not deprecated in my current iOS 10.3 – thomers Jun 16 '17 at 10:24
-3
self.tableView.contentInset = UIEdgeInsetsMake(self.navigationController.navigationBar.frame.size.height -80, 0, 0, 0);
Schemetrical
  • 5,506
  • 2
  • 26
  • 43
Angelote
  • 1
  • 1