Skip to content

Commit 93ba4bc

Browse files
committed
Attempting to use new layout to adjust item widths to fill collection view's width.
1 parent 759f16f commit 93ba4bc

File tree

10 files changed

+306
-55
lines changed

10 files changed

+306
-55
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//
2+
// MTZCollectionViewFlowLayout.h
3+
// MTZCollectionViewFlowLayout
4+
//
5+
// Created by Matt Zanchelli on 5/29/14.
6+
// Copyright (c) 2014 Matt Zanchelli. All rights reserved.
7+
//
8+
9+
#import <UIKit/UIKit.h>
10+
11+
@interface MTZCollectionViewFlowLayout : UICollectionViewFlowLayout
12+
13+
/// Treat itemSize as a minimum item size, and stretch the cell in the dimension opposite of @c scrollDirection to fill space.
14+
/// @discussion This will not work if @c collectionView:layout:sizeForItemAtIndexPath: is implemented by @c collectionView 's delegate.
15+
@property (nonatomic, getter = treatsSizeAsMinimumSize) BOOL treatSizeAsMinimumSize;
16+
17+
@end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
//
2+
// MTZCollectionViewFlowLayout.m
3+
// MTZCollectionViewFlowLayout
4+
//
5+
// Created by Matt Zanchelli on 5/29/14.
6+
// Copyright (c) 2014 Matt Zanchelli. All rights reserved.
7+
//
8+
9+
// With some left alignment help: https://github.com/mokagio/UICollectionViewLeftAlignedLayout
10+
//
11+
// Copyright (c) 2014 Giovanni Lodi
12+
//
13+
// Permission is hereby granted, free of charge, to any person obtaining a copy of
14+
// this software and associated documentation files (the "Software"), to deal in
15+
// the Software without restriction, including without limitation the rights to
16+
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
17+
// the Software, and to permit persons to whom the Software is furnished to do so,
18+
// subject to the following conditions:
19+
//
20+
// The above copyright notice and this permission notice shall be included in all
21+
// copies or substantial portions of the Software.
22+
//
23+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
25+
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
26+
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
27+
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
28+
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29+
30+
#import "MTZCollectionViewFlowLayout.h"
31+
32+
@implementation MTZCollectionViewFlowLayout
33+
34+
- (id)init
35+
{
36+
self = [super init];
37+
if (self) {
38+
self.treatSizeAsMinimumSize = YES;
39+
}
40+
return self;
41+
}
42+
43+
- (id)initWithCoder:(NSCoder *)aDecoder
44+
{
45+
self = [super initWithCoder:aDecoder];
46+
if (self) {
47+
self.treatSizeAsMinimumSize = YES;
48+
}
49+
return self;
50+
}
51+
52+
53+
#pragma mark - Properties
54+
55+
- (void)setTreatSizeAsMinimumSize:(BOOL)treatSizeAsMinimumSize
56+
{
57+
_treatSizeAsMinimumSize = treatSizeAsMinimumSize;
58+
59+
UICollectionViewFlowLayoutInvalidationContext *ctx = [[UICollectionViewFlowLayoutInvalidationContext alloc] init];
60+
ctx.invalidateFlowLayoutDelegateMetrics = YES;
61+
[self invalidateLayoutWithContext:ctx];
62+
}
63+
64+
65+
#pragma mark - Providing Layout Attributes
66+
67+
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
68+
{
69+
NSArray *allAttributes = [super layoutAttributesForElementsInRect:rect];
70+
for ( UICollectionViewLayoutAttributes *attributes in allAttributes ) {
71+
if ( !attributes.representedElementKind ) {
72+
NSIndexPath *indexPath = attributes.indexPath;
73+
attributes.frame = [self layoutAttributesForItemAtIndexPath:indexPath].frame;
74+
}
75+
}
76+
return allAttributes;
77+
}
78+
79+
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
80+
{
81+
UICollectionViewLayoutAttributes *attributes = [super layoutAttributesForItemAtIndexPath:indexPath];
82+
83+
// Return early if not treating as minimum size.
84+
if ( !self.treatsSizeAsMinimumSize ) return attributes;
85+
86+
// Get some basic measurements.
87+
UIEdgeInsets sectionInset;
88+
if ( [self.collectionView.delegate respondsToSelector:@selector(collectionView:layout:insetForSectionAtIndex:)] ) {
89+
[((id<UICollectionViewDelegateFlowLayout>)self.collectionView.delegate) collectionView:self.collectionView layout:self insetForSectionAtIndex:indexPath.section];
90+
} else {
91+
sectionInset = self.sectionInset;
92+
}
93+
94+
CGFloat interItemSpacing;
95+
if ( [self.collectionView.delegate respondsToSelector:@selector(collectionView:layout:minimumInteritemSpacingForSectionAtIndex:)] ) {
96+
interItemSpacing = [((id<UICollectionViewDelegateFlowLayout>)self.collectionView.delegate) collectionView:self.collectionView layout:self minimumInteritemSpacingForSectionAtIndex:indexPath.section];
97+
} else {
98+
interItemSpacing = self.minimumInteritemSpacing;
99+
}
100+
101+
// Measurements dependent on scroll direction.
102+
CGFloat totalDimension, totalSectionInset, minimumItemDimension;
103+
switch ( self.scrollDirection ) {
104+
case UICollectionViewScrollDirectionVertical:
105+
totalDimension = self.collectionView.bounds.size.width;
106+
totalSectionInset = sectionInset.left + sectionInset.right;
107+
minimumItemDimension = self.itemSize.width;
108+
break;
109+
case UICollectionViewScrollDirectionHorizontal:
110+
totalDimension = self.collectionView.bounds.size.height;
111+
totalSectionInset = sectionInset.top + sectionInset.bottom;
112+
minimumItemDimension = self.itemSize.height;
113+
break;
114+
}
115+
116+
// Calculate the new dimension based on working dimension and number of items in a line.
117+
CGFloat workingDimension = totalDimension - totalSectionInset;
118+
CGFloat numberOfItemsInLine = floor((workingDimension - interItemSpacing) / (minimumItemDimension + interItemSpacing));
119+
CGFloat newDimension = MAX(minimumItemDimension, workingDimension / numberOfItemsInLine);
120+
121+
// Set the new size.
122+
switch ( self.scrollDirection ) {
123+
case UICollectionViewScrollDirectionVertical:
124+
attributes.size = CGSizeMake(newDimension, attributes.size.height);
125+
break;
126+
case UICollectionViewScrollDirectionHorizontal:
127+
attributes.size = CGSizeMake(attributes.size.width, newDimension);
128+
break;
129+
}
130+
131+
/*
132+
// Align to the left.
133+
if ( indexPath.item == 0 ) {
134+
CGRect frame = attributes.frame;
135+
frame.origin.x = 0;
136+
attributes.frame = frame;
137+
} else {
138+
NSIndexPath *previousIndexPath = [NSIndexPath indexPathForItem:indexPath.item-1 inSection:indexPath.section];
139+
CGRect previousFrame = [self layoutAttributesForItemAtIndexPath:previousIndexPath].frame;
140+
CGRect strecthedCurrentFrame = CGRectMake(0, attributes.frame.origin.y, workingDimension, attributes.frame.size.height);
141+
142+
// If the current frame, once aligned to the left and stretched to the full collection view width, intersects the previous frame, then they are on the same line.
143+
if ( !CGRectIntersectsRect(previousFrame, strecthedCurrentFrame) ) {
144+
// Make sure the first item on a line is left aligned.
145+
CGRect frame = attributes.frame;
146+
frame.origin.x = 0;
147+
attributes.frame = frame;
148+
} else {
149+
attributes.frame = CGRectMake(CGRectGetMaxX(previousFrame) + interItemSpacing, attributes.frame.origin.y, attributes.frame.size.width, attributes.frame.size.height);
150+
}
151+
}
152+
*/
153+
154+
return attributes;
155+
}
156+
157+
158+
#pragma mark - Invalidating the Layout
159+
160+
- (UICollectionViewLayoutInvalidationContext *)invalidationContextForBoundsChange:(CGRect)newBounds
161+
{
162+
// This cast should hopefully never cause issues. `super` should always return `UICollectionViewFlowLayoutInvalidationContext`.
163+
UICollectionViewFlowLayoutInvalidationContext *ctx = (UICollectionViewFlowLayoutInvalidationContext *) [super invalidationContextForBoundsChange:newBounds];
164+
ctx.invalidateFlowLayoutDelegateMetrics = YES;
165+
return ctx;
166+
}
167+
168+
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
169+
{
170+
/*
171+
// Only bother if this option is set.
172+
if ( self.treatSizeAsMinimumSize ) {
173+
// The new and old dimension (that we care about)
174+
CGFloat newDimension, oldDimension;
175+
// Set the new/old dimension according to scroll direction.
176+
switch (self.scrollDirection) {
177+
case UICollectionViewScrollDirectionVertical:
178+
newDimension = newBounds.size.width;
179+
oldDimension = self.collectionView.bounds.size.width;
180+
break;
181+
case UICollectionViewScrollDirectionHorizontal:
182+
newDimension = newBounds.size.height;
183+
oldDimension = self.collectionView.bounds.size.height;
184+
break;
185+
}
186+
187+
// If the dimension we care about changed, invalidate.
188+
if ( newDimension != oldDimension ) {
189+
return YES;
190+
}
191+
}
192+
*/
193+
194+
return [super shouldInvalidateLayoutForBoundsChange:newBounds];
195+
}
196+
197+
@end

