Reputation: 22252
Using a UICollectionViewController
and my own UICollectionViewLayout
subclass, I've put together a view that shows a Gannt style time chart. Using that framework to do the bands has been really easy.
What's not as clear as to me is how to design the backing view that gives time context. I sketched up an example in Inkscape:
The black rectangular outline is an example of where the user might currently be scrolled to.
So, I get how to do the pink/orange/yellow bands. What I'm less clear how to achieve is the background striping and the time labels.
One option that I've started on is to make a custom UIView
subclass and set it as the backgroundView
property of my collectionView
. Doing the drawRect:
to draw the vertical stripes would be easy.
The harder part is getting the time labels to show up always at the top/bottom of the current scroll area, rather than at the edges of the backgroundView. Is there a way to figure out what the current visible area of my background view is, rather than the frame/bounds
which will be "full" view?
Or should I be somehow using the decoration features of the UICollectionView
and UICollectionViewLayout
. Didn't seem like a good fit for what I saw of the API there, but this is my first UICollectionView
, so maybe I'm wrong?
UPDATE
I have learned 2 things since the original post, which leave me even more confused how to accomplish this than before:
The view (if any) in this property is positioned underneath all of the other content and sized automatically to fill the entire bounds of the collection view.
They basically mean the screen of the device, not the actual extent of the view. I found with logging, that no matter the scrolling, the bounds\frame
and drawRect:
argument were always the size of the screen.
layoutAttributesForElementsInRect:
argument. My thought was that I would fit UICollectionViewLayoutAttributes
to the incoming argument. Unfortunately, this rectangle seems to often be larger than the viewable area. I assume they do some caching beyond the edges of the visible area.Upvotes: 0
Views: 1328
Reputation: 22252
Here's what I ended up doing. I actually ended up (ab?)using a decoration view. Here's how I did it, I was pretty pleased with the result actually:
I added a TimeBarsView
class, as a subclass of UICollectionReusableView
:
@interface TimeBarsView : UICollectionReusableView
@property (assign, nonatomic) NSInteger offset; // horizontal scroll offset
@end
And I use the the offset
property to draw the background view as if it were scrolled to that position in its drawRect:
method. The piece that glues that together is:
- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes {
[super applyLayoutAttributes: layoutAttributes];
self.offset = layoutAttributes.indexPath.item;
}
The trick here is that I'm using the the IndexPath
of the background path to communicate its scroll position.
In my UIControllerViewLayout
subclass, I implement the following methods:
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
return YES;
}
Called from initWithCoder
and init
:
- (void)registerTimeBars {
[self registerClass:[TimeBarsView class] forDecorationViewOfKind:@"TimeBars"];
}
The usual layoutAttributesForElementsInRect:
method with a preamble like this:
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
NSMutableArray *inRect = [NSMutableArray array];
[inRect addObject:
[self
layoutAttributesForDecorationViewOfKind:@"TimeBars" // link to the TimeBars
atIndexPath: [NSIndexPath
indexPathForItem:self.collectionView.contentOffset.x // use the current scroll x value as the indexPath
inSection:0]]];
... // other layouts for the normal item views like usual
return inRect;
}
And finally
- (UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString *)decorationViewKind atIndexPath:(NSIndexPath *)indexPath {
UICollectionViewLayoutAttributes *layoutAttributes = [UICollectionViewLayoutAttributes layoutAttributesForDecorationViewOfKind:decorationViewKind withIndexPath:indexPath];
CGPoint offset = self.collectionView.contentOffset;
CGSize size = self.collectionView.bounds.size;
// align current frame so it matches the current scroll box
layoutAttributes.frame = CGRectMake(offset.x, offset.y, size.width, size.height);
layoutAttributes.zIndex = -1; // make sure it's below other views
return layoutAttributes;
}
Upvotes: 2
Reputation: 66242
Should I be somehow using the decoration features…?
No, these are equivalent to headers/footers in a UITableView. They won't work for the background.
Honestly, I think you're overcomplicating it. I would just make two UICollectionViews - one for the yellow/orange/pink bars, and one underneath for the time bars. Disable user interaction on the bottom collection view. In the scrollViewDidScroll:
delegate callback, set the content offset of the bottom collection view to match the content offset of the top collection view. Then the bottom one will scroll with the top one, and they will appear to work as one.
Upvotes: 1