How to create an Objective-C category

1 Comment

What

Categories are objective-c’s way of allowing you to add methods to an existing class without having to touch it’s source.

For example, say we wished had a method on NSString that decorated our string with some pretty output.

- (NSString *) decorate
{
    return [NSString stringWithFormat:@"=== %@ ===", self];;
}

And we wished we could call it directly on NSString like this:

NSLog(@"%@", @"Booya".decorate);

Outputs:

=== Booya ===

Categories allow us to do that. Here’s how.

How

The format of a category is the ClassName you are extending, followed by the name you want to give your category (i.e. “Utils”).

#import "ClassName.h"

@interface ClassName ( CategoryName )
// method declarations
@end

To make a new category in Xcode we basically create a new class. Go:

New File (Command + N).
Select ‘Objective-C category’.

Specify the class you want to add a category onto (i.e. NSString).

Then add your category method to your .h file.

#import <Foundation/Foundation.h>

@interface NSString (Utils)
- (NSString *) decorate;
@end

And your implementation to you .m file.

#import "NSString+Utils.h"

@implementation NSString (Utils)
- (NSString *) decorate
{
    return [NSString stringWithFormat:@"=== %@ ===", self];;
}
@end

Now you can go to the class where you want to use the extension, import your category header and make your method call.

#import "NSString+Utils.h"

- (IBAction)tapMePressed:(id)sender
{
    NSLog(@"%@", @"Booya".decorate);
}

That’s it!

Links that help

Apple documentation on category

How to UIWebView iOS

2 Comments

Here’s a simple walk through of how to display a web page in iOS.

Drag out a WebView onto your ViewController.

Create a property.

ViewController.h

@interface ViewController : UIViewController 
@property (weak, nonatomic) IBOutlet UIWebView *webView;
@end

Implement the UIWebViewDelegate

ViewController.h

@interface ViewController : UIViewController <UIWebViewDelegate>

Fix the autogenerated synthesize:

ViewController.m

@synthesize webView = _webView;

Make yourself the delegate and display your web page.

ViewController.m

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    self.webView.delegate = self;
        
    NSURL *url = [NSURL URLWithString:@"http://google.com"];
    NSURLRequest *requestURL = [NSURLRequest requestWithURL:url];
    [self.webView loadRequest:requestURL];
}

Hit Run.

Can optionally add the following delegate methods if you want to kick it up a notch.

ViewController.m

#pragma mark - Optional UIWebViewDelegate delegate methods
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    return YES;
}

- (void)webViewDidStartLoad:(UIWebView *)webView
{
    [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
}

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}

- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}

Here’s the source.

ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController <UIWebViewDelegate>
@property (weak, nonatomic) IBOutlet UIWebView *webView;
@end

ViewController.m

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

@synthesize webView = _webView;

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    self.webView.delegate = self;
    
    NSURL *url = [NSURL URLWithString:@"http://google.com"];
    NSURLRequest *requestURL = [NSURLRequest requestWithURL:url];
    [self.webView loadRequest:requestURL];
}

- (void)viewDidUnload
{
    [self setWebView:nil];
    [super viewDidUnload];
    // Release any retained subviews of the main view.
}

#pragma mark - Optional UIWebViewDelegate delegate methods
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    return YES;
}

- (void)webViewDidStartLoad:(UIWebView *)webView
{
    [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
}

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}

- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}

@end

Tips and tricks for working with Frank iOS acceptance testing framework

4 Comments

Learned a lot this week from former colleague Pete Hodgson on Frank (thank you).

Along the way I’ve picked up a few learning that make working with Frank easier.

How to programmatically add a accessibility label to a UIElement

Do this so Frank can easily find your element. Then add this line to your cellForRowAtIndexPath after your cell is created.

    cell.accessibilityLabel = [NSString stringWithFormat:@"CustomCell%d", indexPath.row]; // FOR FRANK!

How to select a customer UIElement

    touch( "view:'CustomTableCell' marked:'CustomTableCell0'" )

That’s all I’ve got for now. More later.

Links that help

http://testingwithfrank.com/supplied_steps.html

Frank Google Group

Getting started with Frank

1 Comment

Peter Hodgson has an excellent write up on how to get started with Frank.

http://blog.thepete.net/blog/2012/06/24/writing-your-first-frank-test/

He also has a short screen cast here to help get you going.

http://blog.thepete.net/blog/2012/06/16/lowering-franks-barrier-to-entry/

I am new to Frank. But by following these intros I was able to get the Olympic test app up and running. So start there, and things will probably work.

