UITableView Tricks – Part 2 – Infinite Scrolling

Your ads will be inserted here by

Easy Plugin for AdSense.

Please go to the plugin admin page to
Paste your ad code OR
Suppress this ad slot.

Last time I posted about the UITableView Tricks to lay the cells of the tablview in Circular fashion. It was well received by the community and thank you all for your feedback and compliments. 

I received many requests for making this circular list scroll infinitely as with UIPickerView, which means that the list repeats forever. It really makes sense just because the list itself is in circular fashion and user expect the content to repeat, without him/her having to scroll back to top.

I came across the 2011 WWDC video – Advanced Scrollview Techniques which provides the best feasible solution to support infinite scrolling. Many attempts have been made before to find a solution by having the height of the scrollview’s content size to a large number so that it takes days, if not AGES for a user to get to the end point. But it was never a complete solution.

 

I went a bit closer to the perfect solution and here it is. The goal here is to provide a generic solution to a UITableView while having the user/ developer to do minimum or ZERO changes. This lead me to create a subclass of UITableView, and named as BBTableView

 

By using BBTableView,  a developer has to do ZERO changes ( some minor changes for additional functionality, explained later ) to enable infinite scrolling. 

 

Behind the scenes:

 

 The core logic of this solution is

 

1. To increase the tableview content by a factor of 3, so that we make the 3 copies of the content laid one after another vertically. 

2. Whenever the top end of the scroll is reached, move the the scroll offset back to start of the 2nd copy

3. When the bottom end of the scroll is reached, we move the scroll offset back to the start of the 2nd copy minus the height of the tableview, so that we end up showing the same content as we are now.

Infinite Scrolling

This solution did bring an obstacle to my goal of making this component as a Drag-n-Drop component. i.e. Making 2 additional copies of rows provided by datasource also changes the index paths used in datasource or delegate methods that are propagated to the component user, a developer who would use this component.

Your ads will be inserted here by

Easy Plugin for AdSense.

Please go to the plugin admin page to
Paste your ad code OR
Suppress this ad slot.

This can be avoided by intercepting the delegate or datasource methods to morph the index paths exchanged between the UITableView and the its datasource or delegate methods.

Thanks Evadne Wu , for allowing me to use the interceptor component written by her.

Two  properties have been added to BBTableView,

a. contentAlignment

Allows the change the direction of the semi-circle. It supports two directions


ClockWiseScreen Shot 2012 12 07 at 9 25 33 PM

eBBTableViewContentAlignmentRight Layouts the circle Clockwise

eBBTableViewContentAlignmentRight – Layouts the circle Anti-Clockwise

b. enableInfiniteScrolling

    set YES to enable infinite scrolling. NO to reset to default scrolling behavior of UITableView

 

As every software, this component too have its Cons:

1. Since the issue with Index Paths resolved by intercepting delegate / datasource methods, Any additions or modifications to UITableView datasource/ delegate methods involving index path, requires a change to counterpart datasource / delegate methods implemented in BBTableView. However, on a good note, this change includes only getting the morphed index path and calling appropriate method on the receiver of Datasource interceptor.

A  sample intercepted Datasource method would look like:

(UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return [_dataSourceInterceptor.receiver tableView:tableView cellForRowAtIndexPath:MORPHED_INDEX_PATH(indexPath)];

}

 

Check the source code updated in github here https://github.com/bharath2020/UITableViewTricks

Happy Coding 🙂

UITableView Tricks

Your ads will be inserted here by

Easy Plugin for AdSense.

Please go to the plugin admin page to
Paste your ad code OR
Suppress this ad slot.

 

 

 

 

Recently we came across innovative UI controls like Path and Clear controls. I am pretty amazed to see a simple UITableView and its cells in iOS, can be tweaked to get a eye-catching effects as in Clear app.  After that there came a series of controls using UITableView, but this one from raw engineering was quiet nice.

 

After playing with it, I revisited the nuances of UITableView and came up with this simple UI Layoout which is our topic now. Take at look at this demo

 

