BDGridView is the heart of the user experience of Pholio, and this article
should give you the high-level overview you need to understand the source
BDGridView a versatile class. I can use it to show all of the galleries as tiles, or
I can show the individual photos in a set:
To achieve this versatility,
BDGridView uses a similar delegate pattern
to table views. Like a table view,
BDGridView has a
that tells the grid view how many cells there are (
and supplies the content of each cell (
gridView:cellForIndex:). The only
additional thing that the grid data source must do that a table data source
does not is supply the size of each cell (
With the delegate responsible for determining what cells to display, the
BDGridView is simply to lay out the cells in a grid. Because there
are likely more cells than will fit within the drawing area,
UIScrollView lets you have content that is larger than what you can
view on the screen. The scroll view’s
contentArea property defines
the logical size of your view — it’s the blue line in the diagram above.
contentOffset is the upper left corner of the visible area. If you
contentOffset, the visible area will move around. The standard
bounds property is the full viewable area — the black line in the
The trick to using a
UIScrollView effectively is to only create the subviews
you need to draw the visible area. For
BDGridView, all of this work happens
layoutSubviews. Here’s the rough outline of what happens. (Note: Much
of this comes from Apple’s excellent PhotoScroller
I recompute how large the
contentAreais. In the simple case where every grid cell is the same size, this is trivial. I make the width of the
contentAreamatch the width of the
bounds. This means I will use all available horizontal space with no side-to-side scrolling. Once I’ve fixed the width of
contentArea, I compute how many cells will fit across in one row. (The actual implementation isn’t simple division because I might want to guarantee a minimum distance between each cell in the row, but conceptually it’s just division.) Finally, once I know the number of cells per row, I can compute the number of rows I need to display all of my cells. The size of the cell determines the height of each row, and simple multiplication gives me the height of
Why do I do this each time in
layoutSubviews? To handle rotation! When you rotate your iPad, the width of the viewable area changes… which could mean a different number of cells per row, and therefore a different number of rows and a completely different
contentAreaat the end of the rotation.
BDGridViewmaintains a set of all of the cells that are currently visible, creatively named
visibleViews. The next step in
layoutSubviewsis to walk through each cell in
visibleViewsand make sure it’s still supposed to be visible. Perhaps it scrolled off the screen? If it’s not on the screen any more, the cell gets removed as a subview, removed from
visibleViews, and added to
layoutSubviewswalks through each cell index that is supposed to be visible and makes sure that it really is visible. If it finds a cell that is supposed to be visible but isn’t, it asks the view delegate to create the cell for that index. Just like a table view delegate, the grid view delegate will reuse a cell from
recycledViewsif one is available.
layoutSubviewsgoes through each visible view and makes sure that the
frameproperty is set appropriately: This will make sure each cell is in the right place in the content area. Note this is done for all visible cells and not just the ones that were just added to the set of visible cells, again to handle rotation. When you rotate the screen, the position of currently visible cells may need to change to handle the new width of the content area.
One Wrinkle: “Drop Cap” Style
In Pholio version 2.2, I added support for an optional “drop cap,” named after the typographic practice of having the first letter in a paragraph span several lines of text. This one simple change lets me create layouts with much more visual interest than a monotonous grid. You can see in the following screenshots that the Nature tile spans two rows and two columns.
To support drop cap cells, I formalized the concepts of a grid index and a cell index. The grid index defines the location of a cell in the content area, and the cell index defines the item in the grid. If you use a drop cap, the first cell index takes up more than one grid index. That’s a mouthful, so hopefully pictures help. In the screenshots above, the image on the left shows the grid indexes, and the image on the right shows the cell indexes.
Most of the work of supporting the drop cap style was writing the two routines
that convert a grid index to a cell index, and vice versa. Armed with those
routines, I had to make sure that whenever
BDGridView deals with the location of things in the content
area, it uses the grid index of the cell. (For instance, when setting the
frame of a visible cell in
layoutSubviews, I use the grid index of
the cell.) However, whenever I ask the delegate for a new cell to display, I use
the cell index.
This covers the basics of
BDGridView. While this class is not currently
written as a stand-alone library, I designed it to be general-purpose. I hope
you find either the code or the ideas helpful in your own projects.