Showing posts with label iOS. Show all posts
Showing posts with label iOS. Show all posts

Monday, December 8, 2014

Setup What? Origins Of Four Step Unit Testing in iOS

When I’m at my desk during the week and have written new code, I always create a new unit test file, and it looks something like this:

I asked myself two questions:
  1. Why do I create a new testcase class for every new class?
  2. Why do all of the tests I have always contains four-steps?

History of XCTest and xUnit


XCTest is based off of OCUnit and falls under the family of xUnit testing frameworks. xUnit is the collective name for several unit testing frameworks that derive their structure and functionality from Smalltalk’s SUnit [1], notably JUnit, which was also authored by Kent Beck [2]. In March 1998, OCUnit was written by Sen:te using Kent Beck’s description of the Smalltalk testing framework [3]. Understanding the origins of XCTest and JUnit helped me discover that most of the testing habits I have today all derive from Kent Beck’s Simple Smalltalk Testing: With Patterns.

One Testcase Class

I recommend that developers write their own unit tests, one per class. The framework supports the writing of suites of tests, which can be attached to a class. I recommend that all classes respond to the message “testSuite”, returning a suite containing the unit tests.
— Kent Beck, Simple Smalltalk Testing: With Patterns

It was Kent Beck’s philosophy that led to the practice of creating one testcase class per class. Beck believed that test logic should be encoded as a single test method on some class [4], and a testcase class gives you a place to group these similarly related test methods [5]. Making test methods instance methods of a testcase class and creating a testcase object for each test, allows us to manipulate the test methods at runtime, such as running a single test case, a group of test cases, or skipping a particular test case [6].




Source: xUnitPatterns


Four-Phase Test




Source: xUnitPatterns

The idea of having a setup method, a test method, then a tearDown method come from Beck’s work, and has been formalized into what is called the Four-Phase Test. In the Four-Phase Test, every test has four distinct phases that are executed in sequence: setup, exercise SUT (system under test), result verification, and fixture teardown [6]. The purpose of creating clear distinctions in the phases was to make the system under test extremely obvious.

The inspiration for the design was based around the fact that automated tests should serve at least two purposes:

First, they should act as documentation of how the system under test (SUT) should behave; we call this Tests as Documentation. Second, they should be a self-verifying executable specification.
— Gerard Meszaros, xUnitPatterns

The Four-Phase Test can be thought of as a state machine. In the fixture setup phase, the test establishes the prior state of the world. In the second phase we interact with the SUT, which transitions us to the next state. In the third phase we analyze the post state of the world and verify that it meets our expectations. Then in the fourth phase, we reset the state of the world prior to running the test.

Conclusion

Unit testing may sometimes become a laborious process, so it’s refreshing to take a step back and understand why we do the things that we do. The inspiration of unit testing frameworks in iOS or OS X has been around since the first version of SUnit in 1994 [7], and given that unit testing hasn’t changed much since then, it seems that the paradigms of one testcase class per class and four-phase testing are here to stay.


Sources


[1] "XUnit." Wikipedia. January 12, 2014. Accessed December 08, 2014. http://en.wikipedia.org/wiki/XUnit.

[2] "JUnit." Wikipedia. January 12, 2014. Accessed December 08, 2014. http://en.wikipedia.org/wiki/JUnit.

[3] "IPhone Unit Testing | Sen:te." Sente RSS. Accessed December 08, 2014. http://www.sente.ch/?p=535&lang=en.

[4] "Test Method." At XUnitPatterns.com. Accessed December 08, 2014. http://xunitpatterns.com/Test%20Method.html.

[5] "Testcase Class." At XUnitPatterns.com. Accessed December 08, 2014. http://xunitpatterns.com/Testcase%20Class.html.

[6] "Four-Phase Test." Four Phase Test at XUnitPatterns.com. Accessed December 08, 2014. http://xunitpatterns.com/Four%20Phase%20Test.html.

[7] "Ten Years Of Test Driven Development." Ten Years Of Test Driven Development. Accessed December 08, 2014. http://c2.com/cgi/wiki?TenYearsOfTestDrivenDevelopment.

Wednesday, June 26, 2013

How To Make Fast Scrolling UITableViews With Custom Section Headers and Custom UITableViewCells




1. Create a new master detail application in XCode.

2. To set a custom flat navigation bar color create a two new objective-c categories with the code below and configure the navigation bar by importing the UINavigationBar+FlatUI.h file into your master view controller and calling configureFlatNavigationBarWithColor: on your navigation bar in the viewDidLoad method. You should end up with something like below.




// At the Top of the file
#import "UINavigationBar+FlatUI.h" 

- (void)viewDidLoad
{
    [super viewDidLoad];
 // Do any additional setup after loading the view, typically from a nib.
  self.navigationItem.leftBarButtonItem = self.editButtonItem;
  [self.navigationController.navigationBar configureFlatNavigationBarWithColor:[UIColor colorWithRed:44.0f/255.0f green:71.0f/255.0f blue:98.0f/255.0f alpha:1.0f]];
  UIBarButtonItem *addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(insertNewObject:)];
  self.navigationItem.rightBarButtonItem = addButton;
}

UINavigationBar+FlatUI.h


//
//  UINavigationBar+FlatUI.h
//  FlatUI
//
//  Created by Jack Flintermann on 5/3/13.
//  Copyright (c) 2013 Jack Flintermann. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface UINavigationBar (FlatUI)

- (void) configureFlatNavigationBarWithColor:(UIColor *)color; UI_APPEARANCE_SELECTOR

@end

UINavigationBar+FlatUI.m


//
//  UINavigationBar+FlatUI.m
//  FlatUI
//
//  Created by Jack Flintermann on 5/3/13.
//  Copyright (c) 2013 Jack Flintermann. All rights reserved.
//