If however you do run into complications, see if any of these workarounds work for you.

“Error: No developer directory found at /Developer”?

If you get this error, run this command from the command line:

sudo xcode-select -switch /Applications/Xcode.app/Contents/Developer

Unable to activate rspec-2.9.0, … conflicts with rspec-expectations (Gem::LoadError)

You get this error if you’be been doing some rails development and you have a different version of rspec setup in your rvm.

You can resolve this by creating a new gemset and then installing the latest rspec version as follows:

> rvm gemset list
> rvm gemset create frank
> rvm 1.9.3@frank
> gem install rspec

Note: you will have to substitute 1.9.3 with whatever version of ruby you are currently on:

> ruby -v

Make your app accessible

Follow these instructions here:

Can read about how to do that here.

Error loading /Library/ScriptingAdditions/Adobe Unit Types.osax

Follow Solution 1 on this page.

http://helpx.adobe.com/photoshop/kb/unit-type-conversion-error-applescript.html

More info

No architectures to compile for (ARCHS=armv6 armv7, VALID_ARCHS=i386)

I got this error when we made a lot of changes to our project file, and it wouldn’t work out of the box with the nice >frank build command :(

To work around this I had to manually frankify my app, and then run a build script as shown below:

1. Manually frankifying your app

Follow these instructions:

https://github.com/moredip/Frank/wiki/Frankifying-your-app-in-XCode-4

2. Add and run this build script.

Follow this instructions:

How to add a run build script to Xcode project

To run this build script:

echo APP_BUNDLE_PATH=\"$BUILT_PRODUCTS_DIR/$EXECUTABLE_NAME.app\" > $SRCROOT/Frank/features/support/bundle_path.rb

If everything works you should see your app on Symbiote here:

http://localhost:37265/

And you should be able to run the sample cucumber test from the command line like this:

cucumber Frank/features/my_first.feature

Discussion on Frank google group

Symbiote

Working Effectively With Xcode

1 Comment

Here are some notes from WWDC’s Session 402 – Working Effectively with Xcode video (signin required).

Behaviors

Behaviors let you setup some nice editor defaults that make debugging and dealing with build errors less annoying in Xcode.

You can get to Behaviors via Xcode preferences (Command ,) -> Behaviors and changing the actions on the right, for various triggers on the left.

Here are some good simple ones to get you going.

Build generates new issues

Clicking on the ‘Build generates new issues’ trigger and selecting ‘Show tab named’ Build will open a new tab if a build error occurs and put the output there. This is nice because it doesn’t mess up your current edit file and force you to step back once you fix the error.

Run pauses

This one is handy for when you hit a break point, and you want to do your debugging in a separate tab called debug.

Console window

Say you want to bring up a console window anytime you run your application. In Behaviors select the ‘Run Starts’ trigger and select ‘Show a tab named’ and enter ‘Console’.

Define your own edit code behavior

By clicking the ‘+’ sign and assigning a shortcut key, you can define your own edit code behavior and hit a shortcut key every time you want Xcode to setup your current window for code editting.

Simply hit the ‘+’ sign

Double click the command key icon on that line

And assign a short cut (i.e. F10).

Then setup your behaviors (in this case I like to hide all the panels and menus I’m not interested in).

Emacs gestures

Xcode supports a lot of emacs key bindings:

Control A/E Beginning and end of line
Control N New line
Control B Backward
Control F Forward

Command Control E – inscope variable name change

Open Quickly options

Shift Command O opens a file quickly in Xcode.
To open new file in assistant editor go: Option Return

To open somewhere else tab go: Option Shift Return
and use the arrows keys to select open location.

How to multi-line UILabel iOS revisited

Leave a comment

I wrote an earlier post about multi-line labels but I wanted something more distilled. Here are some condensed notes on how to do multi-line lables with xibs.

1. Set label to multi-line.

2. Set your UILabel struts and springs.

3. Calculate label height.

+ (CGFloat)heightForLabelWithString:(NSString *)pString andWidth:(CGFloat)pWidth
{
    CGSize labelSize = [pString sizeWithFont:[UIFont fontWithName:@"HelveticaNeue" size:14.5f] constrainedToSize:CGSizeMake(pWidth, MAXFLOAT) lineBreakMode:UILineBreakModeWordWrap];
    return labelSize.height;   
}

4. Redraw label based on height.

CGFloat bodyHeight = [CustomCell heightForLabelWithString:text.body andWidth:290];
    self.bodyLabel.frame = CGRectMake(self.bodyLabel.frame.origin.x, self.bodyLabel.frame.origin.y, self.bodyLabel.frame.size.width, bodyHeight);
    
    self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, self.frame.size.width, self.bodyLabel.frame.origin.y + self.bodyLabel.frame.size.height + 10); 

