1

I have read many other threads on this and the Apple docs, but haven't found a solution yet for my particular problem.

My app uses a UITabBarController as the rootViewController, and in one of the tabs I have a UISegmentedControl in the navigationBar to switch between three child UITableViewControllers.

(In the real app two of the childVCs are a custom UIViewController, I'm just using three UITableViewControllers for the sample app).

The segmentedControl setup and the switching all works fine. The thing that goes wrong is that only the first UITableViewController is shown correctly. For the second and third one, part of the first cell is hidden under the navigationBar. When I click through all three, the first one is still ok.

I have made a little sample app to show what's going on, using very bright colors for demonstration purposes: https://www.dropbox.com/s/7pfutvn5jba6rva/SegmentedControlVC.zip?dl=0

Here is also some code (I'm not using storyboards):

// AppDelegate.m

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    FirstViewController *fvc = [[FirstViewController alloc] init];
    UINavigationController *firstNavigationController = [[UINavigationController alloc] initWithRootViewController: fvc];

    SecondViewController *svc = [[SecondViewController alloc] init];
    UINavigationController *secondNavigationController = [[UINavigationController alloc] initWithRootViewController: svc];

    // Initialize tab bar controller, add tabs controllers
    UITabBarController *tabBarController = [[UITabBarController alloc] init];
    tabBarController.viewControllers = @[firstNavigationController, secondNavigationController];

    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.rootViewController = tabBarController;
    [self.window makeKeyAndVisible];

    return YES;
}


// FirstViewController.m

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    self.title = @"One";
    self.view.backgroundColor = [UIColor orangeColor];

    UITableViewController *vc1 = [[UITableViewController alloc] init];
    UITableViewController *vc2 = [[UITableViewController alloc] init];
    UITableViewController *vc3 = [[UITableViewController alloc] init];

    vc1.view.backgroundColor = [UIColor redColor];
    vc2.view.backgroundColor = [UIColor blueColor];
    vc3.view.backgroundColor = [UIColor greenColor];

    self.viewControllers = @[vc1, vc2, vc3];
    self.segmentTitles = @[@"Red", @"Blue", @"Green"];

    self.segmentedControl = [[UISegmentedControl alloc] initWithItems: self.segmentTitles];
    [self.segmentedControl addTarget: self
                              action: @selector(segmentClicked:)
                    forControlEvents: UIControlEventValueChanged];

    self.navigationItem.titleView = self.segmentedControl;

    self.segmentedControl.selectedSegmentIndex = 0;

 // set the first child vc:  
    UIViewController *vc = self.viewControllers[0];

    [self addChildViewController: vc];
    vc.view.frame = self.view.bounds;
    [self.view addSubview: vc.view];
    self.currentVC = vc;
}

- (void)segmentClicked:(id)sender
{
    if (sender == self.segmentedControl)
    {
        NSUInteger index = self.segmentedControl.selectedSegmentIndex;
        [self loadViewController: self.viewControllers[index]];
    }
}

- (void)loadViewController:(UIViewController *)vc
{
    [self addChildViewController: vc];

    [self transitionFromViewController: self.currentVC
                      toViewController: vc
                              duration: 1.0
                               options: UIViewAnimationOptionTransitionFlipFromBottom
                            animations: ^{
                                [self.currentVC.view removeFromSuperview];
                                vc.view.frame = self.view.bounds;
                                [self.view addSubview: vc.view];
                            } completion: ^(BOOL finished) {
                                [vc didMoveToParentViewController: self];
                                [self.currentVC removeFromParentViewController];
                                self.currentVC = vc;
                            }
     ];
}

So obviously my question is, why does this happen, and what can I do to fix it?

Edit: adding screenshots.

First VC Second VC Third VC

EDIT: Based on the answer below I changed the code in the animation block to:

[self.currentVC.view removeFromSuperview];

if ([vc.view isKindOfClass: [UIScrollView class]])
{
    UIEdgeInsets edgeInsets = UIEdgeInsetsMake(self.topLayoutGuide.length, 0, self.bottomLayoutGuide.length, 0);
    [UIView performWithoutAnimation: ^{
      vc.view.frame = self.view.bounds;
       ((UIScrollView *)vc.view).contentInset = edgeInsets;
         ((UIScrollView *)vc.view).scrollIndicatorInsets = edgeInsets;
     }];
  }
   else
   {
       vc.view.frame = self.view.bounds;
   }

   [self.view addSubview: vc.view];

Now it works. I'm going to try this with a custom UIViewController as well.

koen
  • 5,383
  • 7
  • 50
  • 89
  • Post some screenshots of the problem. – Léo Natan Aug 01 '15 at 16:35
  • I've added the screenshots, you can see the separator lines of the 2nd and 3rd table are higher. – koen Aug 01 '15 at 16:43
  • Do not calculate the inset by hand. Instead, use the `topLayoutGuide.length` of the controller. – Léo Natan Aug 01 '15 at 18:03
  • About animation, you can use `[UIView performWithoutAnimation:]` to set the insets without animations. – Léo Natan Aug 01 '15 at 18:04
  • Hmm, if I use `topLayoutGuide.length`, then `viewDidLayoutSubviews` is only called the first time (for the red VC), and my problem is back. – koen Aug 01 '15 at 18:15
  • Something is strange. `viewDidLayoutSubviews` should not be used to set subsequent views' insets, because indeed it should not be called later. But you say, setting them in `loadViewController:` does not work, which is strange. If you do set them only in `loadViewController:` and you see it badly, what happens if you attempt to scroll with the finger on a bad table view? Does it allow you to scroll the cell into full view? If so, you need to set the `contentOffset` as well to be `{0, contentInset.top}`. – Léo Natan Aug 01 '15 at 18:20
  • Ah, I misunderstood, I moved the insets code to the animation block, and `topLayoutGuide.length` now works. I had to tweak the code in the animation block some more to get it to work, see my edit above. – koen Aug 01 '15 at 18:33

1 Answers1

1

The issue is that you do not set the correct content inset to each table view. The system attempts to do it for you, but I guess your setup is too complex for it, and it only does it for the first tableview that is loaded in viewDidLoad. In your loadViewController: method, when replacing the currently displayed view, make sure to set both the contentInset and scrollIndicatorInsets to the values of the previous view. I think the system will manage to set the correct insets later, in case you rotate to landscape. Try it. If it doesn't, you will need to do it on your own in viewDidLayoutSubviews.

Léo Natan
  • 56,823
  • 9
  • 150
  • 195
  • Setting the `contentInset` and `scrollIndicatorInsets` didn't work. I'm going to look into `viewDidLayoutSubviews`. But that would require to subclass `UITableViewController`, correct? – koen Aug 01 '15 at 16:51
  • No, I meant in the container controller. – Léo Natan Aug 01 '15 at 16:54