#import "UINavigationBar+FlatUI.h"
#import "UIImage+FlatUI.h"

@implementation UINavigationBar (FlatUI)

- (void) configureFlatNavigationBarWithColor:(UIColor *)color {
    [self setBackgroundImage:[UIImage imageWithColor:color cornerRadius:0]
               forBarMetrics:UIBarMetricsDefault & UIBarMetricsLandscapePhone];
    NSMutableDictionary *titleTextAttributes = [[self titleTextAttributes] mutableCopy];
    if (!titleTextAttributes) {
        titleTextAttributes = [NSMutableDictionary dictionary];
    }
    [titleTextAttributes setValue:[UIColor clearColor] forKey:UITextAttributeTextShadowColor];
    [titleTextAttributes setValue:[NSValue valueWithUIOffset:UIOffsetMake(0, 0)] forKey:UITextAttributeTextShadowOffset];
    [self setTitleTextAttributes:titleTextAttributes];
    if([self respondsToSelector:@selector(setShadowImage:)])
    {
        [self setShadowImage:[UIImage imageWithColor:[UIColor clearColor] cornerRadius:0]];
    }
}

@end

UIImage+Color.h


//
//  UIImage+Color.h
//  FlatUI
//
//  Created by Jack Flintermann on 5/3/13.
//  Copyright (c) 2013 Jack Flintermann. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface UIImage (FlatUI)

+ (UIImage *)imageWithColor:(UIColor *)color
               cornerRadius:(CGFloat)cornerRadius;

+ (UIImage *) backButtonImageWithColor:(UIColor *)color
                            barMetrics:(UIBarMetrics) metrics
                          cornerRadius:(CGFloat)cornerRadius;

UIImage+Color.m


+ (UIImage *)imageWithColor:(UIColor *)color
               cornerRadius:(CGFloat)cornerRadius {
    CGFloat minEdgeSize = edgeSizeFromCornerRadius(cornerRadius);
    CGRect rect = CGRectMake(0, 0, minEdgeSize, minEdgeSize);
    UIBezierPath *roundedRect = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:cornerRadius];
    roundedRect.lineWidth = 0;
    UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0.0f);
    [color setFill];
    [roundedRect fill];
    [roundedRect stroke];
    [roundedRect addClip];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return [image resizableImageWithCapInsets:UIEdgeInsetsMake(cornerRadius, cornerRadius, cornerRadius, cornerRadius)];
}

+ (UIImage *) backButtonImageWithColor:(UIColor *)color
                            barMetrics:(UIBarMetrics) metrics
                          cornerRadius:(CGFloat)cornerRadius {

    CGSize size;
    if (metrics == UIBarMetricsDefault) {
        size = CGSizeMake(50, 30);
    }
    else {
        size = CGSizeMake(60, 23);
    }
    UIBezierPath *path = [self bezierPathForBackButtonInRect:CGRectMake(0, 0, size.width, size.height) cornerRadius:cornerRadius];
    UIGraphicsBeginImageContextWithOptions(size, NO, 0.0f);
    [color setFill];
    [path addClip];
    [path fill];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return [image resizableImageWithCapInsets:UIEdgeInsetsMake(cornerRadius, 15, cornerRadius, cornerRadius)];
    
}

3. Now to make the navigation items look flat we can create another objective-c category to change the appearance. Import UIBarButtonItem+FlatUI.h in your master view controller Then add the code below to the viewDidLoad method. You should see something like this:



- (void)viewDidLoad
{
    [super viewDidLoad];
 // Do any additional setup after loading the view, typically from a nib.
  self.navigationItem.leftBarButtonItem = self.editButtonItem;
  [self.navigationController.navigationBar configureFlatNavigationBarWithColor:[UIColor colorWithRed:44.0f/255.0f green:71.0f/255.0f blue:98.0f/255.0f alpha:1.0f]];
  UIBarButtonItem *addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(insertNewObject:)];
  self.navigationItem.rightBarButtonItem = addButton;
  [UIBarButtonItem configureFlatButtonsWithColor:[UIColor colorWithRed:52.0f/255.0f green:152.0f/255.0f blue:219.0f/255.0f alpha:1.0f]
                                highlightedColor:[UIColor colorWithRed:41.0f/255 green:128.0f/255 blue:185.0f/255 alpha:1.0f]
                                    cornerRadius:3];
}

UIBarButtonItem+FlatUI.h


//
//  UIBarButtonItem+FlatUI.h
//  FlatUI
//
//  Created by Jack Flintermann on 5/8/13.
//  Copyright (c) 2013 Jack Flintermann. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface UIBarButtonItem (FlatUI)

// styles a single bar button item
- (void) configureFlatButtonWithColor:(UIColor *)color
                     highlightedColor:(UIColor *)highlightedColor
                         cornerRadius:(CGFloat) cornerRadius UI_APPEARANCE_SELECTOR;

// styles all bar button items that exist within a class heirarchy (same as UIAppearanceProxy methods)
+ (void) configureFlatButtonsWithColor:(UIColor *) color
                      highlightedColor:(UIColor *)highlightedColor
                          cornerRadius:(CGFloat) cornerRadius
                       whenContainedIn:(Class )containerClass, ... NS_REQUIRES_NIL_TERMINATION;

// styles all bar button items (can be overwritten with the above methods)
+ (void) configureFlatButtonsWithColor:(UIColor *) color
                      highlightedColor:(UIColor *)highlightedColor
                          cornerRadius:(CGFloat) cornerRadius;


// removes the text shadows off a single bar button item (sadly, this can't be easily done for all buttons simultaneously)
- (void) removeTitleShadow;

