Skip to content

Commit

Permalink
A simple sample that shows how to make suplementary views stick.
Browse files Browse the repository at this point in the history
  • Loading branch information
Derrick J. Hathaway committed Jun 4, 2013
1 parent de35663 commit ed0a723
Show file tree
Hide file tree
Showing 11 changed files with 256 additions and 59 deletions.
26 changes: 14 additions & 12 deletions StickyViews.xcodeproj/project.pbxproj
Expand Up @@ -17,8 +17,9 @@
49089AA2175E320A00240B76 /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 49089AA1175E320A00240B76 /* Default@2x.png */; };
49089AA4175E320A00240B76 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 49089AA3175E320A00240B76 /* Default-568h@2x.png */; };
49089AA7175E320A00240B76 /* MainStoryboard_iPhone.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 49089AA5175E320A00240B76 /* MainStoryboard_iPhone.storyboard */; };
49089AAA175E320A00240B76 /* MainStoryboard_iPad.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 49089AA8175E320A00240B76 /* MainStoryboard_iPad.storyboard */; };
49089AAD175E320A00240B76 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 49089AAC175E320A00240B76 /* ViewController.m */; };
49089AB6175E33EC00240B76 /* MyLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 49089AB5175E33EC00240B76 /* MyLayout.m */; };
49089AB9175E38D800240B76 /* MyHeaderOrFooterView.m in Sources */ = {isa = PBXBuildFile; fileRef = 49089AB8175E38D800240B76 /* MyHeaderOrFooterView.m */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
Expand All @@ -36,9 +37,12 @@
49089AA1175E320A00240B76 /* Default@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default@2x.png"; sourceTree = "<group>"; };
49089AA3175E320A00240B76 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = "<group>"; };
49089AA6175E320A00240B76 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = en; path = en.lproj/MainStoryboard_iPhone.storyboard; sourceTree = "<group>"; };
49089AA9175E320A00240B76 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = en; path = en.lproj/MainStoryboard_iPad.storyboard; sourceTree = "<group>"; };
49089AAB175E320A00240B76 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = "<group>"; };
49089AAC175E320A00240B76 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = "<group>"; };
49089AB4175E33EC00240B76 /* MyLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MyLayout.h; sourceTree = "<group>"; };
49089AB5175E33EC00240B76 /* MyLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MyLayout.m; sourceTree = "<group>"; };
49089AB7175E38D800240B76 /* MyHeaderOrFooterView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MyHeaderOrFooterView.h; sourceTree = "<group>"; };
49089AB8175E38D800240B76 /* MyHeaderOrFooterView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MyHeaderOrFooterView.m; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -88,10 +92,13 @@
49089A9C175E320A00240B76 /* AppDelegate.h */,
49089A9D175E320A00240B76 /* AppDelegate.m */,
49089AA5175E320A00240B76 /* MainStoryboard_iPhone.storyboard */,
49089AA8175E320A00240B76 /* MainStoryboard_iPad.storyboard */,
49089AAB175E320A00240B76 /* ViewController.h */,
49089AAC175E320A00240B76 /* ViewController.m */,
49089A94175E320A00240B76 /* Supporting Files */,
49089AB4175E33EC00240B76 /* MyLayout.h */,
49089AB5175E33EC00240B76 /* MyLayout.m */,
49089AB7175E38D800240B76 /* MyHeaderOrFooterView.h */,
49089AB8175E38D800240B76 /* MyHeaderOrFooterView.m */,
);
path = StickyViews;
sourceTree = "<group>";
Expand Down Expand Up @@ -166,7 +173,6 @@
49089AA2175E320A00240B76 /* Default@2x.png in Resources */,
49089AA4175E320A00240B76 /* Default-568h@2x.png in Resources */,
49089AA7175E320A00240B76 /* MainStoryboard_iPhone.storyboard in Resources */,
49089AAA175E320A00240B76 /* MainStoryboard_iPad.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -180,6 +186,8 @@
49089A9A175E320A00240B76 /* main.m in Sources */,
49089A9E175E320A00240B76 /* AppDelegate.m in Sources */,
49089AAD175E320A00240B76 /* ViewController.m in Sources */,
49089AB6175E33EC00240B76 /* MyLayout.m in Sources */,
49089AB9175E38D800240B76 /* MyHeaderOrFooterView.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -202,14 +210,6 @@
name = MainStoryboard_iPhone.storyboard;
sourceTree = "<group>";
};
49089AA8175E320A00240B76 /* MainStoryboard_iPad.storyboard */ = {
isa = PBXVariantGroup;
children = (
49089AA9175E320A00240B76 /* en */,
);
name = MainStoryboard_iPad.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */

/* Begin XCBuildConfiguration section */
Expand Down Expand Up @@ -278,6 +278,7 @@
GCC_PREFIX_HEADER = "StickyViews/StickyViews-Prefix.pch";
INFOPLIST_FILE = "StickyViews/StickyViews-Info.plist";
PRODUCT_NAME = "$(TARGET_NAME)";
TARGETED_DEVICE_FAMILY = 1;
WRAPPER_EXTENSION = app;
};
name = Debug;
Expand All @@ -289,6 +290,7 @@
GCC_PREFIX_HEADER = "StickyViews/StickyViews-Prefix.pch";
INFOPLIST_FILE = "StickyViews/StickyViews-Info.plist";
PRODUCT_NAME = "$(TARGET_NAME)";
TARGETED_DEVICE_FAMILY = 1;
WRAPPER_EXTENSION = app;
};
name = Release;
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions StickyViews/MyHeaderOrFooterView.h
@@ -0,0 +1,13 @@
//
// MyHeaderOrFooterView.h
// StickyViews
//
// Created by derrick on 6/4/13.
// Copyright (c) 2013 derrh. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface MyHeaderOrFooterView : UICollectionReusableView

