mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
0c6a42003f
// FREEBIE
172 lines
5.3 KiB
Objective-C
172 lines
5.3 KiB
Objective-C
//
|
||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||
//
|
||
|
||
#import "ConversationViewLayout.h"
|
||
#import "UIView+OWS.h"
|
||
|
||
NS_ASSUME_NONNULL_BEGIN
|
||
|
||
@interface ConversationViewLayout ()
|
||
|
||
@property (nonatomic) CGSize contentSize;
|
||
|
||
@property (nonatomic, readonly) NSMutableDictionary<NSNumber *, UICollectionViewLayoutAttributes *> *itemAttributesMap;
|
||
|
||
// This dirty flag may be redundant with logic in UICollectionViewLayout,
|
||
// but it can't hurt and it ensures that we can safely & cheaply call
|
||
// prepareLayout from view logic to ensure that we always have a¸valid
|
||
// layout without incurring any of the (great) expense of performing an
|
||
// unnecessary layout pass.
|
||
@property (nonatomic) BOOL hasLayout;
|
||
|
||
@property (nonatomic) int contentWidth;
|
||
|
||
@end
|
||
|
||
#pragma mark -
|
||
|
||
@implementation ConversationViewLayout
|
||
|
||
- (instancetype)init
|
||
{
|
||
if (self = [super init]) {
|
||
_itemAttributesMap = [NSMutableDictionary new];
|
||
}
|
||
|
||
return self;
|
||
}
|
||
|
||
- (void)invalidateLayout
|
||
{
|
||
[super invalidateLayout];
|
||
|
||
[self clearState];
|
||
}
|
||
|
||
- (void)invalidateLayoutWithContext:(UICollectionViewLayoutInvalidationContext *)context
|
||
{
|
||
[super invalidateLayoutWithContext:context];
|
||
|
||
[self clearState];
|
||
}
|
||
|
||
- (void)clearState
|
||
{
|
||
self.contentSize = CGSizeZero;
|
||
[self.itemAttributesMap removeAllObjects];
|
||
self.hasLayout = NO;
|
||
}
|
||
|
||
- (void)prepareLayout
|
||
{
|
||
[super prepareLayout];
|
||
|
||
id<ConversationViewLayoutDelegate> delegate = self.delegate;
|
||
if (!delegate) {
|
||
OWSFail(@"%@ Missing delegate", self.logTag);
|
||
[self clearState];
|
||
return;
|
||
}
|
||
|
||
if (self.collectionView.bounds.size.width <= 0.f || self.collectionView.bounds.size.height <= 0.f) {
|
||
OWSFail(
|
||
@"%@ Collection view has invalid size: %@", self.logTag, NSStringFromCGRect(self.collectionView.bounds));
|
||
[self clearState];
|
||
return;
|
||
}
|
||
|
||
if (self.hasLayout) {
|
||
return;
|
||
}
|
||
self.hasLayout = YES;
|
||
|
||
// TODO: Remove this log statement after we've reduced the invalidation churn.
|
||
DDLogVerbose(@"%@ prepareLayout", self.logTag);
|
||
|
||
const int vInset = 8;
|
||
const int hInset = 10;
|
||
const int vSpacing = 5;
|
||
const int viewWidth = (int)floor(self.collectionView.bounds.size.width);
|
||
const int contentWidth = (int)floor(viewWidth - 2 * hInset);
|
||
self.contentWidth = contentWidth;
|
||
|
||
NSArray<id<ConversationViewLayoutItem>> *layoutItems = self.delegate.layoutItems;
|
||
|
||
CGFloat y = vInset + self.delegate.layoutHeaderHeight;
|
||
CGFloat contentBottom = y;
|
||
BOOL isRTL = self.collectionView.isRTL;
|
||
|
||
NSInteger row = 0;
|
||
for (id<ConversationViewLayoutItem> layoutItem in layoutItems) {
|
||
CGSize layoutSize = [layoutItem cellSizeForViewWidth:viewWidth contentWidth:contentWidth];
|
||
|
||
layoutSize.width = MIN(viewWidth, floor(layoutSize.width));
|
||
layoutSize.height = floor(layoutSize.height);
|
||
CGRect itemFrame;
|
||
switch (layoutItem.layoutAlignment) {
|
||
case ConversationViewLayoutAlignment_Incoming:
|
||
case ConversationViewLayoutAlignment_Outgoing: {
|
||
BOOL isIncoming = layoutItem.layoutAlignment == ConversationViewLayoutAlignment_Incoming;
|
||
BOOL isLeft = isIncoming ^ isRTL;
|
||
if (isLeft) {
|
||
itemFrame = CGRectMake(hInset, y, layoutSize.width, layoutSize.height);
|
||
} else {
|
||
itemFrame
|
||
= CGRectMake(viewWidth - (hInset + layoutSize.width), y, layoutSize.width, layoutSize.height);
|
||
}
|
||
break;
|
||
}
|
||
case ConversationViewLayoutAlignment_FullWidth:
|
||
itemFrame = CGRectMake(0, y, viewWidth, layoutSize.height);
|
||
break;
|
||
case ConversationViewLayoutAlignment_Center:
|
||
itemFrame = CGRectMake(
|
||
hInset + round((viewWidth - layoutSize.width) * 0.5f), y, layoutSize.width, layoutSize.height);
|
||
break;
|
||
}
|
||
|
||
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:0];
|
||
UICollectionViewLayoutAttributes *itemAttributes =
|
||
[UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
|
||
itemAttributes.frame = itemFrame;
|
||
self.itemAttributesMap[@(row)] = itemAttributes;
|
||
|
||
contentBottom = itemFrame.origin.y + itemFrame.size.height;
|
||
y = contentBottom + vSpacing;
|
||
row++;
|
||
}
|
||
|
||
contentBottom += vInset;
|
||
self.contentSize = CGSizeMake(viewWidth, contentBottom);
|
||
}
|
||
|
||
- (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect
|
||
{
|
||
NSMutableArray<UICollectionViewLayoutAttributes *> *result = [NSMutableArray new];
|
||
for (UICollectionViewLayoutAttributes *itemAttributes in self.itemAttributesMap.allValues) {
|
||
if (CGRectIntersectsRect(rect, itemAttributes.frame)) {
|
||
[result addObject:itemAttributes];
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
|
||
- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
|
||
{
|
||
return self.itemAttributesMap[@(indexPath.row)];
|
||
}
|
||
|
||
- (CGSize)collectionViewContentSize
|
||
{
|
||
return self.contentSize;
|
||
}
|
||
|
||
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
|
||
{
|
||
return self.collectionView.bounds.size.width != newBounds.size.width;
|
||
}
|
||
|
||
@end
|
||
|
||
NS_ASSUME_NONNULL_END
|