@end

UIBarButtonItem+FlatUI.m


//
//  UIBarButtonItem+FlatUI.m
//  FlatUI
//
//  Created by Jack Flintermann on 5/8/13.
//  Copyright (c) 2013 Jack Flintermann. All rights reserved.
//

#import "UIBarButtonItem+FlatUI.h"
#import "UIImage+FlatUI.h"

@implementation UIBarButtonItem (FlatUI)

- (void) configureFlatButtonWithColor:(UIColor *)color
                     highlightedColor:(UIColor *)highlightedColor
                         cornerRadius:(CGFloat) cornerRadius {
  
  [UIBarButtonItem configureItemOrProxy:self forFlatButtonWithColor:color highlightedColor:highlightedColor cornerRadius:cornerRadius];
  
}

+ (void) configureFlatButtonsWithColor:(UIColor *) color
                      highlightedColor:(UIColor *)highlightedColor
                          cornerRadius:(CGFloat) cornerRadius {
  
  [self configureFlatButtonsWithColor:color highlightedColor:highlightedColor cornerRadius:cornerRadius whenContainedIn:[UINavigationBar class], [UINavigationController class], nil];
}

+ (void) configureFlatButtonsWithColor:(UIColor *) color
                      highlightedColor:(UIColor *)highlightedColor
                          cornerRadius:(CGFloat) cornerRadius
                       whenContainedIn:(Class )containerClass, ... {
  va_list vl;
  va_start(vl, containerClass);
  id appearance = [UIBarButtonItem appearanceWhenContainedIn:containerClass, nil];
  va_end(vl);
  [UIBarButtonItem configureItemOrProxy:appearance forFlatButtonWithColor:color highlightedColor:highlightedColor cornerRadius:cornerRadius];
}

- (void) removeTitleShadow {
  NSMutableDictionary *titleTextAttributes = [[self titleTextAttributesForState:UIControlStateNormal] mutableCopy];
  if (!titleTextAttributes) {
    titleTextAttributes = [NSMutableDictionary dictionary];
  }
  [titleTextAttributes setValue:[NSValue valueWithUIOffset:UIOffsetMake(0, 0)] forKey:UITextAttributeTextShadowOffset];
  [self setTitleTextAttributes:titleTextAttributes forState:UIControlStateNormal];
}

//helper method, basically a wrapper to allow creating a custom UIAppearance method that doesn't conform to the usual naming style
+ (void) configureItemOrProxy:(id)appearance
       forFlatButtonWithColor:(UIColor *)color
             highlightedColor:(UIColor *)highlightedColor
                 cornerRadius:(CGFloat) cornerRadius {
  UIImage *backButtonPortraitImage = [UIImage backButtonImageWithColor:color
                                                            barMetrics:UIBarMetricsDefault
                                                          cornerRadius:cornerRadius];
  UIImage *highlightedBackButtonPortraitImage = [UIImage backButtonImageWithColor:highlightedColor
                                                                       barMetrics:UIBarMetricsDefault
                                                                     cornerRadius:cornerRadius];
  UIImage *backButtonLandscapeImage = [UIImage backButtonImageWithColor:color
                                                             barMetrics:UIBarMetricsLandscapePhone
                                                           cornerRadius:2];
  UIImage *highlightedBackButtonLandscapeImage = [UIImage backButtonImageWithColor:highlightedColor
                                                                        barMetrics:UIBarMetricsLandscapePhone
                                                                      cornerRadius:2];
  
  [appearance setBackButtonBackgroundImage:backButtonPortraitImage
                                  forState:UIControlStateNormal
                                barMetrics:UIBarMetricsDefault];
  [appearance setBackButtonBackgroundImage:backButtonLandscapeImage
                                  forState:UIControlStateNormal
                                barMetrics:UIBarMetricsLandscapePhone];
  [appearance setBackButtonBackgroundImage:highlightedBackButtonPortraitImage
                                  forState:UIControlStateHighlighted
                                barMetrics:UIBarMetricsDefault];
  [appearance setBackButtonBackgroundImage:highlightedBackButtonLandscapeImage
                                  forState:UIControlStateHighlighted
                                barMetrics:UIBarMetricsLandscapePhone];
  
  [appearance setBackButtonTitlePositionAdjustment:UIOffsetMake(1.0f, 1.0f) forBarMetrics:UIBarMetricsDefault];
  [appearance setBackButtonTitlePositionAdjustment:UIOffsetMake(1.0f, 1.0f) forBarMetrics:UIBarMetricsLandscapePhone];
  
  UIImage *buttonImageNormal       = [UIImage imageWithColor:color cornerRadius:cornerRadius];
  UIImage *buttonImageHightlighted = [UIImage imageWithColor:highlightedColor cornerRadius:cornerRadius];
  [appearance setBackgroundImage:buttonImageNormal forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
  [appearance setBackgroundImage:buttonImageHightlighted forState:UIControlStateHighlighted barMetrics:UIBarMetricsDefault];
}

@end
4. To create custom section header views in your tableview create a new section header view class and add a new category and add the code below to your master view controller. Also add the QuartzCore.Framework to your project. You should end up with something like this.








- (void)viewDidLoad
{
    [super viewDidLoad];
 // Do any additional setup after loading the view, typically from a nib.
  self.navigationItem.leftBarButtonItem = self.editButtonItem;
  [self.navigationController.navigationBar configureFlatNavigationBarWithColor:[UIColor colorWithRed:44.0f/255.0f green:71.0f/255.0f blue:98.0f/255.0f alpha:1.0f]];
  UIBarButtonItem *addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(insertNewObject:)];
  self.navigationItem.rightBarButtonItem = addButton;
  [UIBarButtonItem configureFlatButtonsWithColor:[UIColor colorWithRed:52.0f/255.0f green:152.0f/255.0f blue:219.0f/255.0f alpha:1.0f]
                                highlightedColor:[UIColor colorWithRed:41.0f/255 green:128.0f/255 blue:185.0f/255 alpha:1.0f]
                                    cornerRadius:3];
  // Add This Line
  self.tableView.sectionHeaderHeight = 46.0f;
}