Classes/MTZWhatsNewViewController/MTZWhatsNewFeatureCollectionViewCell.m

+5-16
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,6 @@ - (void)layoutForList
9393
{
9494
[self removeAllConstraints];
9595

96-
self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, 320, 112);
97-
9896
self.textLabel.textAlignment = NSTextAlignmentLeft;
9997
self.detailTextLabel.textAlignment = NSTextAlignmentLeft;
10098

@@ -105,34 +103,26 @@ - (void)layoutForList
105103
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:self.imageView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];
106104

107105
// Horizontally space icon and labels.
108-
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(26)-[icon(64)]-(10)-[title]-(26)-|" options:NSLayoutFormatDirectionLeftToRight metrics:nil views:views]];
109-
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(26)-[icon(64)]-(10)-[detail]-(26)-|" options:NSLayoutFormatDirectionLeftToRight metrics:nil views:views]];
106+
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(26)-[icon(64)]-(10)-[title(>=194)]-(26)-|" options:NSLayoutFormatDirectionLeftToRight metrics:nil views:views]];
107+
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(26)-[icon(64)]-(10)-[detail(>=194)]-(26)-|" options:NSLayoutFormatDirectionLeftToRight metrics:nil views:views]];
110108
// Vertically align labels.
111-
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(>=0)-[title(20)]-(0)-[detail(34)]-(>=29)-|" options:NSLayoutFormatDirectionLeftToRight metrics:nil views:views]];
109+
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(>=0)-[title(20)]-(0)-[detail(34)]-(>=29)-|" options:0 metrics:nil views:views]];
112110
}
113111