Bring it all together in code

CustomCell.h

#import <UIKit/UIKit.h>
#import "NSString.h"

@interface CustomCell : UITableViewCell

@property (strong, nonatomic) IBOutlet UILabel *bodyLabel;

+ (NSString *)reuseIdentifier;
- (void)populate:(NSString *)text;
+ (CGFloat)heightForText:(NSString *)text;
@end

CustomCell.m

#import "CustomCell.h"
#import "UITableViewCell+Utils.h"

#define kYOrigin 10

@implementation CustomCell
@synthesize bodyLabel = _bodyLabel;

- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        // Initialization code
    }
    return self;
}

- (void)populate:(NSString *)text
{
    self.bodyLabel.text = text;

    CGFloat bodyHeight = [CustomCell heightForLabelWithString:text andWidth:290];
    self.bodyLabel.frame = CGRectMake(self.bodyLabel.frame.origin.x, self.bodyLabel.frame.origin.y, self.bodyLabel.frame.size.width, bodyHeight);
    
    self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, self.frame.size.width, self.bodyLabel.frame.origin.y + self.bodyLabel.frame.size.height + 10); //10 for padding
}

+ (CGFloat)heightForText:(NSString *)text
{
    CGFloat bodyHeight = [self heightForLabelWithString:text andWidth:290];
    return kYOrigin + bodyHeight + 10;
}

+ (NSString *)reuseIdentifier 
{    
    return @"CustomCellIdentifier";
}

@end

MyViewController.m

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
        return [CustomCell heightForText:text];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    
CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:[CustomCell reuseIdentifier]];
    if (cell == nil) {
        [[NSBundle mainBundle] loadNibNamed:@"CustomCell" owner:self options:nil];
        cell = _textSectionCell;
        _textSectionCell = nil;
    }        
    
    [cell populate:@”some really long multi-line body of text ...”];
    return cell; 
}

UITableViewCell+Utils.h (category)

#import "UITableViewCell+Utils.h"

@implementation UITableViewCell (Utils)

+ (CGFloat)heightForLabelWithString:(NSString *)pString 
{
    CGSize labelSize = [pString sizeWithFont:[UIFont fontWithName:@"HelveticaNeue" size:14.5f] constrainedToSize:CGSizeMake(kDefaultLabelWidth, MAXFLOAT) lineBreakMode:UILineBreakModeWordWrap];
    return labelSize.height;
}

+ (CGFloat)heightForLabelWithString:(NSString *)pString andWidth:(CGFloat)pWidth
{
    CGSize labelSize = [pString sizeWithFont:[UIFont fontWithName:@"HelveticaNeue" size:14.5f] constrainedToSize:CGSizeMake(pWidth, MAXFLOAT) lineBreakMode:UILineBreakModeWordWrap];
    return labelSize.height;   
}
@end

How to kill Xcode from the command line

5 Comments

#!/bin/bash
echo "Killing xcode..."
kill $(ps aux | grep 'Xcode' | awk '{print $2}')

How to convert NSDate to different time zones

Leave a comment

Here’s a NSDate category to convert UTC (or GMT) to local time and back.

#import "NSDate+Utils.h"

@implementation NSDate (Utils)

-(NSDate *) toLocalTime
{
    NSTimeZone *tz = [NSTimeZone localTimeZone];
    NSInteger seconds = [tz secondsFromGMTForDate: self];
    return [NSDate dateWithTimeInterval: seconds sinceDate: self];
}

-(NSDate *) toGlobalTime
{
    NSTimeZone *tz = [NSTimeZone localTimeZone];
    NSInteger seconds = -[tz secondsFromGMTForDate: self];
    return [NSDate dateWithTimeInterval: seconds sinceDate: self];
}

@end

Links that help
http://stackoverflow.com/questions/1268509/convert-utc-nsdate-to-local-timezone-objective-c
http://stackoverflow.com/questions/5985468/iphone-differences-among-time-zone-convenience-methods

How to shrink a UILabel down to it’s actual size

Leave a comment

Say you have a label that’s three digits long, with a fixed with of something big (like 200pts) and you want to know it’s actual width.

Here’s how you do it:

ViewController.m