#pragma mark - Table View

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{ // Change This to Non Zero Number
  return 100;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{ // Change This to Non Zero Number
  return 1;
}

// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    }


//  NSDate *object = _objects[indexPath.row];
  cell.textLabel.text = @"";
    return cell;
}

static NSString *SectionHeaderIdentifier = @"SectionHeaderIdentifier";

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
  SectionHeaderView *headerView = [self.tableView dequeueReusableHeaderFooterViewWithIdentifier:SectionHeaderIdentifier];
  if (headerView == nil) {
    headerView = [[SectionHeaderView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(tableView.frame), tableView.sectionHeaderHeight)];
  }
  [headerView setTitle:@"Title" subTitle:@"Subtitle" image:[UIImage imageNamed:@"search-icon-timeline-translucent@2x.png"]];
  return headerView;
}

UIColor+LightAndDark.h


//
//  UIColor+LightAndDark.h
//  Lua
//
//  Created by Kurry Tran on 12/24/12.
//  Copyright (c) 2012 Lua Technologies. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface UIColor (LightAndDark)
- (UIColor *)lighterColor:(CGFloat)percent;
- (UIColor *)darkerColor:(CGFloat)percent;
@end

UIColor+LightAndDark.m


//
//  UIColor+LightAndDark.m
//  Lua
//
//  Created by Kurry Tran on 12/24/12.
//  Copyright (c) 2012 Lua Technologies. All rights reserved.
//

#import "UIColor+LightAndDark.h"

@implementation UIColor (LightAndDark)
//http://stackoverflow.com/questions/11598043/get-slightly-lighter-and-darker-color-from-uicolor

- (UIColor *)lighterColor:(CGFloat)percent
{
  float h, s, b, a;
  if ([self getHue:&h saturation:&s brightness:&b alpha:&a])
    return [UIColor colorWithHue:h
                      saturation:s
                      brightness:MIN(b * (1+percent), 1.0)
                           alpha:a];
  return nil;
}

- (UIColor *)darkerColor:(CGFloat)percent
{
  float h, s, b, a;
  if ([self getHue:&h saturation:&s brightness:&b alpha:&a])
    return [UIColor colorWithHue:h
                      saturation:s
                      brightness:b * (1-percent)
                           alpha:a];
  return nil;
}
@end

SectionHeaderView.h


//
//  SectionHeaderView.h
//  FastScrolling-UITableViews
//
//  Created by Kurry L Tran on 6/26/13.
//  Copyright (c) 2013 Kurry L Tran. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface SectionHeaderView : UIView
{
  NSString *_title;
  NSString *_subTitle;
  UIImage *_image;
}
- (void)setTitle:(NSString *)title subTitle:(NSString *)subTitle;
- (void)setTitle:(NSString *)title subTitle:(NSString *)subTitle image:(UIImage *)image;

SectionHeaderView.m


//
//  SectionHeaderView.m
//  FastScrolling-UITableViews
//
//  Created by Kurry L Tran on 6/26/13.
//  Copyright (c) 2013 Kurry L Tran. All rights reserved.
//

#import "SectionHeaderView.h"
#import "UIColor+LightAndDark.h"
#import <QuartzCore/QuartzCore.h>

@implementation SectionHeaderView

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
    }
    return self;
}

- (void)setTitle:(NSString *)title subTitle:(NSString *)subTitle
{
  _title = title;
  _subTitle = subTitle;
  [self setNeedsDisplay];
}

- (void)setTitle:(NSString *)title subTitle:(NSString *)subTitle image:(UIImage *)image
{
  _title = title;
  _subTitle = subTitle;
  _image = image;
  [self setNeedsDisplay];

}


// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
    // Drawing code
  // This Creates a White A Black to White Gradient Layer to Blend With the Solid Color
  CAGradientLayer *gradientLayer = [CAGradientLayer layer];
  [gradientLayer setFrame:rect];
  NSArray *colors = [NSArray arrayWithObjects:
                     (id)[[UIColor whiteColor] CGColor],
                     (id)[[[UIColor blackColor] colorWithAlphaComponent:1.0f] CGColor],
                     nil];
  gradientLayer.colors = colors;
  gradientLayer.opacity = 0.09;
  [self.layer addSublayer:gradientLayer];
  
  // This Generates Three Random Numbers between [0,1] to seed random color
  float r = (float)rand()/(float)RAND_MAX;
  float g = (float)rand()/(float)RAND_MAX;
  float b = (float)rand()/(float)RAND_MAX;

  UIColor *baseColor = [UIColor colorWithRed:r green:g blue:b alpha:1.0f];
  UIColor *topBorderColor = [baseColor lighterColor:0.20];
  UIColor *bottomBorderColor = [baseColor darkerColor:0.20];
  
  // This Draws The Background Color
  CGContextRef context = UIGraphicsGetCurrentContext();
  CGContextSetFillColorWithColor(context, baseColor.CGColor);
  CGContextFillRect(context, CGRectMake(0, 0, self.frame.size.width, self.frame.size.height));
  CGContextSaveGState(context);

    // This Draws The Top Border Color
  CGContextMoveToPoint(context, CGRectGetMinX(rect), 0);
  CGContextAddLineToPoint(context, CGRectGetMaxX(rect), 0);
  CGContextSetStrokeColorWithColor(context, topBorderColor.CGColor);
  CGContextSetLineWidth(context, 1);
  CGContextStrokePath(context);
  CGContextRestoreGState(context);
  
  CGContextSaveGState(context);

  // This Draws The Bottom Border Color
  CGContextMoveToPoint(context, CGRectGetMinX(rect), CGRectGetMaxY(rect));
  CGContextAddLineToPoint(context, CGRectGetMaxX(rect), CGRectGetMaxY(rect));
  CGContextSetStrokeColorWithColor(context, bottomBorderColor.CGColor);
  CGContextSetLineWidth(context, 1);
  CGContextStrokePath(context);
  CGContextRestoreGState(context);
  
  CGRect titleLabelFrame = self.bounds;
  titleLabelFrame.origin.x = 69.0;
  titleLabelFrame.origin.y = 8.0;
  
  static UIColor *titleColor;
  titleColor = [UIColor whiteColor];
  static UIColor *subTitleColor;
  subTitleColor = [UIColor whiteColor];
  [titleColor set];
  CGContextSaveGState(context);
  context = UIGraphicsGetCurrentContext();
  CGContextSetBlendMode(context, kCGBlendModeNormal);
  