@end
31 changes: 31 additions & 0 deletions StickyViews/MyHeaderOrFooterView.m
@@ -0,0 +1,31 @@
//
// MyHeaderOrFooterView.m
// StickyViews
//
// Created by derrick on 6/4/13.
// Copyright (c) 2013 derrh. All rights reserved.
//

#import "MyHeaderOrFooterView.h"

@implementation MyHeaderOrFooterView

- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}

/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/

@end
13 changes: 13 additions & 0 deletions StickyViews/MyLayout.h
@@ -0,0 +1,13 @@
//
// MyLayout.h
// StickyViews
//
// Created by derrick on 6/4/13.
// Copyright (c) 2013 derrh. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface MyLayout : UICollectionViewFlowLayout

@end
88 changes: 88 additions & 0 deletions StickyViews/MyLayout.m
@@ -0,0 +1,88 @@
//
// MyLayout.m
// StickyViews
//
// Created by derrick on 6/4/13.
// Copyright (c) 2013 derrh. All rights reserved.
//

#import "MyLayout.h"

@implementation MyLayout


/* in order to make the header and footer float (or stick to the
top and bottom of the screen), we need to invalidate the layout
on bounds change (when scrolling).
*/
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
return YES;
}

/* override this method so that you can add your header and footer
to the list of UICollectionReusableView's that will be requested
from your dataSource.
*/
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSMutableArray *allItems = [[super layoutAttributesForElementsInRect:rect] mutableCopy];

__block BOOL headerFound = NO;
__block BOOL footerFound = NO;
[allItems enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if ([[obj representedElementKind] isEqualToString:UICollectionElementKindSectionHeader]) {
headerFound = YES;
[self updateHeaderAttributes:obj];
} else if ([[obj representedElementKind] isEqualToString:UICollectionElementKindSectionFooter]) {
footerFound = YES;
[self updateFooterAttributes:obj];
}
}];


// Flow layout will remove items from the list if they are supposed to be off screen, so we add them
// back in in those cases.
if (!headerFound) {
[allItems addObject:[self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:[allItems count] inSection:0]]];
}
if (!footerFound) {
[allItems addObject:[self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionFooter atIndexPath:[NSIndexPath indexPathForItem:[allItems count] inSection:0]]];
}

return allItems;
}

/* now provide the layout attributes for your floating header/footer. */
- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:kind withIndexPath:indexPath];
attributes.size = CGSizeMake(self.collectionView.bounds.size.width, 44);
if ([kind isEqualToString:UICollectionElementKindSectionHeader]) {
[self updateHeaderAttributes:attributes];
} else {
[self updateFooterAttributes:attributes];
}
return attributes;
}

#pragma mark - make things stick

- (void)updateHeaderAttributes:(UICollectionViewLayoutAttributes *)attributes
{
CGRect currentBounds = self.collectionView.bounds;
attributes.zIndex = 1;
attributes.hidden = NO;
CGFloat yCenterOffset = currentBounds.origin.y + attributes.size.height/2.f;
attributes.center = CGPointMake(CGRectGetMidX(currentBounds), yCenterOffset);
}

- (void)updateFooterAttributes:(UICollectionViewLayoutAttributes *)attributes
{
CGRect currentBounds = self.collectionView.bounds;
attributes.zIndex = 1;
attributes.hidden = NO;
CGFloat yCenterOffset = currentBounds.origin.y + currentBounds.size.height - attributes.size.height/2.0f;
attributes.center = CGPointMake(CGRectGetMidX(currentBounds), yCenterOffset);
}
@end
2 changes: 0 additions & 2 deletions StickyViews/StickyViews-Info.plist
Expand Up @@ -35,8 +35,6 @@
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
Expand Down
2 changes: 1 addition & 1 deletion StickyViews/ViewController.h
Expand Up @@ -8,6 +8,6 @@

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController
@interface ViewController : UICollectionViewController

@end
31 changes: 25 additions & 6 deletions StickyViews/ViewController.m
Expand Up @@ -7,23 +7,42 @@
//

#import "ViewController.h"
#import "MyHeaderOrFooterView.h"


@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad
#pragma mark - UICollectionView

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return 94;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath];
cell.backgroundColor = [UIColor scrollViewTexturedBackgroundColor];

return cell;
}

- (void)didReceiveMemoryWarning
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
UICollectionReusableView *view;

if ([kind isEqualToString:UICollectionElementKindSectionHeader]) {
view = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"Header" forIndexPath:indexPath];
view.backgroundColor = [UIColor orangeColor];
} else {
view = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:@"Footer" forIndexPath:indexPath];
view.backgroundColor = [UIColor blueColor];
}
return view;
}

@end
26 changes: 0 additions & 26 deletions StickyViews/en.lproj/MainStoryboard_iPad.storyboard

This file was deleted.

0 comments on commit ed0a723

Please sign in to comment.