| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199 |
- //
- // 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+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];
- // Collection view layout
- UICollectionViewFlowLayout *layout = ({
- UICollectionViewFlowLayout *layout = [UICollectionViewFlowLayout new];
- layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
- layout.sectionInset = UIEdgeInsetsZero;
- layout.minimumLineSpacing = kCarouselItemSpacing;
- layout.itemSize = UICollectionViewFlowLayoutAutomaticSize;
- layout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize;
- 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 {
- 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
|