Skip to content

Commit 9fda3c0

Browse files
committed
Making MTZWhatsNewViewController an abstract class giving some basic customization options. MTZWhatsNewGridViewController is a subclass with a list/grid display of features.
1 parent 0de4a64 commit 9fda3c0

File tree

8 files changed

+462
-325
lines changed

8 files changed

+462
-325
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
//
2+
// MTZWhatsNewGridViewController.h
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 "MTZWhatsNewViewController.h"
10+
11+
/// A What's New View Controller subclass that presents the features in a list or grid.
12+
/// @discussion At the root of the features dictionary should be strings representing the app versions. Corresponding to each version string should be an array of features. Each feature should be a dictionary containing string values for any and all of the following: "title", "detail", and "icon". The value of "title" will be displayed in larger/bolder type. The value of "detail" will be displayed below title. The value of "icon" will be used to find an image resource in the app's bundle to use as a representation of the feature.
13+
/*
14+
Example:
15+
{
16+
"1.1" = (
17+
{
18+
icon = "Pull to Refresh";
19+
title = "Pull to Refresh";
20+
detail = "More easily refresh a subscription or playlist.";
21+
},
22+
{
23+
icon = Stations;
24+
title = "Custom Stations";
25+
detail = "Create custom stations of your favorite podcasts.";
26+
}
27+
);
28+
"2.0" = (
29+
{
30+
icon = "iOS 7";
31+
title = "Designed for iOS 7";
32+
detail = "Podcasts has a beautiful new look and feel that fits right in with iOS 7.";
33+
},
34+
{
35+
icon = "Up To Date";
36+
title = "Stay up to date";
37+
detail = "Podcasts now automatically updates with new episodes.";
38+
}
39+
);
40+
"3.0" = (
41+
{
42+
icon = Podcast;
43+
title = "Unplayed Episodes";
44+
detail = "Quickly find episodes you haven\U2019t played yet.";
45+
},
46+
{
47+
icon = Feed;
48+
title = "Browse the Feed";
49+
detail = "Stream available episodes or download them to play later.";
50+
},
51+
{
52+
icon = Saved;
53+
title = "Saved Episodes";
54+
detail = "Save your favorite episodes to ensure you\U2019ll always have them.";
55+
},
56+
{
57+
icon = Delete;
58+
title = "Delete Played Episodes";
59+
detail = "Episodes can be automatically deleted after they are played.";
60+
}
61+
);
62+
}
63+
*/
64+
@interface MTZWhatsNewGridViewController : MTZWhatsNewViewController
65+
66+
/// Whether or not the icons should be treated as templates.
67+
/// @discussion The default is @c YES.
68+
@property (nonatomic) BOOL templatedIcons;
69+
70+
@end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
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

Comments
 (0)