Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting subViews' frames in Storyboard

I just want (for now) to get the dimensions of a subview on the view controller's instantiation.

[This is a reduction to as simple a case I can find of a previous question. I am trying to figure out why subViews of scenes in Storyboards are not behaving the way I expect, which is to say: like XIBs do - I just want to get dimensions of my subviews before anything is actually drawn to the screen]

To condense the problem down to a new, clean project, I do this:

  • create a new, single view project with "using Storyboard" checked
  • add a single UIView to the default existing MainStoryboard_iPad.storyboard (and change its background to green to make it more easily seen - beyond shrinking the dimensions some, this is the only change I make from the default UIView I drag onto the scene)
  • option-click the ViewController.h file in the navigator to bring it up in its own frame underneath the Storyboard frame and insert a pair of braces underneath the @interface directive
  • control-click-and-drag from the UIView in the Storyboard to ViewController.h and tell it to name the outlet firstViewFirstSubView

So we now have for ViewController.h:

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController
{
    IBOutlet UIView *firstViewFirstSubView;
}
@end

Then, I add this method to the ViewController.m:

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    NSLog(@"View Controller will appear. firstViewFirstSubView: %@ ", firstViewFirstSubView);
    NSLog(@"subView's dimmensions: %f by %f at %f, %f", firstViewFirstSubView.frame.size.width,
      firstViewFirstSubView.frame.size.height,
      firstViewFirstSubView.frame.origin.x,
      firstViewFirstSubView.frame.origin.y);
}

At this point, I expect to be able to get the dimensions of the UIView subview. I get all 0s, though:

2012-11-15 15:21:00.743 StoryboardViewBounds[11132:c07] View Controller will appear. firstViewFirstSubView: <UIView: 0x9379730; frame = (0 0; 0 0); autoresize = TM+BM; layer = <CALayer: 0x9378e40>> 
2012-11-15 15:21:00.744 StoryboardViewBounds[11132:c07] subView's dimmensions: 0.000000 by 0.000000 at 0.000000, 0.000000

What am I doing wrong? It seems like this should be very straightforward, so I think I must be missing something simple, whether throwing the right switch in the Storyboard editor or implementing a method that Storyboard needs.

like image 443
Todd Masco Avatar asked Dec 04 '22 13:12

Todd Masco


2 Answers

Those dimensions are calculated and set when the call to layoutSubviews is made, which occurs after viewWillAppear. After layoutSubviews is called on the UIVIew you can get the dimensions.

Look at implementing this method in your view controller: viewDidLayoutSubviews. At this point you should be able to get dimensions of your subviews. Note that this call is available starting in iOS 5.0 but since you reference storyboards I assume you are working at or above that anyway.

- (void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];

    NSLog(@"View Controller did layout subviews. firstViewFirstSubView: %@ ", firstViewFirstSubView);
    NSLog(@"subView's dimmensions: %f by %f at %f, %f", firstViewFirstSubView.frame.size.width,
      firstViewFirstSubView.frame.size.height,
      firstViewFirstSubView.frame.origin.x,
      firstViewFirstSubView.frame.origin.y);
}
like image 144
foggzilla Avatar answered Dec 24 '22 05:12

foggzilla


There is a Related issue that I am compelled to address, which hopefully will save someone else a day of debugging:

I am finding out that in Storyboard:

  1. segue-push does not cause subViews to be laid out at -viewDidLayoutSubviews (they are instead laid out at some other time just before -viewDidAppear).
    Whereas...
  2. segue-modal and [navController.storyboard presentViewController:] does cause subViews to be laid out at -viewDidLayoutSubviews.

The solution is to put [self.mySubView layoutSubviews] within the viewController's -viewDidLayoutSubviews method in order to manually load the subViews within mySubView.

My case was that I had a custom gradient button that was not properly initializing it's visual appearance.
The button was contained within a scrollView that contained a CustomView which contained the custom gradient button.
So, basically... a button within a view within a scrollView.

The app starts out with a UINavigationController having some other ViewController1 loaded.
ViewController1 contains a button which, when pressed, launches a storyboard segue-push to ViewController2.
(this was arranged in storyboard by control-dragging from the button in ViewController1 to ViewController2).

ViewController2 contains the scrollview/customView/customButton.
In ViewController2's -viewDidLayoutSubviews, I initialize the customButton which is a custom Gradient Button having it's own .h/.m files.
GradientButton.m has an initLayers method which configures it graphically and requires the bounds/frame property of the button to be initialized.
However...
in ViewController2's -viewDidLayoutSubviews, self.customButton had a frame of 0,0,0,0.

A few notes:

  1. Yes, I am calling [super viewDidLayoutSubviews] at the beginning of -viewDidLayoutSubviews.
  2. The only view that has been laid out at -viewDidLayoutSubviews is self.view (ViewController2's initial view, as connected in Storyboard's connections panel. in my case - self.view is a scrollView).

To resolve:

  1. in ViewController2, I created an outlet self.bottomView for the view that contained self.customButton.
  2. in ViewController2's -viewDidLayoutSubviews, I call [self.bottomView layoutSubviews]
    • This will then cause customButton's frame/bounds to be properly set.
  3. Next, I call [self.customButton initLayers] which now properly initializes my custom gradient button.

A few notes about ViewController2's -viewDidLayoutSubviews: calling [self.view layoutSubviews] causes bottomView's frame to be initialized, but NOT customButton's frame.
In terms of a view hierarchy, -layoutSubviews applies only to the subViews of self.view and not to any subViews of those subViews.
This only appears to be the case with storyboard segue-push.

The storyboard segue-modal and the programmatic [navController presentViewController] both seem to correctly initialize all levels of the view hierarchy (all "subViews of subViews") by the time -viewDidLayoutSubviews is called.

like image 40
ObjectiveTC Avatar answered Dec 24 '22 04:12

ObjectiveTC