iOS 7 Table view fail to auto adjust content inset

39,056

Solution 1

I have found the answer on apple developer forum. There are two different case.

The first one, the view controller added is a UITableViewController. And the issue should not be appeared since apple will auto padding it.

The second one, the view controller is NOT a UITableViewController. And in the view hierarchy, it contains a UITableView. In this case, if the UITableview(or ScrollView) is the viewController's mainview or the first subview of the mainview, it will work. Otherwise, the view controller doesn't know which scroll view to padding and it will happen the issue.

In my case, the view controller is the second one. And there is a background image view as the first subview of the main view. So, it fails.

Here is the Apple developer forum link (need developer account to access): https://devforums.apple.com/message/900138#900138

Solution 2

If you want the view to underlap the navigation bar, but also want it positioned so the top of the scrollview's content is positioned below the navigation bar by default, you can add a top inset manually once the view is laid out. This is essentially what the view layout system does when the top-level view is a scroll view.

-(void)viewDidLayoutSubviews {
    if ([self respondsToSelector:@selector(topLayoutGuide)]) {
        UIEdgeInsets currentInsets = self.scrollView.contentInset;
        self.scrollView.contentInset = (UIEdgeInsets){
            .top = self.topLayoutGuide.length,
            .bottom = currentInsets.bottom,
            .left = currentInsets.left,
            .right = currentInsets.right
        };
    }
}

Solution 3

Based on Tony's answer I was able to get around this problem programatically with temporarily sending the table view to the back, let the adjustments be made and then send the background view back to the back. In my case there is no flickering to this approach.

In the View Controller:


- (void)viewWillLayoutSubviews {
    [super viewWillLayoutSubviews];

    [self.view sendSubviewToBack:self.tableView];
}

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

    [self.view sendSubviewToBack:self.backgroundView];
}

Obviously if there are other subviews on self.view you may need to re-order those too.

Solution 4

There's probably too many answers on this already, but I had to take Christopher's solution and modify it slightly to support view resizing and allowing the content inset to be changed in a subclass of the UIViewController.

@interface MyViewController ()

@property (weak, nonatomic) IBOutlet UIScrollView *scrollView;
@property (assign, nonatomic) UIEdgeInsets scrollViewInitialContentInset;

@end


@implementation MyViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    [self setScrollViewInitialContentInset:UIEdgeInsetsZero];
}

- (void)viewWillLayoutSubviews {
    [super viewWillLayoutSubviews];

    if (UIEdgeInsetsEqualToEdgeInsets([self scrollViewInitialContentInset], UIEdgeInsetsZero)) {
        [self setScrollViewInitialContentInset:[self.scrollView contentInset]];
    }
}

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

    UIEdgeInsets scrollViewInset = [self scrollViewInitialContentInset];

    if (UIEdgeInsetsEqualToEdgeInsets(scrollViewInset, UIEdgeInsetsZero) {

        if ([self respondsToSelector:@selector(topLayoutGuide)]) {
            scrollViewInset.top = [self.topLayoutGuide length];
        }

        if ([self respondsToSelector:@selector(bottomLayoutGuide)]) {
            scrollViewInset.bottom = [self.bottomLayoutGuide length];
        }

        [self.scrollView setContentInset:scrollViewInset];
    }
}

@end

To explain the point:

Any subclass of MyViewController can now modify the contentInset of scrollView in viewDidLoad and it will be respected. However, if the contentInset of scrollView is UIEdgeInsetsZero: it will be expanded to topLayoutGuide and bottomLayoutGuide.

Solution 5

@Christopher Pickslay solution in Swift 2:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    let topInset = topLayoutGuide.length
    inTableView.contentInset.top = topInset
    inTableView.contentOffset.y = -topInset
    inTableView.scrollIndicatorInsets.top = topInset
}
Share:
39,056
Tony Fung
Author by

Tony Fung

iOS developer in Hong Kong.

Updated on June 11, 2020

