38

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
  • 1,222
  • 1
  • 11
  • 15

7 Answers7

55

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

Tony Fung
  • 1,222
  • 1
  • 11
  • 15
  • 2
    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 ? – Redwarp Feb 18 '14 at 18:42
  • 17
    So why didn't u share the fix? – Ömer Faruk Almalı Apr 21 '14 at 15:39
  • 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... – Gavin Hope Aug 25 '14 at 07:58
  • 1
    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. – Cemen Oct 22 '14 at 10:26
  • @Cemen I have problem even if tableView is subview#0 when I set `navigationBar.translucent = NO;`. I works when `navigationBar.translucent = YES;` – ideawu Jan 06 '15 at 05:07
  • @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. – Cemen Jan 15 '15 at 20:23
  • @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. – Zeeshan Aug 10 '15 at 17:12
  • @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. – Redwarp Aug 11 '15 at 16:17
47

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
        };
    }
}
Christopher Pickslay
  • 17,523
  • 6
  • 79
  • 92
  • 3
    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` – DaGaMs Jan 01 '14 at 14:49
  • I had this issue for like 2 days and couldn't figured it out.. This deserves like 50+ – valbu17 Feb 13 '15 at 04:20
  • Shouldn't it also call `[super viewDidLayoutSubviews];` ? – koen Apr 19 '15 at 17:31
  • @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. – Christopher Pickslay Apr 24 '15 at 19:05
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.

awulf
  • 2,362
  • 1
  • 15
  • 12
  • 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. – rvijay007 Apr 29 '14 at 07:30
  • This seems so promising and encouraging, but it did not work for me either. – Andy Obusek Feb 04 '15 at 19:39
  • 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. – Cristi Băluță Sep 02 '16 at 09:05
2

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.

Ell Neal
  • 6,014
  • 2
  • 29
  • 54
  • 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. – Raj Pawan Gumdal Sep 09 '16 at 20:09
1

@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
}
Avt
  • 16,927
  • 4
  • 52
  • 72
0

Yeah - a bit annoying.

I have a nib with a single tableview within the main view, not using autolayout. There is a tabbar, navigationbar and a statusbar and the app needs to work back to 5.0. In Interface builder that neat 'see it in iOS7 and iOS6.1 side-by-side' thing works, showing the tables neatly fitting (once the iOS6/7 deltas were set properly).

However running on a device or simulator there was a large gap at the top of the table, which was as a result of a content inset (which pretty much matched by iOS6/7 vertical delta) that was set to zero in the nib.

Only solution I got was in viewWillAppear to put in [_tableView setContentInset:UIEdgeInsetsZero].

Another ugly hack with a pretty on-screen result.....

Peter
  • 1,022
  • 9
  • 12
  • 1
    Did you try to set the self.edgesForExtendedLayout = UIRectEdgeNone; It can disable the content inset auto padding. – Tony Fung Oct 07 '13 at 02:59
-5
[self.navigationController setNavigationBarHidden:YES animated:YES]; 

in:

- (void)viewDidLoad 
Hiren
  • 12,720
  • 7
  • 52
  • 72
carlos
  • 1