Skip to content

Commit

Permalink
Changed CHSectionTitleView to CHSectionHeaderView
Browse files Browse the repository at this point in the history
  • Loading branch information
camh committed Mar 2, 2010
1 parent f2248c1 commit d2ec268
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 83 deletions.
17 changes: 5 additions & 12 deletions CHGridView.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

#import <UIKit/UIKit.h>
#import "CHTileView.h"
#import "CHSectionTitleView.h"
#import "CHSectionHeaderView.h"
#import "CHGridLayout.h"

@class CHGridView;
Expand All @@ -33,14 +33,14 @@
- (void)selectedTileAtIndexPath:(CHGridIndexPath)indexPath inGridView:(CHGridView *)gridView;
- (void)visibleTilesChangedTo:(int)tiles;
- (CGSize)sizeForTileAtIndex:(CHGridIndexPath)indexPath inGridView:(CHGridView *)gridView;
- (CHSectionTitleView *)titleViewForHeaderOfSection:(int)section inGridView:(CHGridView *)gridView;
- (CHSectionHeaderView *)titleViewForHeaderOfSection:(int)section inGridView:(CHGridView *)gridView;
@end

@interface CHGridView : UIScrollView {
CHGridLayout *layout;

NSMutableArray *visibleTiles;
NSMutableArray *visibleSectionTitles;
NSMutableArray *visibleSectionHeaders;
NSMutableArray *reusableTiles;

id<CHGridViewDataSource> dataSource;
Expand All @@ -60,10 +60,6 @@
float rowHeight;
int perLine;
float sectionTitleHeight;

CGSize shadowOffset;
UIColor *shadowColor;
CGFloat shadowBlur;
}

@property (nonatomic, assign) id<CHGridViewDataSource> dataSource;
Expand All @@ -77,18 +73,15 @@
@property (nonatomic) int perLine;
@property (nonatomic) float sectionTitleHeight;

@property (nonatomic) CGSize shadowOffset;
@property (nonatomic, retain) UIColor *shadowColor;
@property (nonatomic) CGFloat shadowBlur;

- (void)reloadData;
- (void)reloadDataAndLayoutUpdateNeeded:(BOOL)layoutNeeded;

- (CHTileView *)dequeueReusableTileWithIdentifier:(NSString *)identifier;

- (CHTileView *)tileForIndexPath:(CHGridIndexPath)indexPath;
- (CHGridIndexPath)indexPathForPoint:(CGPoint)point;

- (void)scrollToTileAtIndexPath:(CHGridIndexPath)indexPath animated:(BOOL)animated;

- (void)deselectTileAtIndexPath:(CHGridIndexPath)indexPath;
- (void)deselectSelectedTile;

Expand Down
120 changes: 59 additions & 61 deletions CHGridView.m
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ - (void)removeSectionTitleNotInRange:(CHSectionRange)range;
- (void)removeAllSubviews;
- (NSMutableArray *)tilesForSection:(int)section;
- (NSMutableArray *)tilesFromIndex:(int)startIndex toIndex:(int)endIndex inSection:(int)section;
- (CHSectionTitleView *)sectionTitleViewForSection:(int)section;
- (CHSectionHeaderView *)sectionHeaderViewForSection:(int)section;
- (void)calculateSectionTitleOffset;
@end

@implementation CHGridView
@synthesize dataSource, centerTilesInGrid, allowsSelection, padding, preLoadMultiplier, rowHeight, perLine, sectionTitleHeight, shadowOffset, shadowColor, shadowBlur;
@synthesize dataSource, centerTilesInGrid, allowsSelection, padding, preLoadMultiplier, rowHeight, perLine, sectionTitleHeight;