114112
- (void)layoutForGrid
115113
{
116114
[self removeAllConstraints];
117115

118-
self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, 270, 187);
119-
120116
self.textLabel.textAlignment = NSTextAlignmentCenter;
121117
self.detailTextLabel.textAlignment = NSTextAlignmentCenter;
122118

123-
// Remove all constraints. Start from a clean state.
124-
[self.contentView removeConstraints:self.contentView.constraints];
125-
[self.textLabel removeConstraints:self.textLabel.constraints];
126-
[self.detailTextLabel removeConstraints:self.detailTextLabel.constraints];
127-
[self.imageView removeConstraints:self.imageView.constraints];
128-
129119
// Thew views to be referencing in visual format.
130120
NSDictionary *views = @{@"icon": self.imageView, @"title": self.textLabel, @"detail": self.detailTextLabel};
131121

132122
// Horizontal alignment.
133123
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(>=0)-[icon(64)]-(>=0)-|" options:NSLayoutFormatAlignAllCenterX metrics:nil views:views]];
134-
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(32)-[title]-(32)-|" options:NSLayoutFormatAlignAllCenterX metrics:nil views:views]];
135-
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(32)-[detail]-(32)-|" options:NSLayoutFormatAlignAllCenterX metrics:nil views:views]];
124+
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(32)-[title(>=206)]-(32)-|" options:NSLayoutFormatAlignAllCenterX metrics:nil views:views]];
125+
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(32)-[detail(>=206)]-(32)-|" options:NSLayoutFormatAlignAllCenterX metrics:nil views:views]];
136126
// Vertical alignment.
137127
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(>=0)-[icon(64)]-10-[title(20)]-4-[detail(34)]-(>=28)-|" options:NSLayoutFormatAlignAllCenterX metrics:nil views:views]];
138128
}
@@ -147,7 +137,6 @@ - (void)prepareForReuse
147137
- (void)removeAllConstraints
148138
{
149139
// Remove all constraints. Start from a clean state.
150-
[self removeConstraints:self.constraints];
151140
[self.contentView removeConstraints:self.contentView.constraints];
152141
[self.textLabel removeConstraints:self.textLabel.constraints];
153142
[self.detailTextLabel removeConstraints:self.detailTextLabel.constraints];

Classes/MTZWhatsNewViewController/MTZWhatsNewGridViewController.m

+25-21
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
#import "MTZCollectionView.h"
1212
#import "MTZWhatsNewFeatureCollectionViewCell.h"
13+
#import "MTZCollectionViewFlowLayout.h"
1314

1415
#import "NSLayoutConstraint+Common.h"
1516

@@ -26,7 +27,7 @@ @interface MTZWhatsNewGridViewController () <UICollectionViewDelegate, UICollect
2627
@property (strong, nonatomic) MTZCollectionView *collectionView;
2728

2829
/// The layout for the collection view.
29-
@property (strong, nonatomic) UICollectionViewFlowLayout *flowLayout;
30+
@property (strong, nonatomic) MTZCollectionViewFlowLayout *flowLayout;
3031

3132
@end
3233

@@ -62,19 +63,10 @@ - (id)initWithCoder:(NSCoder *)aDecoder
6263
return self;
6364
}
6465

65-
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
66-
{
67-
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
68-
if (self) {
69-
[self __MTZWhatsNewGridViewController_Setup];
70-
}
71-
return self;
72-
}
73-
7466
- (void)__MTZWhatsNewGridViewController_Setup
7567
{
7668
// Feature collection view.
77-
self.flowLayout = [[UICollectionViewFlowLayout alloc] init];
69+
self.flowLayout = [[MTZCollectionViewFlowLayout alloc] init];
7870
self.flowLayout.minimumLineSpacing = 2;
7971
self.flowLayout.minimumInteritemSpacing = 0;
8072
self.flowLayout.headerReferenceSize = self.flowLayout.footerReferenceSize = CGSizeZero;
@@ -90,6 +82,7 @@ - (void)__MTZWhatsNewGridViewController_Setup
9082
self.collectionView.backgroundColor = [UIColor clearColor];
9183
self.collectionView.contentInset = self.contentInset;
9284
self.collectionView.scrollIndicatorInsets = self.contentInset;
85+
[self calculateLayoutItemSize];
9386

9487
// Defaults.
9588
self.templatedIcons = YES;
@@ -101,6 +94,26 @@ - (void)viewDidAppear:(BOOL)animated
10194
[self.collectionView performSelector:@selector(flashScrollIndicators) withObject:nil afterDelay:0];
10295
}
10396

97+
- (void)viewDidLayoutSubviews
98+
{
99+
[super viewDidLayoutSubviews];
100+
[self calculateLayoutItemSize];
101+
}
102+
103+
- (void)calculateLayoutItemSize
104+
{
105+
CGSize itemSize = [self shouldUseGridLayout] ? CGSizeMake(270, 187) : CGSizeMake(320, 108);
106+
107+
if ( CGSizeEqualToSize(self.flowLayout.itemSize, itemSize) ) return;
108+
109+
self.flowLayout.itemSize = itemSize;
110+
111+
UICollectionViewFlowLayoutInvalidationContext *ctx = [[UICollectionViewFlowLayoutInvalidationContext alloc] init];
112+
ctx.invalidateFlowLayoutAttributes = YES;
113+
ctx.invalidateFlowLayoutDelegateMetrics = YES;
114+
[self.flowLayout invalidateLayoutWithContext:ctx];
115+
}
116+
104117
- (void)styleDidChange
105118
{
106119
[super styleDidChange];
@@ -173,15 +186,6 @@ - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollection
173186
return CGSizeZero;
174187
}
175188

176-
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
177-
{
178-
if ( [self shouldUseGridLayout] ) {
179-
return CGSizeMake(270, 187);
180-
} else {
181-
return CGSizeMake(320, 108);
182-
}
183-
}
184-
185189
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView
186190
layout:(UICollectionViewLayout *)collectionViewLayout
187191
insetForSectionAtIndex:(NSInteger)section
@@ -288,7 +292,7 @@ - (BOOL)shouldUseGridLayout
288292
{
289293
// iPhone width = 320
290294
// iPad's UIModalPresentationFormSheet width = 540
291-
return self.collectionView.frame.size.width >= 540;
295+
return self.collectionView.bounds.size.width >= 540;
292296
}
293297

294298
@end

0 commit comments

Comments
 (0)