Blog coding and discussion of coding about JavaScript, PHP, CGI, general web building etc.

Saturday, January 7, 2017

Is it possible to use AutoLayout with UITableView's tableHeaderView?

Is it possible to use AutoLayout with UITableView's tableHeaderView?


Since I discovered AutoLayout I use it everywhere, now I'm trying to use it with a tableHeaderView.

I made a subclass of UIView added everything (labels etc...) I wanted with their constraints, then I added this CustomView to the UITableView'tableHeaderView.

Everything works just fine except the UITableView always displays above the CustomView, by above I mean the CustomView is under the UITableView so it can't be seen !

It seems that no matter what I do, the height of the UITableView'tableHeaderView is always 0 (so is the width, x and y).

My question : is it possible at all to accomplish this without setting the frame manually ?

EDIT : The CustomView'subview that I'm using has these constraints :

    _title = [[UILabel alloc]init];      _title.text = @"Title";      [self addSubview:_title];      [_title keep:[KeepTopInset rules:@[[KeepEqual must:5]]]]; // title has to stay at least 5 away from the supperview Top      [_title keep:[KeepRightInset rules:@[[KeepMin must:5]]]];      [_title keep:[KeepLeftInset rules:@[[KeepMin must:5]]]];      [_title keep:[KeepBottomInset rules:@[[KeepMin must:5]]]];  

I'm using a handy library 'KeepLayout' because writing constraints manually takes forever and way too many line for one single constraint but the methods are self-explaining.

And the UITableView has these constraints :

_tableView = [[UITableView alloc]init];  _tableView.translatesAutoresizingMaskIntoConstraints = NO;  _tableView.delegate = self;  _tableView.dataSource = self;  _tableView.backgroundColor = [UIColor clearColor];  [self.view addSubview:_tableView];  [_tableView keep:[KeepTopInset rules:@[[KeepEqual must:0]]]];// These 4 constraints make the UITableView stays 0 away from the superview top left right and bottom.  [_tableView keep:[KeepLeftInset rules:@[[KeepEqual must:0]]]];  [_tableView keep:[KeepRightInset rules:@[[KeepEqual must:0]]]];  [_tableView keep:[KeepBottomInset rules:@[[KeepEqual must:0]]]];    _detailsView = [[CustomView alloc]init];  _tableView.tableHeaderView = _detailsView;  

I don't know if I have to set some constraints directly on the CustomView, I think the height of the CustomView is determined by the constraints on the UILabel "title" in it.

EDIT 2: After another investigation it seems the height and width of the CustomView are correctly calculated, but the top of the CustomView is still at the same level than the top of the UITableView and they move together when I scroll.

Answer by rdelmar for Is it possible to use AutoLayout with UITableView's tableHeaderView?


I've been unable to add a header view using constraints (in code). If I give my view a width and/or a height constraint, I get a crash with the message saying:

 "terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Auto Layout still required after executing -layoutSubviews. UITableView's implementation of -layoutSubviews needs to call super."  

When I add a view in the storyboard to my table view, it shows no constraints, and it works fine as a header view, so I think that the placement of the header view isn't done using constraints. It doesn't seem to behave like a normal view in that regard.

The width is automatically the width of the table view, the only thing you need to set is the height -- the origin values are ignored, so it doesn't matter what you put in for those. For instance, this worked fine (as does 0,0,0,80 for the rect):

UIView *headerview = [[UIView alloc] initWithFrame:CGRectMake(1000,1000, 0, 80)];  headerview.backgroundColor = [UIColor yellowColor];  self.tableView.tableHeaderView = headerview;  

Answer by Ryan for Is it possible to use AutoLayout with UITableView's tableHeaderView?


My table header view is a UIView subclass - I created a single contentView UIView within the initializer, with its bounds the same as the table header view's frame and added all my objects as a subview of that.

Then add the constraints for your objects within the table header view's layoutSubviews method rather than within the initializer. That solved the crash.