- (id)init{
return [self initWithFrame:CGRectZero];
Expand All @@ -35,8 +35,8 @@ - (id)initWithFrame:(CGRect)frame{
if(visibleTiles == nil)
visibleTiles = [[NSMutableArray alloc] init];

if(visibleSectionTitles == nil)
visibleSectionTitles = [[NSMutableArray alloc] init];
if(visibleSectionHeaders == nil)
visibleSectionHeaders = [[NSMutableArray alloc] init];

if(reusableTiles == nil)
reusableTiles = [[NSMutableArray alloc] init];
Expand All @@ -58,10 +58,6 @@ - (id)initWithFrame:(CGRect)frame{

preLoadMultiplier = 5.0f;

shadowOffset = CGSizeMake(0.0f, 0.0f);
shadowColor = [[UIColor colorWithWhite:0.0f alpha:0.5f] retain];
shadowBlur = 0.0f;

[self setBackgroundColor:[UIColor whiteColor]];

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reuseHiddenTiles) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
Expand All @@ -72,11 +68,10 @@ - (id)initWithFrame:(CGRect)frame{
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];

[shadowColor release];
[sectionCounts release];
[layout release];
[reusableTiles release];
[visibleSectionTitles release];
[visibleSectionHeaders release];
[visibleTiles release];
[super dealloc];
}
Expand All @@ -91,35 +86,35 @@ - (void)loadVisibleSectionTitlesForSectionRange:(CHSectionRange)range{
for (i = range.start; i <= range.end; i++) {
BOOL found = NO;

for(CHSectionTitleView *title in visibleSectionTitles){
if(title.section == i) found = YES;
for(CHSectionHeaderView *header in visibleSectionHeaders){
if(header.section == i) found = YES;
}

if(!found){
CGFloat yCoordinate = [layout yCoordinateForTitleOfSection:i];

CHSectionTitleView *sectionTitle = nil;
CHSectionHeaderView *sectionHeader = nil;

if([[self delegate] respondsToSelector:@selector(titleViewForHeaderOfSection:inGridView:)]){
sectionTitle = [[self delegate] titleViewForHeaderOfSection:i inGridView:self];
[sectionTitle setFrame:CGRectMake(b.origin.x, yCoordinate, b.size.width, sectionTitleHeight)];
sectionHeader = [[self delegate] titleViewForHeaderOfSection:i inGridView:self];
[sectionHeader setFrame:CGRectMake(b.origin.x, yCoordinate, b.size.width, sectionTitleHeight)];
}else{
sectionTitle = [[CHSectionTitleView alloc] initWithFrame:CGRectMake(b.origin.x, yCoordinate, b.size.width, sectionTitleHeight)];
sectionHeader = [[CHSectionHeaderView alloc] initWithFrame:CGRectMake(b.origin.x, yCoordinate, b.size.width, sectionTitleHeight)];
if([dataSource respondsToSelector:@selector(titleForHeaderOfSection:inGridView:)])
[sectionTitle setTitle:[dataSource titleForHeaderOfSection:i inGridView:self]];
[sectionHeader setTitle:[dataSource titleForHeaderOfSection:i inGridView:self]];
}

[sectionTitle setYCoordinate:yCoordinate];
[sectionTitle setSection:i];
[sectionTitle setAutoresizingMask:(UIViewAutoresizingFlexibleWidth)];
[sectionHeader setYCoordinate:yCoordinate];
[sectionHeader setSection:i];
[sectionHeader setAutoresizingMask:(UIViewAutoresizingFlexibleWidth)];

if(self.dragging || self.decelerating)
[self insertSubview:sectionTitle atIndex:self.subviews.count - 1];
[self insertSubview:sectionHeader atIndex:self.subviews.count - 1];
else
[self insertSubview:sectionTitle atIndex:self.subviews.count];
[self insertSubview:sectionHeader atIndex:self.subviews.count];

[visibleSectionTitles addObject:sectionTitle];
[sectionTitle release];
[visibleSectionHeaders addObject:sectionHeader];
[sectionHeader release];
}
}

Expand All @@ -138,10 +133,6 @@ - (void)loadVisibleTileForIndexPath:(CHGridIndexPath)indexPath withRect:(CGRect)

[tile setIndexPath:indexPath];
[tile setSelected:NO];

[tile setShadowOffset:shadowOffset];
[tile setShadowColor:shadowColor];
[tile setShadowBlur:shadowBlur];

if([[self delegate] respondsToSelector:@selector(sizeForTileAtIndex:inGridView:)] && centerTilesInGrid){
CGSize size = [[self delegate] sizeForTileAtIndex:indexPath inGridView:self];
Expand Down Expand Up @@ -186,24 +177,28 @@ - (void)reuseHiddenTiles{
- (void)removeSectionTitleNotInRange:(CHSectionRange)range{
NSMutableArray *toDelete = [NSMutableArray array];

for (CHSectionTitleView *title in visibleSectionTitles) {
int s = title.section;
for (CHSectionHeaderView *header in visibleSectionHeaders) {
int s = header.section;
if(s < range.start || s > range.end){
[toDelete addObject:title];
[toDelete addObject:header];
}
}

[toDelete makeObjectsPerformSelector:@selector(removeFromSuperview)];
[visibleSectionTitles removeObjectsInArray:toDelete];
[visibleSectionHeaders removeObjectsInArray:toDelete];
}

- (void)reloadData{
[self reloadDataAndLayoutUpdateNeeded:YES];
}

- (void)reloadDataAndLayoutUpdateNeeded:(BOOL)layoutNeeded{
if(dataSource == nil) return;

[self removeAllSubviews];
[visibleTiles removeAllObjects];
[visibleSectionTitles removeAllObjects];

[visibleSectionHeaders removeAllObjects];
CGRect b = [self bounds];

if([dataSource respondsToSelector:@selector(numberOfSectionsInGridView:)]){
Expand All @@ -215,22 +210,25 @@ - (void)reloadData{

[sectionCounts removeAllObjects];

[layout setGridWidth:b.size.width];
[layout setPadding:padding];
[layout setPerLine:perLine];
[layout setPreLoadMultiplier:preLoadMultiplier];
[layout setRowHeight:rowHeight];
[layout setSectionTitleHeight:sectionTitleHeight];
if(layoutNeeded){
[layout setGridWidth:b.size.width];
[layout setPadding:padding];
[layout setPerLine:perLine];
[layout setPreLoadMultiplier:preLoadMultiplier];
[layout setRowHeight:rowHeight];
[layout setSectionTitleHeight:sectionTitleHeight];

[layout setSections:sections];
int i;
for(i = 0; i < sections; i++){
int numberInSection = [dataSource numberOfTilesInSection:i GridView:self];
[sectionCounts addObject:[NSNumber numberWithInt:numberInSection]];
[layout setNumberOfTiles:numberInSection ForSectionIndex:i];
}

[layout setSections:sections];
int i;
for(i = 0; i < sections; i++){
int numberInSection = [dataSource numberOfTilesInSection:i GridView:self];
[sectionCounts addObject:[NSNumber numberWithInt:numberInSection]];
[layout setNumberOfTiles:numberInSection ForSectionIndex:i];
[layout updateLayout];
}

[layout updateLayout];

[self setNeedsLayout];

maxReusable = ceilf((self.bounds.size.height / rowHeight) * perLine) * 2;
Expand Down Expand Up @@ -288,7 +286,7 @@ - (void)layoutSubviews{

- (void)removeAllSubviews{
[visibleTiles makeObjectsPerformSelector:@selector(removeFromSuperview)];
[visibleSectionTitles makeObjectsPerformSelector:@selector(removeFromSuperview)];
[visibleSectionHeaders makeObjectsPerformSelector:@selector(removeFromSuperview)];
[reusableTiles makeObjectsPerformSelector:@selector(removeFromSuperview)];
}

Expand Down Expand Up @@ -333,14 +331,14 @@ - (NSMutableArray *)tilesFromIndex:(int)startIndex toIndex:(int)endIndex inSecti

#pragma mark section title accessor methods

- (CHSectionTitleView *)sectionTitleViewForSection:(int)section{
CHSectionTitleView *titleView = nil;
- (CHSectionHeaderView *)sectionHeaderViewForSection:(int)section{
CHSectionHeaderView *headerView = nil;

for(CHSectionTitleView *sectionView in visibleSectionTitles){
if([sectionView section] == section) titleView = sectionView;
for(CHSectionHeaderView *header in visibleSectionHeaders){
if([headerView section] == section) headerView = header;
}

return titleView;
return headerView;
}

#pragma mark indexPath accessor methods
Expand Down Expand Up @@ -444,15 +442,15 @@ - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
- (void)calculateSectionTitleOffset{
float offset = self.contentOffset.y;

for(CHSectionTitleView *title in visibleSectionTitles){
CGRect f = [title frame];
float sectionY = title.yCoordinate;
for(CHSectionHeaderView *header in visibleSectionHeaders){
CGRect f = [header frame];
float sectionY = [header yCoordinate];

if(sectionY <= offset && offset > 0.0f){
f.origin.y = offset;
if(offset <= 0.0f) f.origin.y = sectionY;

CHSectionTitleView *sectionTwo = [self sectionTitleViewForSection:title.section + 1];
CHSectionHeaderView *sectionTwo = [self sectionHeaderViewForSection:header.section + 1];
if(sectionTwo != nil){
CGFloat sectionTwoHeight = sectionTwo.frame.size.height;
CGFloat sectionTwoY = sectionTwo.yCoordinate;
Expand All @@ -464,10 +462,10 @@ - (void)calculateSectionTitleOffset{
f.origin.y = sectionY;
}

if(f.origin.y <= offset) [title setOpaque:NO];
else [title setOpaque:YES];
if(f.origin.y <= offset) [header setOpaque:NO];
else [header setOpaque:YES];

[title setFrame:f];
[header setFrame:f];
}
}

Expand Down
2 changes: 1 addition & 1 deletion CHSectionTitleView.h → CHSectionHeaderView.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

#import <UIKit/UIKit.h>

@interface CHSectionTitleView : UIView {
@interface CHSectionHeaderView : UIView {
UIView *topLine;

int section;
Expand Down
4 changes: 2 additions & 2 deletions CHSectionTitleView.m → CHSectionHeaderView.m
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
// http://cameron.io/project/chgridview
//

#import "CHSectionTitleView.h"
#import "CHSectionHeaderView.h"

@implementation CHSectionTitleView
@implementation CHSectionHeaderView
@synthesize section, title, yCoordinate;

- (id)initWithFrame:(CGRect)frame {
Expand Down
17 changes: 10 additions & 7 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -17,31 +17,34 @@ NOTICE: **CHGridView is not production-level software yet. Don't use it in shipp
- CHGridView: UIScrollView subclass that handles loading and display of tiles and section titles.
- CHTileView: UIView subclass to draw content, intended to be subclassed for specific use.
- CHImageTileView: CHTileView subclass to easily display images with a border, similar to Apple's Photos application.
- CHSectionTitleView: UIView subclass to display section names, can be subclassed and used for custom section titles.
- CHSectionHeaderView: UIView subclass to display section headers, can be subclassed and used for custom section titles.
- CHGridLayout: Computes layout and caches it in a big array, making it easy for CHGridView to only display visible tiles and section titles.
- CHGridLayoutTile: Simple object that stores `indexPath` and `rect` for a tile.
- CHGridLayoutSection: Simple object to store section index and its Y co-ordinate position.

###Usage:

Exactly like UITableView. Just implement the two required data source methods: `numberOfTilesInSection` and `tileForIndexPath`. CHGridView assumes there is at least one section. The method `tileForIndexPath` works very much like UITableView; CHGridView reuses tiles just like UITableView reuses cells. Call `dequeueReusableTileWithIdentifier` to get a reusable tile, if it's `nil`, `init` and `autorelease` a new tile and return it.
Exactly like UITableView. Just implement the two required data source methods: `numberOfTilesInSection` and `tileForIndexPath`. CHGridView assumes there is at least one section. The method `tileForIndexPath` works very much like UITableView; CHGridView reuses tiles like UITableView reuses cells. Call `dequeueReusableTileWithIdentifier` to get a reusable tile, if it's `nil`, `init` and `autorelease` a new tile and return it.

There's two basic styles to use in GHGridView, one that resembles the Photos application, and one that mimics iPhoto and the iPad photo grid. The property that controls it is called `centerTilesInGrid`. Set it to `YES` for the iPhoto style.

Row height, tiles per line, padding, section title height and shadow are all properties of CHGridView. These are not meant to change often like the data source and delegate methods. However, if you do change them, make sure to call `reloadData` to recalculate the layout.

Shadows are drawn in Core Graphics and add to your padding. Adjust shadow properties and padding until a desirable result is achieved. You can set shadows for a CHTileView directly, but don't; set shadow properties in CHGridView and they will be applied to all tiles.
Shadows are drawn in Core Graphics and add to your padding. Adjust shadow properties and padding until a desirable result is achieved.

Orientation changes and resizing are supported. Section titles and tiles are set to the correct autoresizing mask, but you should call `reloadData` after you resize CHGridView for optimal re-layout, same goes for re-orientation.

If you disable scrolling with `setScrollingEnabled`, you can probably use this as a un-scrollable grid view, but I haven't tested it.

###Behind the scenes:

- CHGridView only loads visible tiles and section titles, plus two rows above and beneath (on the Original iPhone, iPhone 3G or first-generation iPod Touch, it only loads half a row). There's only about 30 tiles loaded at any single time.
- CHTileView shadows are not transparent, they are rendered onto the same background color as CHGridView. It's possible to change it if you long for the scrolling performance of Android or WebOS.
CHGridView only loads visible tiles and section titles, plus four rows above and beneath (on the Original iPhone, iPhone 3G or first-generation iPod Touch, it only loads two rows). There's only about 30 or 50 tiles loaded at any single time.

CHGridView, like most scalable computer interfaces, makes trade-offs. It's currently optimized to for low memory usage, but requires more processor cyles. Performance is roughly the same if you use 3000 tiles or 200 tiles. Optimally CHGridView should be smart enough to switch this trade-off at some point (if < 400 tiles, for instance).

- CHTileView shadows are not transparent, they are rendered onto the same background color as CHGridView. It's possible to change it if you long for the scrolling performance of Android or Palm webOS.
- CHImageTileView supports scaling images up/down to fit its frame (and preserves aspect ratio) but it's not fast enough to use. The property is called `scalesImageToFit` and you should never use it.
- Section titles are only transparent when they need to be, otherwise they are opaque. If you subclass CHSectionTitleView, you'll need to check [self isOpaque] to deal with transparency on your own.
- Section headers are only transparent when they need to be, otherwise they are opaque. If you subclass CHSectionHeaderView, you'll need to check `[self isOpaque]` to compensate for transparency if needed.

###Roadmap (roughly in order):

Expand All @@ -57,7 +60,7 @@ If you disable scrolling with `setScrollingEnabled`, you can probably use this a

###Performance:

I've tested CHGridView informally with a test application on both my iPhones. For my data source, I used 31 images to populate 1,984 tiles separated with 64 sections. They were exported from iPhoto as PNGs with a maximum width of 160 pixels, then were drawn centered and cropped in CHImageTileView. Scrolling performance is not as good as Apple's Photos grid view, especially on my original iPhone.
I've tested CHGridView informally with a test application on both my iPhones. For my data source, I used 31 images to populate 2,976 tiles separated into 96 sections. They were exported from iPhoto as PNGs with a maximum width of 160 pixels, then were drawn centered and cropped in CHImageTileView. Scrolling performance is not as good as Apple's Photos grid view, especially on my original iPhone.

- Original iPhone: about 18 - 35 fps.
- iPhone 3G3: about 30 - 50 fps.
Expand Down

0 comments on commit d2ec268

Please sign in to comment.