// Draws Title
  [_title drawAtPoint:CGPointMake(titleLabelFrame.origin.x, titleLabelFrame.origin.y)
            forWidth:200
            withFont:[UIFont boldSystemFontOfSize:17]
            fontSize:17
       lineBreakMode:NSLineBreakByTruncatingTail
  baselineAdjustment:UIBaselineAdjustmentAlignCenters];
  
// Draws SubTitle
  [subTitleColor set];
  [_subTitle drawAtPoint:CGPointMake(69, 25)
               forWidth:200
               withFont:[UIFont systemFontOfSize:13.0]
               fontSize:13
          lineBreakMode:NSLineBreakByTruncatingTail
     baselineAdjustment:UIBaselineAdjustmentAlignCenters];

  [_image drawInRect:CGRectMake(10.0, 10.0, 30.0, 28.0)];
}


@end


5. To custom draw fast tableview cells create a subclass of ABTableViewCell: https://github.com/enormego/ABTableViewCell, which draws the cell with core graphics. You should end up with something like this:
















ABTableViewCell.h


// Copyright (c) 2008 Loren Brichter
// 
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
//  ABTableViewCell.h
//
//  Created by Loren Brichter
//  Copyright 2008 Loren Brichter. All rights reserved.
//

#import <UIKit/UIKit.h>

// to use: subclass ABTableViewCell and implement -drawContentView:
@interface ABTableViewCell : UITableViewCell
{
 UIView *contentView;
}

- (void)drawContentView:(CGRect)r; // subclasses should implement

@end


ABTableViewCell.m


// Copyright (c) 2008 Loren Brichter
// 
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
//  ABTableViewCell.m
//
//  Created by Loren Brichter
//  Copyright 2008 Loren Brichter. All rights reserved.
// 

#import "ABTableViewCell.h"
@interface ABTableViewCellView : UIView
@end

@implementation ABTableViewCellView

- (void)drawRect:(CGRect)r
{
 [(ABTableViewCell *)[self superview] drawContentView:r];
}

@end



@implementation ABTableViewCell

- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
 if ((self = [super initWithStyle:style reuseIdentifier:reuseIdentifier])) {
  contentView = [[ABTableViewCellView alloc]initWithFrame:CGRectZero];
  contentView.opaque = YES;
  [self addSubview:contentView];
 }
 return self;
}


- (void)setFrame:(CGRect)f
{
 [super setFrame:f];
 CGRect b = [self bounds];
 //b.size.height -= 1; // leave room for the seperator line
 [contentView setFrame:b];
}

- (void)setNeedsDisplay
{
 [super setNeedsDisplay];
 [contentView setNeedsDisplay];
}

- (void)drawContentView:(CGRect)r
{
 // subclasses should implement this
}

@end


KTTableViewCell.h


//
//  KTTableViewCell.h
//  FastScrolling-UITableViews
//
//  Created by Kurry Tran on 6/27/13.
//  Copyright (c) 2013 Kurry L Tran. All rights reserved.
//

#import "ABTableViewCell.h"

@interface KTTableViewCell : ABTableViewCell
{
    NSString *_title;
    NSString *_subTitle;
    UIImage *_image;
}
- (void)setTitle:(NSString *)title subTitle:(NSString *)subtitle image:(UIImage *)image;
@end




//
//  KTTableViewCell.m
//  FastScrolling-UITableViews
//
//  Created by Kurry Tran on 6/27/13.
//  Copyright (c) 2013 Kurry L Tran. All rights reserved.
//

#import "KTTableViewCell.h"

@implementation KTTableViewCell

- (void)setTitle:(NSString *)title subTitle:(NSString *)subtitle image:(UIImage *)image
{
    _title = title;
    _subTitle = subtitle;
    _image = image;
    [self setNeedsDisplay];
}

