2

I have overridden collectionView:viewForSupplementaryElementOfKind:atIndexPath: to show a header view for my JSQMessagesViewController subclass in swift. This works really well, but the header view scrolls with the chat, and I would like to have it fixed on top.

Apple introduced in iOS 9 the sectionHeadersPinToVisibleBounds property, and this should have fixed header view, but for some reason this doesn't work in the JSQMessagesViewController subclass. I have added self.collectionView.collectionViewLayout.sectionHeadersPinToVisibleBounds = true to viewDidLoad()

Another solution I tried (from this post How to make Supplementary View float in UICollectionView as Section Headers do in UITableView plain style), was to subclass JSQMessagesCollectionViewFlowLayout, and this almost worked. When swiping up, the header view stays fixed on top, and the chat messages are pushed under the header view. But as soon as I try to swipe down, I receive the following error in my console when the app crashes:

*** Assertion failure in -[UICollectionViewData validateLayoutInRect:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit/UIKit-3512.60.12/UICollectionViewData.m:408
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'layout attributes for supplementary item at index path (<NSIndexPath: 0xc000000000000016> {length = 2, path = 0 - 0}) changed from <JSQMessagesCollectionViewLayoutAttributes: 0x13f65a730> index path: (<NSIndexPath: 0xc000000000000016> {length = 2, path = 0 - 0}); element kind: (UICollectionElementKindSectionHeader); frame = (0 0; 414 100); zIndex = 10;  to <JSQMessagesCollectionViewLayoutAttributes: 0x13f679920> index path: (<NSIndexPath: 0xc000000000000016> {length = 2, path = 0 - 0}); element kind: (UICollectionElementKindSectionHeader); frame = (0 10; 414 100); zIndex = 1024;  without invalidating the layout'

Have anyone had success fixing the header view in the same way for JSQMessagesViewController?

The methods I changed was shouldInvalidateLayoutForBoundsChange: where I returned YES,

and layoutAttributesForElementsInRect: which I changed to:

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    NSMutableArray *attributesInRect = [[super layoutAttributesForElementsInRect:rect] mutableCopy];

//    if (self.springinessEnabled) {
//        NSMutableArray *attributesInRectCopy = [attributesInRect mutableCopy];
//        NSArray *dynamicAttributes = [self.dynamicAnimator itemsInRect:rect];
//        
//        //  avoid duplicate attributes
//        //  use dynamic animator attribute item instead of regular item, if it exists
//        for (UICollectionViewLayoutAttributes *eachItem in attributesInRect) {
//            
//            for (UICollectionViewLayoutAttributes *eachDynamicItem in dynamicAttributes) {
//                if ([eachItem.indexPath isEqual:eachDynamicItem.indexPath]
//                    && eachItem.representedElementCategory == eachDynamicItem.representedElementCategory) {
//                    
//                    [attributesInRectCopy removeObject:eachItem];
//                    [attributesInRectCopy addObject:eachDynamicItem];
//                    continue;
//                }
//            }
//        }
//        
//        attributesInRect = attributesInRectCopy;
//    }
    UICollectionView * const cv = self.collectionView;
    CGPoint const contentOffset = cv.contentOffset;
    NSMutableIndexSet *missingSections = [NSMutableIndexSet indexSet];


    for (UICollectionViewLayoutAttributes *layoutAttributes in attributesInRect) {
        if ([layoutAttributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader]) {
            [missingSections removeIndex:layoutAttributes.indexPath.section];
        }
    }

    [attributesInRect enumerateObjectsUsingBlock:^(JSQMessagesCollectionViewLayoutAttributes *attributesItem, NSUInteger idx, BOOL *stop) {
        if (attributesItem.representedElementCategory == UICollectionElementCategoryCell) {
            [self jsq_configureMessageCellLayoutAttributes:attributesItem];
            [missingSections addIndex:attributesItem.indexPath.section];
        }
        else {
            attributesItem.zIndex = -1;
        }
    }];

    [missingSections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:idx];
        UICollectionViewLayoutAttributes *layoutAttributes = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:indexPath];
        [attributesInRect addObject:layoutAttributes];
    }];

    for (UICollectionViewLayoutAttributes *layoutAttributes in attributesInRect)  {
        if ([layoutAttributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader]) {
            NSInteger section = layoutAttributes.indexPath.section;
            NSInteger numberOfItemsInSection = [cv numberOfItemsInSection:section];
            NSIndexPath *firstCellIndexPath = [NSIndexPath indexPathForItem:0 inSection:section];
            NSIndexPath *lastCellIndexPath = [NSIndexPath indexPathForItem:MAX(0, (numberOfItemsInSection - 1)) inSection:section];
            NSIndexPath *firstObjectIndexPath = [NSIndexPath indexPathForItem:0 inSection:section];
            NSIndexPath *lastObjectIndexPath = [NSIndexPath indexPathForItem:MAX(0, (numberOfItemsInSection - 1)) inSection:section];
            UICollectionViewLayoutAttributes *firstObjectAttrs;
            UICollectionViewLayoutAttributes *lastObjectAttrs;
            if (numberOfItemsInSection > 0) {
                firstObjectAttrs = [self layoutAttributesForItemAtIndexPath:firstObjectIndexPath];
                lastObjectAttrs = [self layoutAttributesForItemAtIndexPath:lastObjectIndexPath];

            } else {
                firstObjectAttrs = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:firstObjectIndexPath];
                lastObjectAttrs = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionFooter atIndexPath:lastObjectIndexPath];
            }
            CGFloat headerHeight = CGRectGetHeight(layoutAttributes.frame);
            CGPoint origin = layoutAttributes.frame.origin;
            origin.y = MIN(MAX(contentOffset.y + cv.contentInset.top, (CGRectGetMinY(firstObjectAttrs.frame) - headerHeight)), (CGRectGetMaxY(lastObjectAttrs.frame) - headerHeight));
            layoutAttributes.zIndex = 1024;
            layoutAttributes.frame = (CGRect) {
                .origin = origin, .size = layoutAttributes.frame.size
            };
        }
    }



    return attributesInRect;
}
Community
  • 1
  • 1
Ivan C Myrvold
  • 498
  • 5
  • 15

1 Answers1

0

The code I presented above is OK. What caused the crash I think is because I had overridden the collectionView:collectionViewLayout:referenceSizeForHeaderInSection: function in my JSQMessagesViewController subclass. I commented out that function, and instead set the height in the kJSQMessagesLoadEarlierHeaderViewHeight constant in JSQMessagesLoadEarlierHeaderView. Yes, I hijacked the LoadEarlierHeaderView to show my own custom header view.

Now, the header view is pinned to the top, showing my custom headerview, and without any crash.

Ivan C Myrvold
  • 498
  • 5
  • 15