UITableView contentOffSet is not working properly
Asked Answered
P

15

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.

Phare answered 5/3, 2013 at 11:5 Comment(8)
have you tried calling setContentOffset:animated:?Seaway
I tried setContentOffset:animated:, but its not working as desirePhare
have u set the height of headerview?Incunabulum
Yes, headerview height is 40. I have cheked in the logs as wellPhare
and whats your table frame?Incunabulum
TableView frame is (0 41; 320 367)Phare
did my answer helped u?Incunabulum
Have you checked my answer below? It should solve your issue.Firstborn
D
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

Dissolution answered 25/4, 2014 at 22:40 Comment(2)
To me, this is the best answer on this thread. It isn't "hacky"Coffeecolored
works for me as well. It likes in Android, you need postDelayed() in onCreate() method.Binomial
F
20

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);
}
Firstborn answered 15/7, 2013 at 15:36 Comment(3)
Thanks, works great. I used this in viewDidLoad so that the tableview didn't change when users popped back to it.Cords
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!Laparotomy
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)Iatric
S
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)
        }
Succory answered 17/10, 2016 at 1:44 Comment(4)
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!!Titan
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 whyInflationary
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.Succory
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.Timoteo
U
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];
Unanimous answered 29/6, 2017 at 9:23 Comment(2)
Works for me. IOS 11. Just type [yourSuperbTableView beginUpdates]; and so on, for Obj-C.Saransk
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.Succory
S
5

I fixed it with layoutIfNeeded()

    self.articlesCollectionView.reloadData()
    self.articlesCollectionView.layoutIfNeeded()
    self.articlesCollectionView.setContentOffset(CGPoint(x: 0, y: 40), animated: false)
Spandau answered 22/8, 2018 at 16:13 Comment(0)
W
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.

Wooer answered 12/7, 2016 at 7:3 Comment(0)
B
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.

Barlow answered 17/10, 2014 at 23:1 Comment(1)
Added more info, because the answer was downvoted for some reason.Barlow
S
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

Satterfield answered 10/4, 2015 at 10:36 Comment(1)
works perfect, in my case i had to add [self hideSearchBar]; in the viewWillAppearCobaltic
B
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.

Balder answered 5/3, 2013 at 11:18 Comment(0)
I
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

Incunabulum answered 5/3, 2013 at 12:30 Comment(1)
Did you try this thing in your xib and remove all contentoffset codeIncunabulum
T
0

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

[searchBar sizeToFit];
Taiga answered 6/3, 2013 at 8:58 Comment(0)
H
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.

Harriott answered 25/6, 2014 at 8:18 Comment(0)
D
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
}
Dropping answered 16/12, 2015 at 21:22 Comment(0)
F
0

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

Fadeout answered 30/5, 2017 at 10:25 Comment(1)
Any idea why this is marked "deprecated" in developer.apple.com/documentation/uikit/uiviewcontroller/… ? Because it is not deprecated in my current iOS 10.3 <UIKit/UIViewController.h>Ireneirenic
S
-3
self.tableView.contentInset = UIEdgeInsetsMake(self.navigationController.navigationBar.frame.size.height -80, 0, 0, 0);
Syndesis answered 12/5, 2015 at 18:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.