-(void) drawContentView:(CGRect)r
{
    static UIColor *textColor;
    textColor = [UIColor blackColor];
    static UIColor *borderColor;
    borderColor = [UIColor colorWithRed:200.0f/255.0f green:200.0f/255.0f blue:200.0f/255.0f alpha:1.0f];
    static UIColor *selectedColor;
    selectedColor = [UIColor colorWithRed:212.0f/255.0f green:212.0f/255.0f blue:212.0f/255.0f alpha:1.0f];
    
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    if(self.highlighted || self.selected)
 {
        r.origin.y -= 1.0f;
        r.size.height += 1.0f;
        CGContextSetFillColorWithColor(context, selectedColor.CGColor);
  CGContextFillRect(context, CGRectMake(0, 0, CGRectGetWidth(r), CGRectGetMaxY(r)));
        CGContextSaveGState(context);
    }
 else
 {
  CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
  CGContextFillRect(context, CGRectMake(0, 0, r.size.width, r.size.height));
        CGContextSaveGState(context);
        
        CGContextMoveToPoint(context, CGRectGetMinX(r), 0);
        CGContextAddLineToPoint(context, CGRectGetMaxX(r), 0);
        CGContextSetStrokeColorWithColor(context, borderColor.CGColor);
        CGContextSetLineWidth(context, 1);
        CGContextStrokePath(context);
        CGContextRestoreGState(context);
        
        CGContextSaveGState(context);
        
        CGContextMoveToPoint(context, CGRectGetMinX(r), CGRectGetMaxY(r));
        CGContextAddLineToPoint(context, CGRectGetMaxX(r), CGRectGetMaxY(r));
        CGContextSetStrokeColorWithColor(context, borderColor.CGColor);
        CGContextSetLineWidth(context, 1);
        CGContextStrokePath(context);
        CGContextRestoreGState(context);
        [_image drawInRect:CGRectMake(5, 5, 45, 45)];
 }

    [textColor set];
    [_title drawAtPoint:CGPointMake(65.0f, 7.0f)
               forWidth:200
               withFont:[UIFont boldSystemFontOfSize:17]
               fontSize:17
          lineBreakMode:NSLineBreakByTruncatingTail
     baselineAdjustment:UIBaselineAdjustmentAlignCenters];
    
    [_subTitle drawAtPoint:CGPointMake(65.0f, 28.0f)
                  forWidth:200
                  withFont:[UIFont systemFontOfSize:16.0f]
                  fontSize:16
             lineBreakMode:NSLineBreakByTruncatingTail
        baselineAdjustment:UIBaselineAdjustmentAlignCenters];
 
}
@end


// Add This At The Top
#import "KTTableViewCell.h"

- (void)viewDidLoad
{
    [super viewDidLoad];
 // Do any additional setup after loading the view, typically from a nib.
  self.navigationItem.leftBarButtonItem = self.editButtonItem;
  [self.navigationController.navigationBar configureFlatNavigationBarWithColor:[UIColor colorWithRed:44.0f/255.0f green:71.0f/255.0f blue:98.0f/255.0f alpha:1.0f]];
  UIBarButtonItem *addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(insertNewObject:)];
  self.navigationItem.rightBarButtonItem = addButton;
  [UIBarButtonItem configureFlatButtonsWithColor:[UIColor colorWithRed:52.0f/255.0f green:152.0f/255.0f blue:219.0f/255.0f alpha:1.0f]
                                highlightedColor:[UIColor colorWithRed:41.0f/255 green:128.0f/255 blue:185.0f/255 alpha:1.0f]
                                    cornerRadius:3];
  
  self.tableView.sectionHeaderHeight = 46.0f;
// Add This Line 
    self.tableView.rowHeight = 56.0f;
}

// Change this to your new subclass
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    
    KTTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[KTTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    }
    [cell setTitle:@"Title" subTitle:@"Subtitle" image:[UIImage imageNamed:@"placeholder@2x.png"]];

//  NSDate *object = _objects[indexPath.row];
  cell.textLabel.text = @"";
    return cell;
}


Full Source Code Here: http://db.tt/7E2EBbln




Monday, June 24, 2013

Hidden Gems in Cocoa Touch / XCode

1. Open Quickly (Command-Shift-O)
2. View Related Files From Toolbar
3. Use breakpoint actions such as log message or play a sound to avoid recompiling.
4. Use debugDescription.
5. Use Objective-C Subscripting.
NSMutableArray *indexedValues = [NSMutableArray array];
indexedValues[0] = @"One";
NSLog(@"value: %@", indexedValues[0]);
NSMutableDictionary *keyedValues = [NSMutableDictionary dictionary];
keyedValues[@"color"] = [UIColor blueColor];
NSLog(@"value: %@", keyedValues[@"color"]);
6. Private declarations not necessary since Xcode 4.3
7. Synthesize not necessary for @property since Xcode 4.4
8. Reverse arrays quickly inline

 NSArray *numbers = @[ @1, @2, @3 ];
 NSArray *reversed = numbers.reverseObjectEnumerator.allObjects;
9.  Guarantee a mutable object

 NSArray *unknown = self.values; // may be nil
 NSMutableArray *newArray = [NSMutableArray arrayWithArray:unknown];
10. Declare and enumerate different collection types

 id collection = values;
 for (id object in collection) {
... }
11. Remove duplicate values in array without NSSet

[array valueForKeyPath:@"@distinctUnionOfObjects.self"]
12. CAGradientLayer

CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = CGRectMake(150, 250, 500, 500);
UIColor *c1 = [UIColor colorWithRed:0.09 green:0.70 blue:0.98 alpha:1.0];
UIColor *c2 = [UIColor colorWithRed:0.07 green:0.41 blue:0.95 alpha:1.0];
UIColor *c3 = [UIColor colorWithRed:0.81 green:0.46 blue:0.93 alpha:1.0];
gradient.colors = @[(id)c2.CGColor, (id)c3.CGColor, (id)c3.CGColor];
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"colors"];
anim.toValue = @[(id)c1.CGColor, (id)c2.CGColor, (id)c2.CGColor];
anim.duration = 4.0;
anim.autoreverses = YES;
anim.repeatCount = 1e100;
[gradient addAnimation:anim forKey:@"colors"];
[self.view.layer addSublayer:gradient];

