mirror of https://github.com/oxen-io/session-ios
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
179 lines
5.4 KiB
Matlab
179 lines
5.4 KiB
Matlab
8 years ago
|
//
|
||
|
// Copyright (c) 2017 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 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;
|
||
|
|
||
|
@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.tag);
|
||
|
[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.tag, 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.tag);
|
||
|
|
||
|
const int vInset = 15;
|
||
|
const int hInset = 10;
|
||
|
const int vSpacing = 5;
|
||
|
const int viewWidth = (int)floor(self.collectionView.bounds.size.width);
|
||
|
const int maxMessageWidth = (int)floor((viewWidth - 2 * hInset) * 0.7f);
|
||
|
|
||
|
NSArray<id<ConversationViewLayoutItem>> *layoutItems = self.delegate.layoutItems;
|
||
|
|
||
|
CGFloat y = vInset;
|
||
|
CGFloat contentBottom = y;
|
||
|
BOOL isRTL = self.collectionView.isRTL;
|
||
|
|
||
|
NSInteger row = 0;
|
||
|
for (id<ConversationViewLayoutItem> layoutItem in layoutItems) {
|
||
|
CGSize layoutSize = [layoutItem cellSizeForViewWidth:viewWidth maxMessageWidth:maxMessageWidth];
|
||
|
|
||
|
layoutSize.width = MIN(viewWidth, floor(layoutSize.width));
|
||
|
layoutSize.height = floor(layoutSize.height);
|
||
|
CGRect itemFrame;
|
||
|
switch (layoutItem.layoutAlignment) {
|
||
|
case ConversationViewLayoutAlignment_Incoming:
|
||
|
case ConversationViewLayoutAlignment_Outgoing: {
|
||
|
BOOL isLeft = ((layoutItem.layoutAlignment == ConversationViewLayoutAlignment_Incoming && !isRTL)
|
||
|
|| (layoutItem.layoutAlignment == ConversationViewLayoutAlignment_Outgoing && 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;
|
||
|
}
|
||
|
|
||
|
#pragma mark - Logging
|
||
|
|
||
|
+ (NSString *)tag
|
||
|
{
|
||
|
return [NSString stringWithFormat:@"[%@]", self.class];
|
||
|
}
|
||
|
|
||
|
- (NSString *)tag
|
||
|
{
|
||
|
return self.class.tag;
|
||
|
}
|
||
|
|
||
|
@end
|
||
|
|
||
|
NS_ASSUME_NONNULL_END
|