- (void)viewWillAppear:(BOOL)animated
{
    // get label size
    CGSize size = self.myLabel.frame.size;
    CGFloat width = size.width;
    
    NSLog(@"\n\n=======================");
    NSLog(@"original width=%f", width);
    
    // shrink it down to it's actual size
    CGSize textSize = [self.myLabel.text sizeWithFont:[self.myLabel font]];
    
    CGFloat strikeWidth = textSize.width;
    NSLog(@"new width=%f", strikeWidth);
    
}

Gives the output:

original width=200.000000
new width=35.000000

Links that help
http://stackoverflow.com/questions/1340667/pixel-width-of-the-text-in-a-uilabel

How to EGOTableViewPullRefresh

3 Comments

Here are some quick hacked notes for how to get EGOTableViewPullRefresh going.

Download
https://github.com/enormego/EGOTableViewPullRefresh

Drag source into project

Do the Arc non-compile thing on EGORefreshTableHeaderView.

Create a tableView controller give it some data

Add the following to your TableViewController header:

#import <UIKit/UIKit.h>
#import "EGORefreshTableHeaderView.h"

@interface PDTRViewController : UITableViewController <EGORefreshTableHeaderDelegate, UITableViewDelegate, UITableViewDataSource> {
    
    EGORefreshTableHeaderView *_refreshHeaderView;
	
	//  Reloading var should really be your tableviews datasource
	//  Putting it here for demo purposes 
	BOOL _reloading;
}

- (void)reloadTableViewDataSource;
- (void)doneLoadingTableViewData;

@property (strong, nonatomic) NSMutableArray *data;
@end

Add the following to the implementation


#import "PDTRViewController.h"


@implementation PDTRViewController

@synthesize data = _data;


- (NSMutableArray *)data 
{
    if (_data == nil) {
        _data = [NSMutableArray array];                
        for(int n = 1; n <= 100; n = n + 1) {
            NSNumber *num = [NSNumber numberWithInt:n];
            [_data addObject:num];
        }
    }
    return _data;
}

- (id)initWithStyle:(UITableViewStyle)style
{
    self = [super initWithStyle:style];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    

    if (_refreshHeaderView == nil) {
		
		EGORefreshTableHeaderView *view = [[EGORefreshTableHeaderView alloc] initWithFrame:CGRectMake(0.0f, 0.0f - self.tableView.bounds.size.height, self.view.frame.size.width, self.tableView.bounds.size.height)];
		view.delegate = self;
		[self.tableView addSubview:view];
		_refreshHeaderView = view;		
	}
	
	//  update the last update date
	[_refreshHeaderView refreshLastUpdatedDate];

    
}

- (void)viewDidUnload
{
    [super viewDidUnload];

}



#pragma mark - Table view data source

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return self.data.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    
    // Configure the cell...
    
    return cell;
}




#pragma mark -
#pragma mark Data Source Loading / Reloading Methods

- (void)reloadTableViewDataSource{
	
	//  should be calling your tableviews data source model to reload
	//  put here just for demo
	_reloading = YES;
	
}

- (void)doneLoadingTableViewData{
	
	//  model should call this when its done loading
	_reloading = NO;
	[_refreshHeaderView egoRefreshScrollViewDataSourceDidFinishedLoading:self.tableView];
	
}

#pragma mark -
#pragma mark UIScrollViewDelegate Methods

- (void)scrollViewDidScroll:(UIScrollView *)scrollView{	
	
	[_refreshHeaderView egoRefreshScrollViewDidScroll:scrollView];
    
}

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
	
	[_refreshHeaderView egoRefreshScrollViewDidEndDragging:scrollView];
	
}


#pragma mark -
#pragma mark EGORefreshTableHeaderDelegate Methods

- (void)egoRefreshTableHeaderDidTriggerRefresh:(EGORefreshTableHeaderView*)view{
	
	[self reloadTableViewDataSource];
	[self performSelector:@selector(doneLoadingTableViewData) withObject:nil afterDelay:3.0];
	
}

- (BOOL)egoRefreshTableHeaderDataSourceIsLoading:(EGORefreshTableHeaderView*)view{
	
	return _reloading; // should return if data source model is reloading
	
}

- (NSDate*)egoRefreshTableHeaderDataSourceLastUpdated:(EGORefreshTableHeaderView*)view{
	
	return [NSDate date]; // should return date data source was last changed
	
}


@end

Run. Should see it now

Older Entries Newer Entries

Follow

Get every new post delivered to your Inbox.

Join 323 other followers

%d bloggers like this: