194 lines
5.9 KiB
Objective-C
194 lines
5.9 KiB
Objective-C
//
|
|
// KTThumbsView.m
|
|
// Sample
|
|
//
|
|
// Created by Kirby Turner on 3/23/10.
|
|
// Copyright 2010 White Peak Software Inc. All rights reserved.
|
|
//
|
|
|
|
#import "KTThumbsView.h"
|
|
#import "KTThumbView.h"
|
|
#import "KTThumbsViewController.h"
|
|
|
|
|
|
@implementation KTThumbsView
|
|
|
|
@synthesize dataSource = dataSource_;
|
|
@synthesize controller = controller_;
|
|
@synthesize thumbsHaveBorder = thumbsHaveBorder_;
|
|
@synthesize thumbsPerRow = thumbsPerRow_;
|
|
@synthesize thumbSize = thumbSize_;
|
|
|
|
|
|
- (id)initWithFrame:(CGRect)frame
|
|
{
|
|
self = [super initWithFrame:frame];
|
|
if (self) {
|
|
// Set default values.
|
|
thumbsHaveBorder_ = YES;
|
|
thumbsPerRow_ = NSIntegerMin; // Forces caluation because on view size.
|
|
thumbSize_ = CGSizeMake(75, 75);
|
|
|
|
// We keep a collection of reusable thumbnail
|
|
// views. This improves performance by not
|
|
// requiring a create view each and every time.
|
|
reusableThumbViews_ = [[NSMutableSet alloc] init];
|
|
|
|
// No thumbnail views are visible at first; note this by
|
|
// making the firsts very high and the lasts very low
|
|
firstVisibleIndex_ = NSIntegerMax;
|
|
lastVisibleIndex_ = NSIntegerMin;
|
|
lastItemsPerRow_ = NSIntegerMin;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (KTThumbView *)dequeueReusableThumbView
|
|
{
|
|
KTThumbView *thumbView = [reusableThumbViews_ anyObject];
|
|
if (thumbView != nil) {
|
|
// The only object retaining the view is the
|
|
// reusableThumbViews set, so we retain/autorelease
|
|
// it before returning it so that it's not immediately
|
|
// deallocated when removed form the set.
|
|
|
|
[reusableThumbViews_ removeObject:thumbView];
|
|
|
|
}
|
|
return thumbView;
|
|
}
|
|
|
|
- (void)queueReusableThumbViews
|
|
{
|
|
for (UIView *view in [self subviews]) {
|
|
if ([view isKindOfClass:[KTThumbView class]]) {
|
|
[reusableThumbViews_ addObject:view];
|
|
[view removeFromSuperview];
|
|
}
|
|
}
|
|
|
|
firstVisibleIndex_ = NSIntegerMax;
|
|
lastVisibleIndex_ = NSIntegerMin;
|
|
}
|
|
|
|
- (void)reloadData
|
|
{
|
|
[self queueReusableThumbViews];
|
|
[self setNeedsLayout];
|
|
}
|
|
|
|
- (void)layoutSubviews
|
|
{
|
|
[super layoutSubviews];
|
|
|
|
CGRect visibleBounds = [self bounds];
|
|
int visibleWidth = visibleBounds.size.width;
|
|
int visibleHeight = visibleBounds.size.height;
|
|
|
|
// Do a bunch of math to determine which rows and colums
|
|
// are visible.
|
|
|
|
NSInteger itemsPerRow = thumbsPerRow_;
|
|
if (itemsPerRow == NSIntegerMin) {
|
|
itemsPerRow = floor(visibleWidth / thumbSize_.width);
|
|
}
|
|
if (itemsPerRow != lastItemsPerRow_) {
|
|
// Force re-load of grid cells. Unfortunately this means
|
|
// visible cells will reload, which can hurt performance
|
|
// when the thumbnail image isn't cached. Need to find a
|
|
// better approach.
|
|
[self queueReusableThumbViews];
|
|
}
|
|
lastItemsPerRow_ = itemsPerRow;
|
|
|
|
// Ensure a minimum of space between images.
|
|
int minimumSpace = 5;
|
|
if (visibleWidth - itemsPerRow * thumbSize_.width < minimumSpace) {
|
|
itemsPerRow--;
|
|
}
|
|
|
|
if (itemsPerRow < 1) itemsPerRow = 1; // Ensure at least one per row.
|
|
|
|
int spaceWidth = round((visibleWidth - thumbSize_.width * itemsPerRow) / (itemsPerRow + 1));
|
|
int spaceHeight = spaceWidth;
|
|
|
|
// Calculate content size.
|
|
NSInteger thumbCount = [dataSource_ thumbsViewNumberOfThumbs:self];
|
|
int rowCount = ceil(thumbCount / (float)itemsPerRow);
|
|
int rowHeight = thumbSize_.height + spaceHeight;
|
|
CGSize contentSize = CGSizeMake(visibleWidth, (rowHeight * rowCount + spaceHeight));
|
|
[self setContentSize:contentSize];
|
|
|
|
NSInteger rowsPerView = visibleHeight / rowHeight;
|
|
NSInteger topRow = MAX(0, floorf(visibleBounds.origin.y / rowHeight));
|
|
NSInteger bottomRow = topRow + rowsPerView;
|
|
|
|
|
|
CGRect extendedVisibleBounds = CGRectMake(visibleBounds.origin.x, MAX(0, visibleBounds.origin.y), visibleBounds.size.width, visibleBounds.size.height + rowHeight);
|
|
|
|
// Recycle all thumb views that are no longer visible
|
|
for (UIView *view in [self subviews]) {
|
|
|
|
if ([view isKindOfClass:[KTThumbView class]]) {
|
|
// We want to see if the view intersect the scrollView's
|
|
// bounds, so we need to convert their frames to our own
|
|
// coordinate system.
|
|
CGRect viewFrame = [view frame];
|
|
|
|
// If the view doesn't intersect, it's not visible, so we can recycle it
|
|
if (! CGRectIntersectsRect(viewFrame, extendedVisibleBounds)) {
|
|
[reusableThumbViews_ addObject:view];
|
|
[view removeFromSuperview];
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
NSInteger startAtIndex = MAX(0, topRow * itemsPerRow);
|
|
NSInteger stopAtIndex = MIN(thumbCount, (bottomRow * itemsPerRow) + itemsPerRow);
|
|
|
|
// Set our initial origin.
|
|
int x = spaceWidth;
|
|
NSInteger y = spaceHeight + (topRow * rowHeight);
|
|
|
|
// Iterate through the needed thumbnail views adding
|
|
// any views that are missing.
|
|
for (NSInteger index = startAtIndex; index < stopAtIndex; index++) {
|
|
// If index is between first and last, then not missing.
|
|
BOOL isThumbViewMissing = !(index >= firstVisibleIndex_ && index < lastVisibleIndex_);
|
|
|
|
if (isThumbViewMissing) {
|
|
KTThumbView *thumbView = [dataSource_ thumbsView:self thumbForIndex:index];
|
|
|
|
// Set the frame so the view is inserted into the correct position.
|
|
CGRect newFrame = CGRectMake(x, y, thumbSize_.width, thumbSize_.height);
|
|
[thumbView setFrame:newFrame];
|
|
|
|
// Store the current index so the thumb view can
|
|
// find it later.
|
|
[thumbView setTag:index];
|
|
|
|
[thumbView setHasBorder:thumbsHaveBorder_];
|
|
|
|
[self addSubview:thumbView];
|
|
}
|
|
|
|
|
|
// Adjust the position.
|
|
if ( (index+1) % itemsPerRow == 0) {
|
|
// Start new row.
|
|
x = spaceWidth;
|
|
y += thumbSize_.height + spaceHeight;
|
|
} else {
|
|
x += thumbSize_.width + spaceWidth;
|
|
}
|
|
}
|
|
|
|
// Remember which thumb view indexes are visible.
|
|
firstVisibleIndex_ = startAtIndex;
|
|
lastVisibleIndex_ = stopAtIndex;
|
|
}
|
|
|
|
|
|
@end
|