[youtube=http://youtu.be/gaM4V9Dcd4k]

 

You can find the Circle View Source in GitHub

 

I drew this sketch quickly.

 

 

IMG 4359

  

 

I laid down some rules before I started implementing.

 

1. I made clear that this control will not built ground up from scratch

2. Want to leverage the full power of Dequeue in UITableView 

3. Above two rules, means I should be concentrating only on how the content is laid out to present the data in different manner to the user and make it fun.

 

The main task that revolves around this control is how do we lay out the table cells. I used some basic trigonometry function which find out the point in circumference of the circle.

 

So the heart of this control resides in this method where on the while we find out the YPosition of each cell and adding yOffset of the Scrollview’s content offset. This means that the cell would move relatively as the tableview is scrolled.

 

The goal is to find out the X Position which is obtained by using Circle / Ellipse Equation

 

y = vertical_radius * sin ( angle )


x = horizontal_radius * cos (angle)

 

-(void)setupShapeFormationInVisibleCells

{

    NSArray *indexpaths = [mTableViewindexPathsForVisibleRows];

    float shift = ((int)mTableView.contentOffset.y % (int)mTableView.rowHeight);  

    int totalVisibleCells =[indexpaths count];

   

    float y = 0.0;

    float radius = mTableView.frame.size.height/2.0f;

    float xRadius = radius;

    

    for( NSUInteger index =0; index < totalVisibleCells; index++ )

    {

        BBCell *cell = (BBCell*)[mTableView cellForRowAtIndexPath:[ indexpaths objectAtIndex:index]];

        CGRect frame = cell.frame;

        //we get the yPoint on the circle of this Cell

        y = (radius-(index*mTableView.rowHeight) );//ideal yPoint if the scroll offset is zero

        y+=shift;//add the scroll offset

        

        

        //We can find the x Point by finding the Angle from the Ellipse Equation of finding y

        //i.e. Y= vertical_radius * sin(t )

        // t= asin(Y / vertical_radius) or asin = sin inverse

        float angle = asinf(y/(radius));

        

        //Apply Angle in X point of Ellipse equation

        //i.e. X = horizontal_radius * cos( t )

        //here horizontal_radius would be some percentage off the vertical radius.

        //percentage is defined by HORIZONTAL_RADIUS_RATIO

        //HORIZONTAL_RADIUS_RATIO of 1 is equal to circle

        float x = (floorf(xRadius*HORIZONTAL_RADIUS_RATIO)) * cosf(angle );

        x = x + HORIZONTAL_TRANSLATION;

        

        frame.origin.x = x ;

        if( !isnan(x))

        {

            cell.frame = frame;

        }

    }

}

 

 

Couple Macros found in source code which you could control”

  • HORIZONTAL_RADIUS_RATIO – defines the ratio between Vertiacl and horizontal radius. HORIZONTAL_RADIUS_RATIO of value 1 is equal to circle
  • HORIZONTAL_TRANSLATION – Helps translate the position of whole Circle / Ellipse i.e the position of the cells 

You can find the Circle View Source in GitHub

 

 

IMPORTANT UPDATE: 

 

A major improvement has been made to this project. Please read UITableView Tricks – Infinite Scrolling  to know about the new features and improvements.

 

 

——————————————————————————————-

UPDATE #1: 

This Post has been featured in 

 

 ——————————————————————————————–

UPDATE #2:

Have modified the source code to support the inverse form of the circle i.e the circle can be turned toward right as shown in figure by setting the CIRCLE_RIGHT_DIRECTION macro to 1

 

Screen Shot 2012 07 19 at 11 03 55 PM

———————————————————————————————- 

 

 

 

Hope you enjoyed this. Let me know your comments.

Happy Coding 🙂

Pattern Drawing with CGPattern in iOS or Mac

If you are looking for help on how to stroke or fill up a pattern in iOS or Mac OS X, then You have landed at the right place.

NOTE: A reader of this post is assumed to have prior knowledge of custom drawing in iOS’s UIView, CGContext, and Quartz 2D basics

You can find the Code sample from Github

The Sample code demonstrates the following things:

  • Creation of 3 Different kinds of patterns using CGPattern
  • In the Sample, tap “Change Pattern” to switch to next pattern
  • Use the slider, to increase the size of pattern cell

pattern is an image, usually small, used for filling regions by tiling, that is, by placing copies of the pattern side by side like ceramic tiles. Here we see how we can achieve pattern filling in iOS or Mac using CGPattern.

A step by step explanation on usage of CGPatterns

Quartz draw the patterns using cell based drawing technique, where it would provide us the option to draw a single pattern cell in a context, and uses the context to tile across the remaining area evenly.

STEP 1 : Create a CGPattern instance as below

//Pattern Callback methods

// drawPatternCell – callback to draw a single cell

//PatternReleaseInfoCallback – invoked when a pattern is released

CGPatternCallbacks callBack;

callBack.drawPattern = &drawPatternCell;

callBack.releaseInfo = &PatternReleaseInfoCallback;

callBack.version = 0;

 

// param1 – &patters_types[patternCount] – a context info, here which defines which pattern to be drawn


// param2 – rect of a single pattern cell

 

// param3 – transformation matrix

 

// offsetX – a offset or horizontal gap between each cell.. minimum should be width of the cell

 

// offsetY – a offset of vertical gap between each cell. minimum should be heigh of the cell

 

// Style of – how the pattern cell are to be placed where are drawn on a region bigger than the single pattern cell

 

// shouldColor –   a pattern can also be used as a mask, where the color is applied during the actual context where the pattern is drawn. So, literally you have to say true, if the pattern cell can chose its color, or no if the color at its main context is to be chosen

 

// callback – 2 callbacks encapsulated in a struct CGPatterCallbacks

 

_patterns[patternCount] = CGPatternCreate(&patters_types[patternCount], CGRectMake(0.0, 0.0, PATTERN_CELL_WIDTH, PATTERN_CELL_HEIGHT), CGAffineTransformIdentity, PATTERN_CELL_WIDTH, PATTERN_CELL_HEIGHT, kCGPatternTilingConstantSpacing, true, &callBack);

 

 

STEP 2:  (Important) define the drawPattern callback as below. This is the place where you are allowed to draw a single cell of a pattern. The drawing technique of pattern cell is same as one would draw in View’s drawRect method or drawing with any CGBitmapContext.

 

Pattern 1 – Chequered flag –

Chequered Flag Pattern

//Chequered Flag

//Fill the entire background with white color

CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);