Comments

  • Tony Fung
    Tony Fung almost 4 years

    I am transiting my project to iOS7. I am facing a strange problem related to the translucent navigation bar.

    I have a view controller and it has a tableview as subview (let's call it ControllerA) . I init a new uinavigationcontroller with the controllerA and present it modally using presentviewcontroller. The presented view controller's table view is blocked by the navigation bar. I set the automaticallyAdjustsScrollViewInsets to YES but the result did not change. I knew I can set the edgesForExtendedLayout to UIRectEdgeNone, but it will make the navigation bar no more translucent.

    Table view get blocked

    After that, I tried to create a new view controller for testing. It contains almost the same elements. But the result is much different. The table view content does not get blocked.

    enter image description here

    Conclusion

    1. Two View Controllers' automaticallyAdjustsScrollViewInsets set to YES
    2. The project is not using storyboard
    3. The first one is created at Xcode 4.6, The second one is newly created on Xcode 5
    4. I have compared two classes xib and code, not much different
  • Tony Fung
    Tony Fung over 10 years
    Did you try to set the self.edgesForExtendedLayout = UIRectEdgeNone; It can disable the content inset auto padding.
  • DaGaMs
    DaGaMs over 10 years
    Thanks, this was useful for my particular case: I have a UICollectionViewController inside a UIPageViewController, the latter of which is pushed onto a UINavigationController. The internal scrollView of the pageViewController prevents automaticallyAdjustsScrollviewInsets from working correctly. Unfortunately, the topLayoutGuide is not set correctly,either, but I can still manually get the offset of the navigation bar: CGFloat topOffset = self.navigationController.navigationBar.frame.origin.y + self.navigationController.navigationBar.frame.size.height
  • Redwarp
    Redwarp about 10 years
    I have the same issue, the scrollview is the second subview in the main view because of a background image view. Did you find a workaround ?
  • Ömer Faruk Almalı
    Ömer Faruk Almalı about 10 years
    So why didn't u share the fix?
  • rvijay007
    rvijay007 about 10 years
    This did not work for me. First got an error that required me to call - (void) layoutSubviews in DidLayoutSubviews, and after that, my tableView insets were still not correct. Even tried switching the views around in your method, but still did not work.
  • Gavin Hope
    Gavin Hope over 9 years
    Note: if you're doing auto layout programatically, you might have something like id topGuide = self.topLayoutGuide; I found that having that line before adding the UITableView as the first subview to my UIViewController (in viewDidLoad) stopped the auto padding from working...
  • Cemen
    Cemen over 9 years
    Not always. You can get auto padding in non-UITableViewController (at least I've got it). Subviews order makes sense there. If your tableView is subview #0 in your controller's view, it will auto-adjust. Else, it will stay as is.
  • ideawu
    ideawu over 9 years
    @Cemen I have problem even if tableView is subview#0 when I set navigationBar.translucent = NO;. I works when navigationBar.translucent = YES;
  • Cemen
    Cemen over 9 years
    @ideawu it will not place tableView under navigation bar when it's opaque. Instead, it should resize whole controller.view to start from navigation bar bottom, AFAIR.
  • Andy Obusek
    Andy Obusek about 9 years
    This seems so promising and encouraging, but it did not work for me either.
  • valbu17
    valbu17 about 9 years
    I had this issue for like 2 days and couldn't figured it out.. This deserves like 50+
  • koen
    koen about 9 years
    Shouldn't it also call [super viewDidLayoutSubviews]; ?
  • Christopher Pickslay
    Christopher Pickslay about 9 years
    @Koen technically I suppose so. The default implementation is a no-op, so it would only matter if you have a custom intermediary superclass that also implements it.
  • Zeeshan
    Zeeshan over 8 years
    @Redwarp did you find the any way for your problem. I'm facing the same, I've a tableview as second subview (first is image view for background animation) in view controller. I want my tableview to start after navigation bar and cover the offset content on scroll to get the navigation bar blur effect. Please share, if you find any answers. Thanks.
  • Redwarp
    Redwarp over 8 years
    @Zeeshan what I do lately is to deactivate this automatic adjustment of content inset, and do it by hand: I put the scrollview behind the navigation bar, and have the content inside the scrollview start at 64 points. And I also shift the scroll bars by 64 points. Ultimately, I believe that everything that does stuff for you "automatically" can become your enemy, because it's hard to control what happens.
  • Cristi Băluță
    Cristi Băluță over 7 years
    It works but probably depends where you put the code. At first i put it in viewdidload and didn't work. Now it is called a little later after data is coming. My view under the tableview is a placeholder for empty tables. Not sure if there's a better practice but i need to have the pull to refresh functionality and the background to stay in place, that's why this setup.
  • Raj Pawan Gumdal
    Raj Pawan Gumdal over 7 years
    Beautiful and useful answer! It would have been great if Apple had forethought this and somehow provided this feature for not just UITableViewController or top most view of a view controller if it is scrollable but also to any possible scenarios where a scroll view can be deep inside the view hierarchy.