You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

207 lines
7.3 KiB

//
// FLEXScopeCarousel.m
// FLEX
//
// Created by Tanner Bennett on 7/17/19.
// Copyright © 2019 Flipboard. All rights reserved.
//
#import "FLEXScopeCarousel.h"
#import "FLEXCarouselCell.h"
#import "FLEXColor.h"
#import "UIView+FLEX_Layout.h"
const CGFloat kCarouselItemSpacing = 0;
NSString * const kCarouselCellReuseIdentifier = @"kCarouselCellReuseIdentifier";
@interface FLEXScopeCarousel () <UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
@property (nonatomic, readonly) UICollectionView *collectionView;
@property (nonatomic, readonly) FLEXCarouselCell *sizingCell;
@property (nonatomic, readonly) NSLayoutConstraint *heightConstraint;
@property (nonatomic, readonly) id dynamicTypeObserver;
@property (nonatomic, readonly) NSMutableArray *dynamicTypeHandlers;
@property (nonatomic) BOOL constraintsInstalled;
@end
@implementation FLEXScopeCarousel
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [FLEXColor primaryBackgroundColor];
self.autoresizingMask = UIViewAutoresizingFlexibleWidth;
_dynamicTypeHandlers = [NSMutableArray new];
CGSize itemSize = CGSizeZero;
if (@available(iOS 10.0, *)) {
itemSize = UICollectionViewFlowLayoutAutomaticSize;
}
// Collection view layout
UICollectionViewFlowLayout *layout = ({
UICollectionViewFlowLayout *layout = [UICollectionViewFlowLayout new];
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
layout.sectionInset = UIEdgeInsetsZero;
layout.minimumLineSpacing = kCarouselItemSpacing;
layout.itemSize = itemSize;
layout.estimatedItemSize = itemSize;
layout;
});
// Collection view
_collectionView = ({
UICollectionView *cv = [[UICollectionView alloc]
initWithFrame:CGRectZero
collectionViewLayout:layout
];
cv.showsHorizontalScrollIndicator = NO;
cv.backgroundColor = UIColor.clearColor;
cv.delegate = self;
cv.dataSource = self;
[cv registerClass:[FLEXCarouselCell class] forCellWithReuseIdentifier:kCarouselCellReuseIdentifier];
[self addSubview:cv];
cv;
});
// Sizing cell
_sizingCell = [FLEXCarouselCell new];
self.sizingCell.title = @"NSObject";
// Dynamic type
__weak __typeof(self) weakSelf = self;
_dynamicTypeObserver = [NSNotificationCenter.defaultCenter
addObserverForName:UIContentSizeCategoryDidChangeNotification
object:nil queue:nil usingBlock:^(NSNotification *note) {
[self.collectionView setNeedsLayout];
[self setNeedsUpdateConstraints];
// Notify observers
__typeof(self) self = weakSelf;
for (void (^block)(FLEXScopeCarousel *) in self.dynamicTypeHandlers) {
block(self);
}
}
];
}
return self;
}
- (void)dealloc {
[NSNotificationCenter.defaultCenter removeObserver:self.dynamicTypeObserver];
}
#pragma mark - Overrides
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
CGFloat width = 1.f / UIScreen.mainScreen.scale;
// Draw hairline
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [FLEXColor hairlineColor].CGColor);
CGContextSetLineWidth(context, width);
CGContextMoveToPoint(context, 0, rect.size.height - width);
CGContextAddLineToPoint(context, rect.size.width, rect.size.height - width);
CGContextStrokePath(context);
}
+ (BOOL)requiresConstraintBasedLayout {
return YES;
}
- (void)updateConstraints {
if (!self.constraintsInstalled) {
self.translatesAutoresizingMaskIntoConstraints = NO;
self.collectionView.translatesAutoresizingMaskIntoConstraints = NO;
[self.centerXAnchor constraintEqualToAnchor:self.superview.centerXAnchor].active = YES;
[self.widthAnchor constraintEqualToAnchor:self.superview.widthAnchor].active = YES;
[self.topAnchor constraintEqualToAnchor:self.superview.topAnchor].active = YES;
[self.collectionView pinEdgesToSuperview];
_heightConstraint = [self.heightAnchor constraintEqualToConstant:100];
self.heightConstraint.active = YES;
self.constraintsInstalled = YES;
}
self.heightConstraint.constant = [self.sizingCell systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
[super updateConstraints];
}
#pragma mark - Public
- (void)setItems:(NSArray<NSString *> *)items {
NSParameterAssert(items.count);
_items = items.copy;
// Refresh list, select first item initially
[self.collectionView reloadData];
self.selectedIndex = 0;
}
- (void)setSelectedIndex:(NSInteger)idx {
NSParameterAssert(idx < self.items.count);
_selectedIndex = idx;
NSIndexPath *path = [NSIndexPath indexPathForItem:idx inSection:0];
[self.collectionView selectItemAtIndexPath:path
animated:YES
scrollPosition:UICollectionViewScrollPositionCenteredHorizontally];
[self collectionView:self.collectionView didSelectItemAtIndexPath:path];
}
- (void)registerBlockForDynamicTypeChanges:(void (^)(FLEXScopeCarousel *))handler {
[self.dynamicTypeHandlers addObject:handler];
}
#pragma mark - UICollectionView
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
// if (@available(iOS 10.0, *)) {
// return UICollectionViewFlowLayoutAutomaticSize;
// }
self.sizingCell.title = self.items[indexPath.item];
return [self.sizingCell systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return self.items.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath {
FLEXCarouselCell *cell = (id)[collectionView dequeueReusableCellWithReuseIdentifier:kCarouselCellReuseIdentifier
forIndexPath:indexPath];
cell.title = self.items[indexPath.row];
return cell;
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
_selectedIndex = indexPath.item; // In case self.selectedIndex didn't trigger this call
if (self.selectedIndexChangedAction) {
self.selectedIndexChangedAction(indexPath.row);
}
// TODO: dynamically choose a scroll position. Very wide items should
// get "Left" while smaller items should not scroll at all, unless
// they are only partially on the screen, in which case they
// should get "HorizontallyCentered" to bring them onto the screen.
// For now, everything goes to the left, as this has a similar effect.
[collectionView scrollToItemAtIndexPath:indexPath
atScrollPosition:UICollectionViewScrollPositionLeft
animated:YES];
[self sendActionsForControlEvents:UIControlEventValueChanged];
}
@end