Quantcast
Channel: Articles
Viewing all articles
Browse latest Browse all 18

Pull To Refresh

$
0
0

Since iOS 6 the Mail app has a Pull to Refresh indicator:

In this article I’ll show how to implement such an animation from scratch. This is for learning and practice purposes only, there is no need to implement this yourself. You can enable the Pull to Refresh behavior for any UITableViewController by setting a UIRefreshControl:

@implementationMyTableViewController{-(void)viewDidLoad{[superviewDidLoad];UIRefreshControl*refreshControl=[[UIRefreshControlalloc]init];[refreshControladdTarget:selfaction:@selector(refresh)forControlEvents:UIControlEventValueChanged];self.refreshControl=refreshControl;}-(void)refresh{NSLog(@"Refresh!");[self.refreshControlendRefreshing];}@end

For older iOS versions or UIViewControllers classes ODRefreshControl written by Fabio Ritrovato does the job.

Drawing the reload icon

First step is to draw the reload arrow in the icon. This is resized during the animation, so it needs to be drawn as path:

As starting point I drew the shape in Adobe Illustrator by deleting an arc from a circle, converting the path to a shape using Object > Path > Outline Stroke and merging it with a triangle using Pathfinder > Unite:

Now, how to take this to an iOS app?

Drawing a shape in an iOS app

The easiest way to draw an arbitrary shape is to subclass UIView, override drawRect and draw a path using UIBezierPath. Something like this:

#import "ShapeView.h"@implementationShapeView{UIBezierPath*_arc,*_arrow;}-(id)initWithFrame:(CGRect)frame{self=[superinitWithFrame:frame];if(self){CGPointcenter=CGPointMake(100,100);floatr=34;_arc=[UIBezierPathbezierPath];[_arcaddArcWithCenter:centerradius:rstartAngle:0endAngle:M_PI*1.5fclockwise:YES];_arc.lineWidth=14;_arrow=[UIBezierPathbezierPath];CGPointp=CGPointMake(center.x,center.y-r);[_arrowmoveToPoint:CGPointMake(p.x,p.y-20)];[_arrowaddLineToPoint:CGPointMake(p.x+32,p.y)];[_arrowaddLineToPoint:CGPointMake(p.x,p.y+20)];self.backgroundColor=UIColor.whiteColor;}returnself;}-(void)drawRect:(CGRect)rect{[[UIColorredColor]setStroke];[[UIColorredColor]setFill];[_arcstroke];[_arrowfill];}@end

Certainly possible, but not good. Manually writing drawing code isn’t fun even for such a simple shape. There has to be a better way! How about using a SVG graphic?

The path is encoded in the SVG in a compact format, so it should be possible to use this:

<pathfill="#ED1F24"d="M76.104,56.276c0,14.517-11.812,26.327-26.328,26.327c-14.518,0-26.329-11.811-26.329-26.327    c0-14.5,11.783-26.3,26.278-26.327V46.36l31.259-19.965L49.725,3.937v12.615c-21.882,0.027-39.675,17.837-39.675,39.725    C10.049,78.181,27.87,96,49.775,96C71.68,96,89.5,78.181,89.5,56.276H76.104z"/>

Creating a UIBezierPath from a SVG

Arthur Evstifeev wrote UIBezierPath-SVG: a parser to create a UIBezierPath from a SVG path. It doesn’t support ARC, so it is required to disable ARC for this single file. Otherwise it works perfectly and is a very nice category for UIBezierPath:

#import "ShapeView.h"#import "UIBezierPath+SVG.h"@implementationShapeView{UIBezierPath*_arrow;}-(id)initWithFrame:(CGRect)frame{self=[superinitWithFrame:frame];if(self){_arrow=[UIBezierPathbezierPathWithSVGString:@"M76.104,56.276c0,14.517-11.812,26.327-26.328,26.327c-14.518,0-26.329-11.811-26.329-26.327,c0-14.5,11.783-26.3,26.278-26.327V46.36l31.259-19.965L49.725,3.937v12.615c-21.882,0.027-39.675,17.837-39.675,39.725,C10.049,78.181,27.87,96,49.775,96C71.68,96,89.5,78.181,89.5,56.276H76.104z"];self.backgroundColor=[UIColorwhiteColor];}returnself;}-(void)drawRect:(CGRect)rect{[[UIColorredColor]setFill];[_arrowfill];}@end

Out of curiosity, I wondered if I could capture a video of the animation from the hardware device, as the simulator doesn’t feature the Mail app:

Recording the iPhone screen

Reflector app does the trick via AirPlay Mirroring, GIF Brewery converts a movie to an animated GIF:

Creating the drop shape

As starting point for the drop shape, I traced the shape in illustrator as well and placed it in a way so that the coordinates are convenient for later positioning.

For the colors, UIColorFromRGB.h (or alternatively Panic's Developer Color Picker) comes in handy to use hex color values in Objective C code:

Applying the same old bezierPathWithSVGString trick:

#import "ShapeView.h"#import "UIBezierPath+SVG.h"#import "UIColorFromRGB.h"@implementationShapeView{UIBezierPath*_arrow,*_drop;}-(id)initWithFrame:(CGRect)frame{self=[superinitWithFrame:frame];if(self){_arrow=[UIBezierPathbezierPathWithSVGString:@"M9.906,29.144c0,5.371-4.37,9.741-9.741,9.741c-5.372,0-9.742-4.37-9.742-9.741 c0-5.365,4.359-9.73,9.724-9.741v6.072l11.565-7.387L0.146,9.778v4.667c-8.097,0.01-14.681,6.6-14.681,14.698 c0,8.104,6.595,14.697,14.699,14.697c8.104,0,14.698-6.593,14.698-14.698H9.906z"];_drop=[UIBezierPathbezierPathWithSVGString:@"M0,0c-14.359,0-26,11.641-26,26c0,4.729,1.126,8.741,3,13c11,25,12,88,12,88 c0,6.076,4.924,11,11,11s11-4.924,11-11c0,0,1-63,12-88c1.874-4.259,3-8.271,3-13C26,11.641,14.359,0,0,0z"];self.backgroundColor=UIColorFromRGB(0xe2e7ed);}returnself;}-(void)drawRect:(CGRect)rect{CGContextRefctx=UIGraphicsGetCurrentContext();CGContextTranslateCTM(ctx,rect.size.width/2.f,10);[UIColorFromRGB(0x9ba2ab)setFill];[_dropfill];[[UIColorwhiteColor]setFill];[_arrowfill];}@end

Creating a unit test to validate the drawing code using reference images

With Gabriel Handford’sGHUnit being my favorite test framework for iOS, I decided to give its UI Testing Component written by John Boiles a try. After the setup procedure for GHUnit, views can be verified pixel-by-pixel in one single line of code by subclassing GHViewTestCase and calling GHVerifyView:

#import <GHUnitIOS/GHUnit.h>#import "ShapeView.h"@interfaceShapeViewTest : GHViewTestCase{}@end@implementationShapeViewTest-(void)testShapeView{GHVerifyView([[ShapeViewalloc]initWithFrame:CGRectMake(0,0,640,200)]);}@end

Especially the feature to approve changes with a single click comes in quite handy:

The reference images are stored in the Simulator Documents folder. There are scripts to copy the reference images from there to the project folder and vice versa. CopyTestImages.sh is supposed to be in the root project folder and copies the images into a TestImages project folder:

PrepareUITests.sh is supposed to be run from the Tests build phase to set up the images before running the actual tests:

So I used the Xcode Organizer’s Screenshot feature to capture some still frames at different view heights, cropped them using ImageMagick like this:

convert -crop 640x127+0+128 Screenshot.png ShapeViewTest-testShape-1-0-ShapeView.png

and copied them to the TestImages folder. Voila:

To be continued. Next time:

  • converting the SVG path to code that can be animated
  • find the right sizes for the arcs depending on the view height
  • parameterizing the shape drawing

Viewing all articles
Browse latest Browse all 18

Latest Images

Trending Articles





Latest Images