CGContextFillRect(context, CGRectMake(0.0, 0.0, cellWidth, cellHeight));

 

CGContextSetFillColorWithColor(context, [UIColor blackColor].CGColor);

//Fill the top left quarter of the cell with black

CGContextFillRect(context, CGRectMake(0.0, 0.0, cellWidth/2.0, cellHeight/2.0));

 

//Fill the bottom right quarter of the cell with black

CGContextFillRect(context, CGRectMake(cellWidth/2.0, cellHeight/2.0, cellWidth/2.0, cellHeight/2.0));

 

Pattern 2

pattern_2

//http://www.kitchenbeforeandafter.com/wp-content/uploads/2009/05/kitchen-backsplash-tile-patterns2-300×216.gif

//       /

//     /

//  _ /

//   |_|

//  /  

// /    

///      

 

 

//stroke middle square

CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);

CGContextFillRect(context, CGRectMake(0.0, 0.0, cellWidth, cellHeight));

 

 

//stroke diagonal

CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);

CGPoint points [] = {{0.0,0.0}, {cellWidth,cellHeight }, {cellWidth,0.0}, {0.0,cellHeight}};

CGContextStrokeLineSegments(context, points, 4);

 

CGContextSetFillColorWithColor(context, [UIColor blackColor].CGColor);

int num_of_parts = 8;

float partWitdth = cellWidth / num_of_parts;

CGRect middleSpot = CGRectMake(partWitdth * 3, partWitdth*3, 2* partWitdth, 2*partWitdth);

 

CGContextFillRect(context, middleSpot);


Pattern 3

Circular_pattern

The Elements involve in this pattern are a circle drawn with various transformations

  • Circle
  • Semi-Circle
    • Circle shifted towards left by half the width of the cell
    • Circle shifted towards right by half the width of the cell
    • Circle shifted towards top by half the height of the cell
    • Circle shifted towards bottom by half the height of the cell
  • Arc
    • Circle shifted towards left & top by half the width and half the height of the cell respectively
    • Circle shifted towards left & bottom by half the width and half the height of the cell respectively
    • Circle shifted towards right & top by half the width and half the height of the cell respectively
    • Circle shifted towards right & bottom by half the width and half the height of the cell respectively

You can clone a copy of this Sample project from GitHub

STEP 3: Applying the Pattern.

Finally we would be applying the pattern in drawRect method of BBQuartzView in this sample.

// Drawing code

float alpha = 1;

 

//choose the pattern to be filled based on the currentPattern selected

CGContextRef context = UIGraphicsGetCurrentContext();

CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern (NULL);// 6

CGContextSetFillColorSpace (context, patternSpace);// 7

CGColorSpaceRelease (patternSpace);

 

//set the pattern as the Current Context’s fill pattern

 

//we dont need to set any color, as the pattern cell itself has chosen its own color

CGContextSetFillPattern(context, _patterns[_currentPattern],   &alpha);


//We would be filling the entire portion of this view

CGContextFillRect(context, [self bounds]);

  • Choose the pattern that needs to be applied. In this sample, the pattern is chosen based on the _currentPattern variable value (0- Chequered pattern, 1- Pattern2, 3- Pattern3)
  • Patterns are created at the time of initializing the view. In sample, it is in initWithCoder method of BBQuartzView
  • The drawPattern callback is invoked only when the pattern is all set to be drawn. In my case, it is invoked when CGContextFillRect in the above drawing code snippet. You can use this pattern to fill any shape. The selected pattern would also be reflected while using CGContext drawing API like, CGContextFill variants, CGContextFillPath variants
  • The drawPattern Callback is called only once in its lifetime and for subsequent drawing, the pattern cell buffer would be re-used. So resize the single pattern cell, the patterns are re-created

Here is are the pics how the patterns would look like(captured from sample code).

patterns.png

 

You can clone a copy of this Sample project from GitHub

Hope, you find this piece of info useful. Fill in your comments or queries in below comment’s section. Waiting for you comments.

Happy Coding 🙂