- (id)initWithFrame:(CGRect)frame  {      self = [super initWithFrame:CGRectMake(0, 0, 0, 44.0)];      if (self) {          UIView *contentView = [[UIView alloc] initWithFrame:self.bounds];          contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth;            // add other objects as subviews of content view        }      return self;  }    - (void)layoutSubviews  {      [super layoutSubviews];        // remake constraints here  }  

Answer by redent84 for Is it possible to use AutoLayout with UITableView's tableHeaderView?


Accepted answer is a only useful for tables with a single section. For multi-section UITableView just make sure that your header inherits from UITableViewHeaderFooterView and you will be fine.

As an alternative, just embed your current header in the contentView of a UITableViewHeaderFooterView. Exactly like UITableViewCell works.

Answer by Jonathan for Is it possible to use AutoLayout with UITableView's tableHeaderView?


You can get autolayout to provide you with a size by using the systemLayoutSizeFittingSize method.

You can then use this to create the frame for your application. This technique works whenever you need to know the size of a view that uses autolayout internally.

The code in swift looks like

//Create the view  let tableHeaderView = CustomTableHeaderView()    //Set the content  tableHeaderView.textLabel.text = @"Hello world"    //Ask auto layout for the smallest size that fits my constraints      let size = tableHeaderView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)    //Create a frame      tableHeaderView.frame = CGRect(origin: CGPoint.zeroPoint, size: size)    //Set the view as the header      self.tableView.tableHeaderView = self.tableHeaderView  

Or in Objective-C

//Create the view  CustomTableHeaderView *header = [[CustomTableHeaderView alloc] initWithFrame:CGRectZero];    //Set the content  header.textLabel.text = @"Hello world";    //Ask auto layout for the smallest size that fits my constraints  CGSize size = [header systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];    //Create a frame  header.frame = CGRectMake(0,0,size.width,size.height);    //Set the view as the header    self.tableView.tableHeaderView = header  

It should also be noted that in this particular instance, overriding requiresConstraintBasedLayout in your subclass, does result in a layout pass being performed, however the results of this layout pass are ignored and the system frame set to the width of the tableView and 0 height.

Answer by Ben Packard for Is it possible to use AutoLayout with UITableView's tableHeaderView?


I asked and answered a similar question here. In summary, I add the header once and use it to find the required height. That height can then be applied to the header, and the header is set a second time to reflect the change.

- (void)viewDidLoad  {      [super viewDidLoad];        self.header = [[SCAMessageView alloc] init];      self.header.titleLabel.text = @"Warning";      self.header.subtitleLabel.text = @"This is a message with enough text to span multiple lines. This text is set at runtime and might be short or long.";        //set the tableHeaderView so that the required height can be determined      self.tableView.tableHeaderView = self.header;      [self.header setNeedsLayout];      [self.header layoutIfNeeded];      CGFloat height = [self.header systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;        //update the header's frame and set it again      CGRect headerFrame = self.header.frame;      headerFrame.size.height = height;      self.header.frame = headerFrame;      self.tableView.tableHeaderView = self.header;  }  

If you have multi-line labels, this also relies on the custom view setting the preferredMaxLayoutWidth of each label:

- (void)layoutSubviews  {      [super layoutSubviews];        self.titleLabel.preferredMaxLayoutWidth = CGRectGetWidth(self.titleLabel.frame);      self.subtitleLabel.preferredMaxLayoutWidth = CGRectGetWidth(self.subtitleLabel.frame);  }  

or perhaps more generally:

override func layoutSubviews() {      super.layoutSubviews()        for view in subviews {          guard let label = view as? UILabel where label.numberOfLines == 0 else { continue }          label.preferredMaxLayoutWidth = CGRectGetWidth(label.frame)      }  }  

Update January 2015

Unfortunately this still seems necessary. Here is a swift version of the layout process:

tableView.tableHeaderView = header  header.setNeedsLayout()  header.layoutIfNeeded()  let height = header.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height  var frame = header.frame  frame.size.height = height  header.frame = frame  tableView.tableHeaderView = header  

I've found it useful to move this into an extension on UITableView:

extension UITableView {      //set the tableHeaderView so that the required height can be determined, update the header's frame and set it again      func setAndLayoutTableHeaderView(header: UIView) {          self.tableHeaderView = header          header.setNeedsLayout()          header.layoutIfNeeded()          let height = header.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height          var frame = header.frame          frame.size.height = height          header.frame = frame          self.tableHeaderView = header      }  }  

Usage:

let header = SCAMessageView()  header.titleLabel.text = "Warning"  header.subtitleLabel.text = "Warning message here."  tableView.setAndLayoutTableHeaderView(header)  

Answer by Martin for Is it possible to use AutoLayout with UITableView's tableHeaderView?


Another solution is to dispatch the header view creation to the next main thread call:

- (void)viewDidLoad {      [super viewDidLoad];        // ....        dispatch_async(dispatch_get_main_queue(), ^{          _profileView = [[MyView alloc] initWithNib:@"MyView.xib"];          self.tableView.tableHeaderView = self.profileView;      });  }  

Note: It fix the bug when the loaded view has a fixed height. I haven't tried when the header height only depends on its content.

EDIT :

You can find a cleaner solution to this problem by implementing this function, and calling it in viewDidLayoutSubviews

- (void)viewDidLayoutSubviews {      [super viewDidLayoutSubviews];        [self sizeHeaderToFit];  }  

Answer by Marc-Alexandre Brub for Is it possible to use AutoLayout with UITableView's tableHeaderView?


I know this is an old post but After going through all the SO posts regarding this and passing a whole afternoon playing with this, I finally came up with a clean and yet very simple solution

First of all, My view hierarchy looks like this:

  1. Table View
    1. View tableHeaderView
      1. View with an outlet called headerView

Now inside the View (No.3), I set up all the constraints as I would normally including the bottom space to container. This will make the container (i.e. 3.View i.e. headerView) to size itself based on it's subviews and their constraints.

After that, I set the constraints between 3. View and 2. View to these:

  1. Top Space to container: 0
  2. Leading Space to container: 0
  3. Trailing Space to container: 0

Notice that I omit intentionally the bottom space intentionally.

Once all of this is done in the storyboard, everything that's left to do is paste those three lines of codes:

if (self.headerView.frame.size.height != self.tableView.tableHeaderView.frame.size.height) {      UIView *header = self.tableView.tableHeaderView;      CGRect frame = self.tableView.tableHeaderView.frame;      frame.size.height = self.headerView.frame.size.height + frame.origin.y;      header.frame = frame;      self.tableView.tableHeaderView = header;  }  

Answer by Phil for Is it possible to use AutoLayout with UITableView's tableHeaderView?


Code:

  extension UITableView {              func sizeHeaderToFit(preferredWidth: CGFloat) {              guard let headerView = self.tableHeaderView else {                return              }                headerView.translatesAutoresizingMaskIntoConstraints = false              let layout = NSLayoutConstraint(                item: headerView,                attribute: .Width,                relatedBy: .Equal,                toItem: nil,                attribute:                .NotAnAttribute,                multiplier: 1,                constant: preferredWidth)                headerView.addConstraint(layout)                let height = headerView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height              headerView.frame = CGRectMake(0, 0, preferredWidth, height)                headerView.removeConstraint(layout)              headerView.translatesAutoresizingMaskIntoConstraints = true                self.tableHeaderView = headerView            }    }  

Answer by David Nix for Is it possible to use AutoLayout with UITableView's tableHeaderView?


The following worked for me.

  1. Use a plain old UIView as the header view.
  2. Add subviews to that UIView
  3. Use autolayout on the subviews

The main benefit I see is limiting frame calculations. Apple should really update UITableView's API to make this easier.

Example using SnapKit:

let layoutView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.bounds.width, height: 60))  layoutView.backgroundColor = tableView.backgroundColor  tableView.tableHeaderView = layoutView    let label = UILabel()  layoutView.addSubview(label)  label.text = "I'm the view you really care about"  label.snp_makeConstraints { make in      make.edges.equalTo(EdgeInsets(top: 10, left: 15, bottom: -5, right: -15))  }  

Answer by HotJard for Is it possible to use AutoLayout with UITableView's tableHeaderView?


Strange things happens. systemLayoutSizeFittingSize works great for iOS9, but doesn't for iOS 8 in my case. So this problems solves quite easy. Just get link to the bottom view in header and in viewDidLayoutSubviews after super call update header view bounds by inserting height as CGRectGetMaxY(yourview.frame) + padding

UPD: The easiest solution ever: So, in header view place subview and pin it to left, right, top. In that subview place your subviews with auto-height constraints. After that give all the job to the autolayout (no calculation required)

- (void)viewDidLayoutSubviews {      [super viewDidLayoutSubviews];        CGFloat height = CGRectGetMaxY(self.tableView.tableHeaderView.subviews.firstObject.frame);      self.tableView.tableHeaderView.bounds = CGRectMake(0, 0, CGRectGetWidth(self.tableView.bounds), height);      self.tableView.tableHeaderView = self.tableView.tableHeaderView;  }  

As a result subview is expanding/shrinking like it should, at the end it calls viewDidLayoutSubviews. At the time we know the actual size of the view, so set headerView height and update it by re-assigning. Works like a charm!

Also works for footer view.

Answer by user1511613 for Is it possible to use AutoLayout with UITableView's tableHeaderView?


Tips: If you use method setAndLayoutTableHeaderView, you should update subviews's frame,so in this situation UILabel's preferredMaxLayoutWidth should call before systemLayoutSizeFittingSize called, do not call in layoutSubview.

code show

Answer by k06a for Is it possible to use AutoLayout with UITableView's tableHeaderView?


Extended this solution http://collindonnell.com/2015/09/29/dynamically-sized-table-view-header-or-footer-using-auto-layout/ for table footer view:

@interface AutolayoutTableView : UITableView    @end    @implementation AutolayoutTableView    - (void)layoutSubviews {      [super layoutSubviews];        // Dynamic sizing for the header view      if (self.tableHeaderView) {          CGFloat height = [self.tableHeaderView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;          CGRect headerFrame = self.tableHeaderView.frame;            // If we don't have this check, viewDidLayoutSubviews() will get          // repeatedly, causing the app to hang.          if (height != headerFrame.size.height) {              headerFrame.size.height = height;              self.tableHeaderView.frame = headerFrame;              self.tableHeaderView = self.tableHeaderView;          }            [self.tableHeaderView layoutIfNeeded];      }        // Dynamic sizing for the header view      if (self.tableFooterView) {          CGFloat height = [self.tableFooterView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;          CGRect footerFrame = self.tableFooterView.frame;            // If we don't have this check, viewDidLayoutSubviews() will get          // repeatedly, causing the app to hang.          if (height != footerFrame.size.height) {              footerFrame.size.height = height;              self.tableFooterView.frame = footerFrame;              self.tableFooterView = self.tableFooterView;          }            self.tableFooterView.transform = CGAffineTransformMakeTranslation(0, self.contentSize.height - footerFrame.size.height);          [self.tableFooterView layoutIfNeeded];      }  }    @end  

Answer by Ramon Vasconcelos for Is it possible to use AutoLayout with UITableView's tableHeaderView?


I saw a lot of methods here doing so much unnecessary stuff, but you don't need that much to use auto layout in the header view. You just have to create you xib file, put your constraints and instantiate it like this:

func loadHeaderView () {          guard let headerView = Bundle.main.loadNibNamed("CourseSearchHeader", owner: self, options: nil)?[0] as? UIView else {              return          }          headerView.autoresizingMask = .flexibleWidth          headerView.translatesAutoresizingMaskIntoConstraints = true          tableView.tableHeaderView = headerView      }  

Answer by Vincent for Is it possible to use AutoLayout with UITableView's tableHeaderView?


Share my approach.

UITableView+XXXAdditions.m

- (void)xxx_setTableHeaderView:(UIView *)tableHeaderView layoutBlock:(void(^)(__kindof UIView *tableHeaderView, CGFloat *containerViewHeight))layoutBlock {        CGFloat containerViewHeight = 0;        UIView *backgroundView = [[UIView alloc] initWithFrame:CGRectZero];        [backgroundView addSubview:tableHeaderView];        layoutBlock(tableHeaderView, &containerViewHeight);          backgroundView.frame = CGRectMake(0, 0, 0, containerViewHeight);          self.tableHeaderView = backgroundView;  }  

Usage.

[self.tableView xxx_setTableHeaderView:myView layoutBlock:^(__kindof UIView * _Nonnull tableHeaderView, CGFloat *containerViewHeight) {      *containerViewHeight = 170;        [tableHeaderView mas_makeConstraints:^(MASConstraintMaker *make) {        make.top.equalTo(@20);        make.centerX.equalTo(@0);        make.size.mas_equalTo(CGSizeMake(130, 130));      }];    }];  

Answer by Leszek S for Is it possible to use AutoLayout with UITableView's tableHeaderView?


In my case method with systemLayoutSizeFittingSize for some reason did not work. What worked for me is a modification of solution posted by HotJard (he's original solution also did not work in my case on iOS 8). What I needed to do is in header view place a subview and pin it to left, right, top (do not pin to bottom). Put everything using autolayout in that subview and in code do this:

- (void)viewDidLayoutSubviews  {      [super viewDidLayoutSubviews];      [self resizeHeaderToFitSubview];  }    - (void)resizeHeaderToFitSubview  {      UIView *header = self.tableView.tableHeaderView;      [header setNeedsLayout];      [header layoutIfNeeded];      CGFloat height = CGRectGetHeight(header.subviews.firstObject.bounds);      header.bounds = CGRectMake(0, 0, CGRectGetWidth(self.tableView.bounds), height);      self.tableView.tableHeaderView = nil;      self.tableView.tableHeaderView = header;  }  


Fatal error: Call to a member function getElementsByTagName() on a non-object in D:\XAMPP INSTALLASTION\xampp\htdocs\endunpratama9i\www-stackoverflow-info-proses.php on line 72

0 comments:

Post a Comment

Popular Posts

Powered by Blogger.