13. Core Data Private Queue

NSManagedObjectContext *bgContext;
bgContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:
                                    NSPrivateQueueConcurrencyType];
[context performBlock:^{
    // (add, remove, change objects.)
    saveCompleted = [context save:& saveError];
}];

14. Core Data
Really fast fetches
• Only specific properties

 NSFetchRequest
 fetch.propertiesToFetch = @[@"name", @"phone"];
• Only the raw values

fetch.resultType = NSDictionaryResultType 
• Only the object id

 fetch.resultType = NSManagedObjectIDResultType
• Only the count

 fetch.resultType = NSCountResultType

WWDC 2013 Core Data Performance - Optimization and Debugging

These are the things that I thought were most useful to me:

1. Don't fetch more than you need. Only 10 or so rows are visible so set a batch size on your NSFetchRequests.

Example:


NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Contact"];

request.fetchBatchSize = 20;


2. Optimize your data model. Put binary data in separate entities and use external storage. Duplication isn't always a bad thing, if it speeds up your data query.

3. Prefetch relationships if you know you need them.

4. Consider returning dictionaries instead of managed objects.

Example:


NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Quake"];

[request setResultType:NSDictionaryResultType];

[request setPropertiesToFetch:@[@"magnitude"]];


5. Use SQLite to perform your calculations.

Example:


NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Quake"];
NSExpressionDescription *ed = [[NSExpressionDescription alloc] init];
 ed.name = @"minimum";
 ed.expression = [NSExpression expressionForFunction:@"min:"
            arguments:@[ [NSExpression expressionForKeyPath:@"magnitude"]]];
[request setPropertiesToFetch:@[ ed ]];


6. Use SQLite to group your results automatically.

Example:


NSExpressionDescription *ed = [[NSExpressionDescription alloc] init];
 ed.name = @"count";
 ed.expression = [NSExpression expressionForFunction:@"count:"
arguments:@[ [NSExpression expressionForKeyPath:@"magnitude"]]];
 [request setPropertiesToFetch:@[ @"magnitude", ed ]];
 [request setPropertiesToGroupBy:@[ @"magnitude" ]];
7. Use SQL Logging.

Pass argument flags = {1,2,3} on launch:


-com.apple.CoreData.SQLDebug 1


8. Optimize predicates. Text comparisons are expensive. Put numeric comparisons first.


NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Contact"];
Bad:


request.predicate = [NSPredicate predicateWithFormat:@"firstName == %@ AND age > %i", @"John", 40];
Good:

request.predicate = [NSPredicate predicateWithFormat:@"age > %i && firstName == %@", 40, @"John"];
9. Predicate Costs (Low to High):


Beginswith/Endswith < Equality < Contains < Matches



iOS 5 NSFetchedResultsController Sorting Issues With RestKit

We were having issues with NSManagedObjects not being sorted correctly after modifying managed objects locally and saving, but we didn't know why so I had to do some research. I found this post: http://wbyoung.tumblr.com/post/27851725562/core-data-growing-pains which was helpful, and the code snippet below was what fixed it. The issue in iOS 5 is that the NSFetchedResultsController deadlocks when there are changes in a child context. So with RestKit on that particular view controller we just had to switch from using the mainQueueManagedObjectContext to mainQueueManagedObjectContext.parentContext for iOS 5 and it resolved all of our issues.
#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v)  ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)


  NSManagedObjectContext *newManagedObjectContext;
  if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"6.0")) {
    newManagedObjectContext = [RKObjectManager sharedManager].managedObjectStore.mainQueueManagedObjectContext;
  } else{
    newManagedObjectContext = [RKObjectManager sharedManager].managedObjectStore.mainQueueManagedObjectContext.parentContext;
  }
  NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Entity"];
  NSFetchedResultsController *newFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:newManagedObjectContext sectionNameKeyPath:nil cacheName:nil];

Monday, April 29, 2013

Automated Unit Testing with Jenkins, Cocoapods and GHUnit



1. Install homebrew link.
Or paste the command in terminal:
$ruby -e "$(curl -fsSL https://raw.github.com/mxcl/homebrew/go)"

2. Install cocoapods http://cocoapods.org/.

$ sudo gem install cocoapods
$ pod setup

3. Add GHUnit to your podfile.

pod 'GHUnitIOS', '~> 0.5.6'

Common Error: When I first uploaded my repo to Jenkins, I got an error about Jenkins not being able to clone a submodule for Cocoapods, and if you're having this issue you have to make sure to delete anything related to Cocoapods in your .gitmodules file.

Common Error: We added KIF to our project as a cocoapods dependency, but your app will be rejected if you include it in the build you submit to the app store. The solution is to only include KIF for your test target that uses KIF and not your App Store release version of your app. This can be done in your cocoapods file.

Example:
For command line builds XCode defaults to release version (which you can change), and my KIF tests were in the target IntegrationTests.
target :release, :exclusive => true do
  link_with 'IntegrationTests'
 pod 'KIF', '~> 0.0.1'
end

KIF.podspec
Pod::Spec.new do |s|
  s.name         = "KIF"
  s.version      = "0.0.1"
  s.summary      = "KIF, which stands for Keep It Functional, 
is an iOS integration test framework."
  s.homepage     = "https://github.com/square/KIF"
  s.license  = 'MIT'
  s.author       = 'efirestone', 'jpsim'
  s.source       = { :git => "https://github.com/square/KIF.git",
 :commit => "ed057a038f07232e351210e56971fd9440acdd42" }
  s.platform     = :ios, '5.0'
  s.source_files = 'Classes','Additions',
  s.frameworks = 'UIKit', 'Foundation'
end

4. Install Jenkins with homebrew:

