Siddharthan Asokan
Siddharthan Asokan

Reputation: 4441

Tableheader view autolayout issue

I'm loading a custom UIView's XIB file as a header view of a uitableview inside a view controller.

The file owner for the xib file is the viewcontroller. I have both the viewcontroller's & the uiview's interface declared inside the uiviewcontroller.

ViewController.h

      @class ZeroStateView;

      @interface ViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>

      @property (nonatomic, weak) IBOutlet CustomUITableView *tableView;
      @property (nonatomic,strong) NSMutableArray *dataArray;
      @property (weak, nonatomic) IBOutlet ZeroStateView *zeroStateView;


      @end


     @interface ZeroStateView : UIView
     @property (weak, nonatomic) IBOutlet AutoLayoutLabel *titleLabel;
     @property (weak, nonatomic) IBOutlet UIImageView *titleIcon;

     - (void)updateView;

     @end

ViewController.m

    - (void)prepareHeaderViewForZeroState{
            ZeroStateView *sizingView = [ZeroStateView new];
            [[[NSBundle mainBundle] loadNibNamed:@"ZeroStateView" owner:self options:nil] objectAtIndex:0];
            sizingView = self.zeroStateView;
           [sizingView updateView];

           self.tableView.tableHeaderView = sizingView;

           UIView *headerView = self.tableView.tableHeaderView;



           CGFloat height = [headerView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;

         CGRect headerFrame = headerView.frame;
         headerFrame.size.height = height;
         headerView.frame = headerFrame;


         self.tableView.tableHeaderView = headerView;
     }

    @end

    @implementation ZeroStateView

    -(void)updateView{
      self.titleIcon.alpha = 0.5;

      UIFontDescriptor *titleFontDescriptor = [UIFontDescriptor preferredFontDescriptorWithTextStyle:UIFontTextStyleSubheadline];

      self.titleLabel.text =  @"This is a long text message and its really long. This is a long text message and its really long. This is a long text message and its really long. This is a long text message and its really long. This is a long text message and its really long. This is a long text message and its really long. This is a long text message and its really long. This is a long text message and its really long. ";

     }

The AutolayoutLabel class the following method overridden:

   - (void)setBounds:(CGRect)bounds {
      [super setBounds:bounds];

      // For multiline label, preferredMaxLayoutWidth always matches the frame width
      if (self.numberOfLines == 0 && bounds.size.width != self.preferredMaxLayoutWidth) {
          self.preferredMaxLayoutWidth = self.bounds.size.width;
          [self setNeedsUpdateConstraints];
      }
   }

The height calculated by the systemLayoutSizeFittingSize:UILayoutFittingCompressedSize returns 0. As a result I get the following view as the table header view: Header view with height 0

When I added the actual height as below, the uilabel overflows. I'm expecting the uiview to grow as the label height grows.

 headerFrame.size.height = self.sizingView.frame.size.height;

TableHeaderView not being resized properly

Here is the screen capture of that UIViews constraints:

ZeroState view XIB file with constraints

What do I miss here? Can someone point me out?

Update I created a sample project for you guys to check on whats exactly issue is.

Upvotes: 5

Views: 937

Answers (5)

Jatin Patel - JP
Jatin Patel - JP

Reputation: 3733

As you call prepareHeaderViewForZeroState method from viewwillappear. At that point you layout is not calculate. so force layout to calculate before calling systemLayoutSizeFittingSize method to calculate height of cell. Here are the code that you need to write before calling systemLayoutSizeFittingSize.

UIView *header = self.tableView.tableHeaderView;

    [header setNeedsLayout];
    [header layoutIfNeeded];

Edit :

You just left 1 constraints in ZeroStateView.xib. that is Bottom Space to : Superview. kindly refer screenshot.

enter image description here

Output :

enter image description here

Here you have Updated code

Hope this help you.

Upvotes: 1

keithbhunter
keithbhunter

Reputation: 12334

I reimplemented what you had so far in a different way. To start, I removed the UIView in ZeroStateView.xib that the UIImageView and UILabel were embedded in. The base of the xib is already a UIView, so it is unnecessary to add another UIView to it.

Next I changed the constraints around. I don't remember exactly what constraints I changed, so I will just list them out here:

  1. Constraints for UIImageView
    • Align Center X to Superview
    • Width = 60
    • Height = 56
    • Top Space to Superview = 37
    • Bottom Space to UILabel = 31
  2. Constraints for UILabel
    • Left Space to Superview = 15
    • Right Space to Superview = 15
    • Bottom Space to Superview = 45
    • Top Space to UIImageView = 31

Onto the code. In ViewController.h, the IBOutlet wasn't doing anything as far as I could tell, so I changed that property to read @property (strong, nonatomic) ZeroStateView *zeroStateView;

Now the important changes: ViewController.m. There are two UITableViewDelegate methods that will replace prepareHeaderViewForZeroState. In viewDidLoad, initialize the zeroStateView and set the table view's delegate to self.

- (void)viewDidLoad
{
    //...

    // Load the view
    self.zeroStateView = [[[NSBundle mainBundle] loadNibNamed:@"ZeroStateView" owner:self options:nil] firstObject];
    [self.zeroStateView updateView];
    self.zeroStateView.backgroundColor = [UIColor darkGrayColor];

    // Set self for table view delegate for -heightForHeaderInSection: and viewForHeaderInSection:
    self.dataTable.delegate = self;
}

Now that we are the table view's delegate, we get two method calls that will allow us to customize the header view and set its height appropriately.

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
    // This will set the header view to the zero state view we made in viewDidLoad
    return self.zeroStateView;
}

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
    // sizeToFit describes the size that the label can fit itself into.
    // So we are saying that the label can use the width of the view and infinite height.
    CGSize sizeToFit = CGSizeMake(self.view.frame.size.width, MAXFLOAT);

    // Then we ask the label to tell us how it can fit in that size.
    // The label will respond with the width of the view and however much height it needs
    // to display its text. This is the magic of how it grows vertically.
    CGSize size = [self.zeroStateView.titleLabel sizeThatFits:sizeToFit];

    // Add the height the label needs to the overall zero state view. This should be changed
    // to the height of the UIImage + the height we just got + the whitespace above and below
    // each of these views. You can handle that part.
    return self.zeroStateView.frame.size.height + size.height;
}

