|
| 1 | +// |
| 2 | +// MTZWhatsNewGridViewController.m |
| 3 | +// Podcasts |
| 4 | +// |
| 5 | +// Created by Matt Zanchelli on 5/27/14. |
| 6 | +// Copyright (c) 2014 Matt Zanchelli. All rights reserved. |
| 7 | +// |
| 8 | + |
| 9 | +#import "MTZWhatsNewGridViewController.h" |
| 10 | + |
| 11 | +#import "MTZCollectionView.h" |
| 12 | +#import "MTZWhatsNewFeatureCollectionViewCell.h" |
| 13 | + |
| 14 | +#import "NSLayoutConstraint+Common.h" |
| 15 | + |
| 16 | +static const NSString *kTitle = @"title"; |
| 17 | +static const NSString *kDetail = @"detail"; |
| 18 | +static const NSString *kIconName = @"icon"; |
| 19 | + |
| 20 | +@interface MTZWhatsNewGridViewController () <UICollectionViewDelegate, UICollectionViewDataSource> |
| 21 | + |
| 22 | +/// An ordered list of the versions from newest to oldest. |
| 23 | +@property (strong, nonatomic) NSArray *orderedKeys; |
| 24 | + |
| 25 | +/// The collection view to display all the new features. |
| 26 | +@property (strong, nonatomic) MTZCollectionView *collectionView; |
| 27 | + |
| 28 | +@end |
| 29 | + |
| 30 | + |
| 31 | +@implementation MTZWhatsNewGridViewController |
| 32 | + |
| 33 | +#pragma mark - Initialization |
| 34 | + |
| 35 | +- (instancetype)initWithFeatures:(NSDictionary *)features |
| 36 | +{ |
| 37 | + self = [super initWithFeatures:features]; |
| 38 | + if (self) { |
| 39 | + [self __MTZWhatsNewGridViewController_Setup]; |
| 40 | + } |
| 41 | + return self; |
| 42 | +} |
| 43 | + |
| 44 | +- (id)init |
| 45 | +{ |
| 46 | + self = [super init]; |
| 47 | + if (self) { |
| 48 | + [self __MTZWhatsNewGridViewController_Setup]; |
| 49 | + } |
| 50 | + return self; |
| 51 | +} |
| 52 | + |
| 53 | +- (id)initWithCoder:(NSCoder *)aDecoder |
| 54 | +{ |
| 55 | + self = [super initWithCoder:aDecoder]; |
| 56 | + if (self) { |
| 57 | + [self __MTZWhatsNewGridViewController_Setup]; |
| 58 | + } |
| 59 | + return self; |
| 60 | +} |
| 61 | + |
| 62 | +- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil |
| 63 | +{ |
| 64 | + self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; |
| 65 | + if (self) { |
| 66 | + [self __MTZWhatsNewGridViewController_Setup]; |
| 67 | + } |
| 68 | + return self; |
| 69 | +} |
| 70 | + |
| 71 | +- (void)__MTZWhatsNewGridViewController_Setup |
| 72 | +{ |
| 73 | + // Feature collection view. |
| 74 | + UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init]; |
| 75 | + flowLayout.minimumLineSpacing = 2; |
| 76 | + flowLayout.minimumInteritemSpacing = 0; |
| 77 | + flowLayout.headerReferenceSize = flowLayout.footerReferenceSize = CGSizeZero; |
| 78 | + |
| 79 | + self.collectionView = [[MTZCollectionView alloc] initWithFrame:self.contentView.bounds collectionViewLayout:flowLayout]; |
| 80 | + [self.contentView addSubview:self.collectionView]; |
| 81 | + self.collectionView.translatesAutoresizingMaskIntoConstraints = NO; |
| 82 | + [self.contentView addConstraints:[NSLayoutConstraint constraintsToFillToSuperview:self.collectionView]]; |
| 83 | + self.collectionView.delegate = self; |
| 84 | + self.collectionView.dataSource = self; |
| 85 | + [self.collectionView registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"whatsnew"]; |
| 86 | + [self.collectionView registerClass:[MTZWhatsNewFeatureCollectionViewCell class] forCellWithReuseIdentifier:@"feature"]; |
| 87 | + self.collectionView.backgroundColor = [UIColor clearColor]; |
| 88 | +// UIEdgeInsets edgeInsets = UIEdgeInsetsMake(0, 0, buttonHeight, 0); |
| 89 | +// self.collectionView.contentInset = edgeInsets; |
| 90 | +// self.collectionView.scrollIndicatorInsets = edgeInsets; |
| 91 | + |
| 92 | + // Defaults. |
| 93 | + self.templatedIcons = YES; |
| 94 | +} |
| 95 | + |
| 96 | +- (void)viewDidAppear:(BOOL)animated |
| 97 | +{ |
| 98 | + [super viewDidAppear:animated]; |
| 99 | + [self.collectionView flashScrollIndicators]; |
| 100 | +} |
| 101 | + |
| 102 | +- (void)styleDidChange |
| 103 | +{ |
| 104 | + // Reload collection view to change styles. |
| 105 | + [self.collectionView reloadData]; |
| 106 | + |
| 107 | + switch (self.style) { |
| 108 | + case MTZWhatsNewViewControllerStyleDarkContent: |
| 109 | + self.collectionView.indicatorStyle = UIScrollViewIndicatorStyleBlack; |
| 110 | + break; |
| 111 | + case MTZWhatsNewViewControllerStyleLightContent: |
| 112 | + self.collectionView.indicatorStyle = UIScrollViewIndicatorStyleWhite; |
| 113 | + break; |
| 114 | + default: |
| 115 | + break; |
| 116 | + } |
| 117 | +} |
| 118 | + |
| 119 | +- (UIColor *)contentColor |
| 120 | +{ |
| 121 | + switch ( self.style ) { |
| 122 | + case MTZWhatsNewViewControllerStyleLightContent: return [UIColor whiteColor]; |
| 123 | + case MTZWhatsNewViewControllerStyleDarkContent: return [UIColor blackColor]; |
| 124 | + default: return nil; |
| 125 | + } |
| 126 | +} |
| 127 | + |
| 128 | + |
| 129 | +#pragma mark - Properties |
| 130 | + |
| 131 | +- (void)setFeatures:(NSDictionary *)features |
| 132 | +{ |
| 133 | + [super setFeatures:features]; |
| 134 | + |
| 135 | + _orderedKeys = [[self.features allKeys] sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) { |
| 136 | + return [obj2 compare:obj1 options:NSNumericSearch]; |
| 137 | + }]; |
| 138 | + |
| 139 | + // Reload the collection view's data. |
| 140 | + [self.collectionView reloadData]; |
| 141 | +} |
| 142 | + |
| 143 | +- (void)setTemplatedIcons:(BOOL)templatedIcons |
| 144 | +{ |
| 145 | + _templatedIcons = templatedIcons; |
| 146 | + // TODO: reload icons. |
| 147 | +} |
| 148 | + |
| 149 | + |
| 150 | +#pragma mark - UICollectionViewDelegateFlowLayout |
| 151 | + |
| 152 | +- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section |
| 153 | +{ |
| 154 | + // "What's New" |
| 155 | + if ( section == 0 ) { |
| 156 | + if ( [self shouldUseGridLayout] ) { |
| 157 | + return CGSizeMake(self.view.bounds.size.width, 115); |
| 158 | + } else { |
| 159 | + return CGSizeMake(self.view.bounds.size.width, 70); |
| 160 | + } |
| 161 | + } |
| 162 | + |
| 163 | + // No header for section. |
| 164 | + return CGSizeZero; |
| 165 | +} |
| 166 | + |
| 167 | +- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath |
| 168 | +{ |
| 169 | + if ( [self shouldUseGridLayout] ) { |
| 170 | + return CGSizeMake(270, 187); |
| 171 | + } else { |
| 172 | + return CGSizeMake(320, 108); |
| 173 | + } |
| 174 | +} |
| 175 | + |
| 176 | +- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView |
| 177 | + layout:(UICollectionViewLayout *)collectionViewLayout |
| 178 | + insetForSectionAtIndex:(NSInteger)section |
| 179 | +{ |
| 180 | + if ( section == 0 && [self shouldUseGridLayout] && [collectionView numberOfSections] <= 1 && [collectionView numberOfItemsInSection:section] <= 4 ) { |
| 181 | + return UIEdgeInsetsMake(16, 0, 0, 0); |
| 182 | + } |
| 183 | + |
| 184 | + return UIEdgeInsetsZero; |
| 185 | +} |
| 186 | + |
| 187 | + |
| 188 | +#pragma mark - UICollectionViewDelegate |
| 189 | + |
| 190 | +- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath |
| 191 | +{ |
| 192 | + return NO; |
| 193 | +} |
| 194 | + |
| 195 | +- (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath |
| 196 | +{ |
| 197 | + return NO; |
| 198 | +} |
| 199 | + |
| 200 | + |
| 201 | +#pragma mark - UICollectionViewDataSource |
| 202 | + |
| 203 | +- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView |
| 204 | +{ |
| 205 | + return [self.features count]; |
| 206 | +} |
| 207 | + |
| 208 | +- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section |
| 209 | +{ |
| 210 | + NSString *key = self.orderedKeys[section]; |
| 211 | + return [self.features[key] count]; |
| 212 | +} |
| 213 | + |
| 214 | +- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView |
| 215 | + viewForSupplementaryElementOfKind:(NSString *)kind |
| 216 | + atIndexPath:(NSIndexPath *)indexPath |
| 217 | +{ |
| 218 | + UICollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"whatsnew" forIndexPath:indexPath]; |
| 219 | + |
| 220 | + // Create label for "What's New" title. |
| 221 | + UILabel *label = [[UILabel alloc] initWithFrame:view.bounds]; |
| 222 | + [view addSubview:label]; |
| 223 | + label.translatesAutoresizingMaskIntoConstraints = NO; |
| 224 | + [view addConstraints:[NSLayoutConstraint constraintsToStretchHorizontallyToSuperview:label]]; |
| 225 | + label.text = NSLocalizedString(@"What’s New", nil); |
| 226 | + label.textColor = [self contentColor]; |
| 227 | + label.textAlignment = NSTextAlignmentCenter; |
| 228 | + |
| 229 | + // Larger font and divider. |
| 230 | + if ( [self shouldUseGridLayout] ) { |
| 231 | + label.font = [UIFont fontWithName:@"HelveticaNeue-Ultralight" size:62]; |
| 232 | + label.translatesAutoresizingMaskIntoConstraints = NO; |
| 233 | + [label addConstraint:[NSLayoutConstraint constraintToSetStaticHeight:103 toView:label]]; |
| 234 | + [view addConstraints:[NSLayoutConstraint constraintsToStickView:label toEdges:UIRectEdgeLeft|UIRectEdgeTop|UIRectEdgeRight]]; |
| 235 | + |
| 236 | + // Add a visual divider. |
| 237 | + UIView *divider = [[UIView alloc] init]; |
| 238 | + [view addSubview:divider]; |
| 239 | + divider.translatesAutoresizingMaskIntoConstraints = NO; |
| 240 | + [divider addConstraint:[NSLayoutConstraint constraintToSetStaticWidth:296 toView:divider]]; |
| 241 | + [divider addConstraint:[NSLayoutConstraint constraintToSetStaticHeight:0.5 toView:divider]]; |
| 242 | + [view addConstraint:[NSLayoutConstraint constraintWithItem:divider attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:label attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0]]; |
| 243 | + [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[label][divider]" options:NSLayoutFormatDirectionLeftToRight metrics:nil views:@{@"label": label, @"divider": divider}]]; |
| 244 | + divider.backgroundColor = [[self contentColor] colorWithAlphaComponent:0.75f]; |
| 245 | + } else { |
| 246 | + label.font = [UIFont fontWithName:@"HelveticaNeue-Thin" size:30]; |
| 247 | + } |
| 248 | + |
| 249 | + return view; |
| 250 | +} |
| 251 | + |
| 252 | +- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView |
| 253 | + cellForItemAtIndexPath:(NSIndexPath *)indexPath |
| 254 | +{ |
| 255 | + MTZWhatsNewFeatureCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"feature" forIndexPath:indexPath]; |
| 256 | + |
| 257 | + NSDictionary *feature = self.features[self.orderedKeys[indexPath.section]][indexPath.row]; |
| 258 | + |
| 259 | + cell.title = feature[kTitle]; |
| 260 | + cell.detail = feature[kDetail]; |
| 261 | + NSString *iconName = feature[kIconName]; |
| 262 | + if ( iconName ) { |
| 263 | + if ( self.templatedIcons ) { |
| 264 | + cell.icon = [[UIImage imageNamed:iconName] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; |
| 265 | + } else { |
| 266 | + cell.icon = [[UIImage imageNamed:iconName] imageWithRenderingMode:UIImageRenderingModeAutomatic]; |
| 267 | + } |
| 268 | + } |
| 269 | + cell.contentColor = [self contentColor]; |
| 270 | + cell.layoutStyle = [self shouldUseGridLayout] ? MTZWhatsNewFeatureCollectionViewCellLayoutStyleGrid : MTZWhatsNewFeatureCollectionViewCellLayoutStyleList; |
| 271 | + |
| 272 | + return cell; |
| 273 | +} |
| 274 | + |
| 275 | + |
| 276 | +#pragma mark - Helpers |
| 277 | + |
| 278 | +- (BOOL)shouldUseGridLayout |
| 279 | +{ |
| 280 | + // iPhone width = 320 |
| 281 | + // iPad's UIModalPresentationFormSheet width = 540 |
| 282 | + return self.collectionView.frame.size.width >= 540; |
| 283 | +} |
| 284 | + |
| 285 | +@end |
0 commit comments