6

I'm trying to make a detail screen for a to-do list kind of app. Here's what the detail screen currently looks like:

detail screen

This is a UICollectionViewController, with a header. The header contains 2 UILabel objects, and a UITextView object. The layout of these objects is managed by a vertical UIStackView. A UIView is used to set a white background.

I'm having some difficulties in defining the height of this UICollectionReusableView at runtime. Any advice is appreciated.

pedrouan
  • 12,762
  • 3
  • 58
  • 74
Kelvin Lau
  • 6,373
  • 6
  • 34
  • 57
  • http://stackoverflow.com/a/33696385/6064629 maybe it will help you. – Himanshu Aug 29 '16 at 10:31
  • Thanks @himanshu. I'm still hoping there is a better answer than that because that solution basically instantiates an extra copy of the `UICollectionReusableView` and trashes it right after. – Kelvin Lau Aug 30 '16 at 08:47
  • @KelvinLau did you manage to figure anything out? I am not a big fan of the provided solution, because it requires XIB file. – Michael Feb 21 '17 at 12:11

2 Answers2

3

This is a bit of a hack, but seems to work.

    // showhere to keep a reference
    UICollectionReusableView * _cachedHeaderView;


    - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView 
               viewForSupplementaryElementOfKind:(NSString *)kind 
                                     atIndexPath:(NSIndexPath *)indexPath{

        if(!_cachedHeaderView){
            // dequeue the cell from storyboard
            _cachedHeaderView = 
         [collectionView dequeueReusableCellWithReuseIdentifier:@"header_cell" 
                                                   forIndexPath:indexPath];

            // set captions/images on the header etc...

            // tell the collectionview to redraw this section
          [self.collectionView reloadSections:[NSIndexSet 
                            indexSetWithIndex:indexPath.section]];
        }

        return _cachedHeaderView;
    }


        - (CGSize)collectionView:(UICollectionView *)collectionView 
                          layout:(UICollectionViewLayout*)collectionViewLayout        
 referenceSizeForHeaderInSection:(NSInteger)section{

        // once there is a reference ot the view, use it to figure out the height
        if(_cachedHeaderView){
            CGSize size = 
[_cachedHeaderView systemLayoutSizeFittingSize:collectionView.bounds.size 
                 withHorizontalFittingPriority:UILayoutPriorityRequired 
                       verticalFittingPriority:UILayoutPriorityDefaultLow];
            return size;

        }

        // a placeholder value just to get the dequeueReusableCellWithReuseIdentifier to work 
        return CGSizeMake(collectionView.bounds.size.width, 100);
    }

foundry
  • 31,615
  • 9
  • 90
  • 125
Friggles
  • 634
  • 5
  • 7
  • It seems, Self-Sizing cells satisfy mostly the very basic cases, there is still a need to resort to these kind of hacks for headers or footers. – Richard Topchii Mar 05 '18 at 08:17
0

Here is how you can handle custom UICollectionViewReusableView with auto layout without the XIB file.

  1. Implement the referenceSizeForHeaderInSection delegate method.
  2. In it, instantiate the view that you use as a header view.
  3. Set its visibility to hidden, to avoid flashing.
  4. Add the view to the collectionview's superview.
  5. Set it's layout using auto layout, to match the expected visual outcome of the header.
  6. Invoke setNeedsLayout and layoutIfNeeded
  7. Remove the view from the superview

CAUTION: I am not a big fan of this solution, as it adds the custom view to the collectionview's superview each time, to perform calculations. I didn't notice any performance issues though.

CAUTION #2: I'd treat it as a temporary solution, and migrate to self sizing supplementary views once they get published.

I am using PureLayout for autolayout purposes.

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {

    let header = CustomHeaderView()

        header.isHidden = true;
        self.view.addSubview(header)
        header.autoPinEdge(toSuperviewEdge: .leading)
        header.autoPinEdge(toSuperviewEdge: .trailing)
        header.autoPin(toTopLayoutGuideOf: self, withInset: 0)
        header.setupHeader(withData: self.data)
        header.setNeedsLayout()
        header.layoutIfNeeded()
        header.removeFromSuperview()

        return header.frame.size
}
Michael
  • 1,285
  • 18
  • 31