I uploaded my changes to Dropbox here.

Upvotes: 2

lensovet
lensovet

Reputation: 5640

There are a few things here that are confusing to me that might be complicating things.

  1. Why are you subclassing the label?
  2. The constraints don't really make sense.
    • Why does the label have two height constraints, a constant and a ≥ constraint? Remove them both, you don't need either.
    • Also, your vertical space to the bottom of the container is ≥ as well. Why? As it stands, your custom view doesn't have a defined height, which could be why the fitting size returns a height of zero.

Once you resolve those issues, let me know if you have better luck.

Upvotes: 0

A C
A C

Reputation: 729

I'm not sure but you could check if you have a leading,trailing,top and bottom constraints for both the UIImage and the label with reference to the superview.

Edit: Add the width constraint before getting the systemLayoutSize

NSLayoutConstraint *tempWidthConstraint =
[NSLayoutConstraint constraintWithItem:self.contentView
                             attribute:NSLayoutAttributeWidth
                             relatedBy:NSLayoutRelationEqual
                                toItem:nil
                             attribute:NSLayoutAttributeNotAnAttribute
                            multiplier:1.0
                              constant:CGRectGetWidth(window.frame)];

widthConstraint.constant = tempWidthConstraint.constant;
[self.contentView addConstraint:tempWidthConstraint];

CGSize fittingSize = [self.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
CGFloat height = fittingSize.height +1;
[self.contentView removeConstraint:tempWidthConstraint];

Upvotes: 0

pteofil
pteofil

Reputation: 4163

Just changing the size of the frame of the tableHeaderView, doesn't change it's size. You have to set it again to the tableView to force a reload.

You have to call this again self.tableView.tableHeaderView = headerView; after setting the new frame size.

Upvotes: 1

Related Questions