Cameron Askew
Cameron Askew

Reputation: 1413

Placing UIViews one after another

I have a view controller which has an array of subviews. These subviews are custom UIViews to be placed one after another vertically. The subviews are not consistent in height; their height is based on the data used when instantiating them. Additionally, I want to support device rotations so that the subview's UILabels will expand horizontally in landscape mode and be shorter.

I'm having lots of trouble accomplishing this in a good way. When the view controller is laying out it's subviews, I feel like I have no clean way of figuring out how tall each subview is (because I have instantiated them at this point, but not set their frames). Please refer to my code below to understand my frustration.

I would really like it if the layoutSubviews method of the subviews were called before the viewWillLayoutSubviews method of the view controller, but that is not the case.

MyViewController.m

....

- (void)initWithDataObjects:(NSArray*)dataObjects
{
    _dataObjects = dataObjects;
}

- (void)viewDidLoad
{
    mySubviews = [[NSMutableArray alloc] init];

    for (DataObject* do in _dataObjects) {
        MyCustomView *customView = [[MyCustomView alloc] initWithDataObject:do];
        [mySubviews addObject:customView];
        [self.view addSubview:customView];
    }
}

- (void)viewWillLayoutSubviews
{
    int currentHeight = 0;
    for (MyCustomView *customView in mySubviews) {

        int subviewHeight = customView.frame.size.height;
        // PROBLEM: subviewHeight is 0 because the subview hasn't called layoutSubviews yet..

        [customView setFrame:CGRectMake(0, currentHeight, self.view.frame.size.width, subviewHeight)];
        currentHeight += subviewHeight;
    }
}

Upvotes: 0

Views: 160

Answers (1)

mahboudz
mahboudz

Reputation: 39376

I'm editing and summarizing based on the comments that came below:

Here is the order these methods get called:

2013-10-24 01:05:11.391 MyTestApp[34166:70b] -[MyViewController viewDidLoad]
2013-10-24 01:05:11.395 MyTestApp[34166:70b] -[MyViewController addMySubViews]
2013-10-24 01:05:11.395 MyTestApp[34166:70b] -[view1 didMoveToSuperview]
2013-10-24 01:05:11.395 MyTestApp[34166:70b] -[view2 didMoveToSuperview]
2013-10-24 01:05:11.395 MyTestApp[34166:70b] -[view3 didMoveToSuperview]
2013-10-24 01:05:11.398 MyTestApp[34166:70b] -[MyViewController viewWillLayoutSubviews]
2013-10-24 01:05:11.398 MyTestApp[34166:70b] -[MyViewController viewDidLayoutSubviews]
2013-10-24 01:05:11.398 MyTestApp[34166:70b] -[view1 layoutSubviews]
2013-10-24 01:05:11.398 MyTestApp[34166:70b] -[view2 layoutSubviews]
2013-10-24 01:05:11.398 MyTestApp[34166:70b] -[view3 layoutSubviews]

Your best bet is to take your sizing code out of your view's layoutSubviews, and put it in a separate view method (maybe mySize) and call that method from the viewcontroller in viewWillLayoutSubviews, and you can also call it from the view's layoutSubviews (so you don't have duplicate code).

This may be what it would look like (viewWillLayoutSubviews is calling all the subviews' mySize methods):

2013-10-24 01:38:10.501 MyTestApp[34850:70b] -[MyViewController viewDidLoad]
2013-10-24 01:38:10.501 MyTestApp[34850:70b] -[MyViewController addMySubViews]
2013-10-24 01:38:10.501 MyTestApp[34850:70b] -[view1 didMoveToSuperview]
2013-10-24 01:38:10.501 MyTestApp[34850:70b] -[view2 didMoveToSuperview]
2013-10-24 01:38:10.502 MyTestApp[34850:70b] -[view3 didMoveToSuperview]
2013-10-24 01:38:24.396 MyTestApp[34850:70b] -[MyViewController viewWillLayoutSubviews]
2013-10-24 01:38:24.396 MyTestApp[34850:70b] -[view1 mySize]
2013-10-24 01:38:24.396 MyTestApp[34850:70b] -[view2 mySize]
2013-10-24 01:38:24.396 MyTestApp[34850:70b] -[view3 mySize]
2013-10-24 01:38:24.397 MyTestApp[34850:70b] -[MyViewController viewDidLayoutSubviews]
2013-10-24 01:38:24.397 MyTestApp[34850:70b] -[view1 layoutSubviews]
2013-10-24 01:38:24.397 MyTestApp[34850:70b] -[view2 layoutSubviews]
2013-10-24 01:38:24.397 MyTestApp[34850:70b] -[view3 layoutSubviews]

BTW, if you change the frame of your subviews from within the viewcontroller, it will trigger a needsLayout on the subview, and an eventual layoutSubviews.

Upvotes: 1

Related Questions