$ brew install jenkins

5. Add an alias to your .bashrc file and add an alias to start and stop Jenkins.

.bashrc
alias jenkins_start="launchctl load /usr/local/Cellar/jenkins/1.499/homebrew.mxcl.jenkins.plist"
alias jenkins_stop="launchctl unload /usr/local/Cellar/jenkins/1.499/homebrew.mxcl.jenkins.plist"
6. Start Jenkins server by typing the command in terminal:
$ jenkins_start
7. Use this Makefile:
default:
 # Set default make action here
 # xcodebuild -target Tests -configuration MyMainTarget -sdk macosx build 

clean:
 -rm -rf build/*

test:
 GHUNIT_CLI=1 WRITE_JUNIT_XML=1 /usr/bin/xcodebuild -target TARGET_NAME -configuration Debug -sdk iphonesimulator build -project PROJECT_FOLDER/PROJECT_NAME.xcodeproj 

8. In Jenkins, create a job and execute a shell script with the following command:
make test

9. Create an output job to publish the JUnit XML output. By default, the XML results are located in the PROJECT_NAME/build/test-results folder.




10. Save and apply settings.

11. In XCode, add a Run Script build phase and add these scripts in the folder that contains your Xcode project.
sh RunTests.sh

RunTests.sh
#!/bin/sh

# If we aren't running from the command line, then exit
if [ "$GHUNIT_CLI" = "" ] && [ "$GHUNIT_AUTORUN" = "" ]; then
  exit 0
fi

export DYLD_ROOT_PATH="$SDKROOT"
export DYLD_FRAMEWORK_PATH="$CONFIGURATION_BUILD_DIR"
export IPHONE_SIMULATOR_ROOT="$SDKROOT"
#export CFFIXED_USER_HOME="$TEMP_FILES_DIR/iPhone Simulator User Dir" # Be compatible with google-toolbox-for-mac

#if [ -d $"CFFIXED_USER_HOME" ]; then
#  rm -rf "$CFFIXED_USER_HOME"
#fi
#mkdir -p "$CFFIXED_USER_HOME"

export NSDebugEnabled=YES
export NSZombieEnabled=YES
export NSDeallocateZombies=NO
export NSHangOnUncaughtException=YES
export NSAutoreleaseFreedObjectCheckEnabled=YES

export DYLD_FRAMEWORK_PATH="$CONFIGURATION_BUILD_DIR"

TEST_TARGET_EXECUTABLE_PATH="$TARGET_BUILD_DIR/$EXECUTABLE_PATH"

if [ ! -e "$TEST_TARGET_EXECUTABLE_PATH" ]; then
  echo ""
  echo "  ------------------------------------------------------------------------"
  echo "  Missing executable path: "
  echo "     $TEST_TARGET_EXECUTABLE_PATH."
  echo "  The product may have failed to build or could have an old xcodebuild in your path (from 3.x instead of 4.x)."
  echo "  ------------------------------------------------------------------------"
  echo ""
  exit 1
fi

# If trapping fails, make sure we kill any running securityd
#launchctl list | grep GHUNIT_RunIPhoneSecurityd && launchctl remove GHUNIT_RunIPhoneSecurityd
#SCRIPTS_PATH=`cd $(dirname $0); pwd`
#launchctl submit -l GHUNIT_RunIPhoneSecurityd -- "$SCRIPTS_PATH"/RunIPhoneSecurityd.sh $IPHONE_SIMULATOR_ROOT #$CFFIXED_USER_HOME
#trap "launchctl remove GHUNIT_RunIPhoneSecurityd" EXIT TERM INT

RUN_CMD="\"$TEST_TARGET_EXECUTABLE_PATH\" -RegisterForSystemEvents"

echo "Running: $RUN_CMD"
set +o errexit # Disable exiting on error so script continues if tests fail
eval $RUN_CMD
RETVAL=$?
set -o errexit

unset DYLD_ROOT_PATH
unset DYLD_FRAMEWORK_PATH
unset IPHONE_SIMULATOR_ROOT

if [ -n "$WRITE_JUNIT_XML" ]; then
  MY_TMPDIR=`/usr/bin/getconf DARWIN_USER_TEMP_DIR`
  RESULTS_DIR="${MY_TMPDIR}test-results"

  if [ -d "$RESULTS_DIR" ]; then
 `$CP -r "$RESULTS_DIR" "$BUILD_DIR" && rm -r "$RESULTS_DIR"`
  fi
fi

exit $RETVAL

RunIPhoneSecurityd.sh
#!/bin/sh

set -e
set -u

export DYLD_ROOT_PATH="$1"
export IPHONE_SIMULATOR_ROOT="$1"
export CFFIXED_USER_HOME="$2"

"$IPHONE_SIMULATOR_ROOT"/usr/libexec/securityd

12. Add Cocoapods to the rest of your project see here: http://nscookbook.com/2013/03/recipe-18-unit-testing-with-ghunit-cocoapods/. Your project settings should look something like this:



13. In XCode 4.6.2 I had to change my AppDelegate.m file to this to get everything to the code below to get everything to run correctly.

AppDelegate.m
//
//  main.m
//  LuaUnitTests
//
//  Created by Kurry Tran on 10/15/12.
//  Copyright (c) 2012 Lua Technologies. All rights reserved.
//
#import 
#import 

int main(int argc, char *argv[])
{
  int retVal;
  @autoreleasepool {
    if (getenv("GHUNIT_CLI")) {
      retVal = [GHTestRunner run];
    } else {
      retVal = UIApplicationMain(argc, argv, nil, @"GHUnitIOSAppDelegate");
    }
  }
  return retVal;
}
Note: This blog article was very short, but I hope it was helpful.