How to save NSMutableArrays in NSUserDefaults

Leave a comment

Two things to keep in mind with saving an NSMutableArrays with NSUserDefaults:

1. All objects returned from NSUserDefaults are immutable.

2. All objects with the array need to be serializable (or property values).

The first one means if you saving something mutable, you have to reconstruct it when you read it back because it is going to be immutable:

 NSArray *temp = (NSArray *)[self loadUserDefaultWithKey:PHOTOS_KEY];
    self.photos = [NSMutableArray arrayWithArray:temp];

The second one means that whatever you are persisting in your array, it needs to contain property values, or implement some archiving methods so the elements know how to archive, and unarchive themselves when they comes out of storage.

I got caught with this trying to say an ALAsset into a mutable dictionary, and then read it back later. Doesn’t work well because of the second issue.

Here are the error messages you will get.

Terminating app due to uncaught exception ‘NSInternalInconsistencyException’, reason: ‘-[__NSCFArray insertObject:atIndex:]: mutating method sent to immutable object’

Note that dictionaries and arrays in property lists must also contain only property values.

More links on the subject

http://stackoverflow.com/questions/471830/why-nsuserdefaults-failed-to-save-nsmutabledictionary-in-iphone-sdk

http://stackoverflow.com/questions/4690290/how-to-store-nsmutablearray-in-nsuserdefaults

Learning iOS via the Robots And Pencils Academy

3 Comments

Hi all,

I am very excited to announce the launch of a new service me and my friend Michael have been working on.

It’s called The Robots and Pencils Academy and it’s basically screencasts for people who wanting to learn iOS.

As a recent student of iOS, I know how challenging it can be to get started.

New language.
New tools.
New technology.

It can be overwhelming.

This is the website I wish existed when I got started, and because we love sharing what we’ve learned with others, these screencasts are the result.

Getting started

If you are looking for a gentle introduction to Xcode or iOS development in general checkout:



If you just want to see what Objective-C looks like watch:



and a some others we recently created can be found here.

Training

We will also be offering two day bootcamps through the site. Next one is here in Calgary at the end of the month and another coming up in Winnipeg. You can read more about those courses here:

http://academy.robotsandpencils.com/training

Thank you for listening. Please share this with anyone you think may be interested.

Sincerely,

Jonathan & Michael

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

4 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

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

7 Comments

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

How to display UIActivityIndicatorView spinner for long running operations

13 Comments

Say you’ve got a long running process and you want to popup a spinner while your process is working. Here’s how you do it.

ViewController.h

#import <UIKit/UIKit.h>

@interface rcsViewController : UIViewController
@property (weak, nonatomic) IBOutlet UILabel *myLabel;
@property (weak, nonatomic) IBOutlet UIButton *myButton;
@end

ViewController.m

#import "rcsViewController.h"

@implementation rcsViewController
@synthesize myLabel;
@synthesize myButton;

- (IBAction)process:(id)sender {    
        
    // replace right bar button 'refresh' with spinner
    UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
    spinner.center = CGPointMake(160, 240);
    spinner.hidesWhenStopped = YES;
    [self.view addSubview:spinner];    
    [spinner startAnimating];
        
    // how we stop refresh from freezing the main UI thread
    dispatch_queue_t downloadQueue = dispatch_queue_create("downloader", NULL);
    dispatch_async(downloadQueue, ^{

        // do our long running process here
        [NSThread sleepForTimeInterval:10];
        
        // do any UI stuff on the main UI thread
        dispatch_async(dispatch_get_main_queue(), ^{
            self.myLabel.text = @"After!";
            [spinner stopAnimating];
        });
        
    });
    dispatch_release(downloadQueue);
}

- (void)viewDidLoad
{
    [super viewDidLoad];    
}

- (void)viewDidUnload
{
    [self setMyLabel:nil];
    [self setMyButton:nil];
    [super viewDidUnload];
}

@end

The magic happens when the users presses the button. Here we create our spinner, add it to our subview, and start it up.

    UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
    spinner.center = CGPointMake(160, 240);
    spinner.hidesWhenStopped = YES;
    [self.view addSubview:spinner];    
    [spinner startAnimating];

Once it’s going we create a separate thread to do our processing in, along with a call to the main UI thread when it’s done (which changes the label and stops the spinner).

 dispatch_queue_t downloadQueue = dispatch_queue_create("downloader", NULL);
    dispatch_async(downloadQueue, ^{

        // do our long running process here
        [NSThread sleepForTimeInterval:10];
        
        // do any UI stuff on the main UI thread
        dispatch_async(dispatch_get_main_queue(), ^{
            self.myLabel.text = @"After!";
            [spinner stopAnimating];
        });
        
    });

That’s it!

Mobile apps are more work than you think

14 Comments

Expectation management is a big part of software delivery. Unless you write software for a living, it’s hard to appreciate how much work goes into the little white box you see on the Google search screen, or the math and physics behind a game like Angry Birds.

Having just finished my first iPhone app, even I was surprise at the amount of work it took to take a few pictures and generate an email to send to a couple of friends.

Building on this post from Kent Nguyen, I thought it would be fun to share some of the behind the scenes magic that makes these simple apps work.

TakeMyStuff

The app itself is pretty simple. It’s called takemystuff (sorry not available in iStore yet) and it basically helps people give stuff away to friends.

Basically, you take pictures of stuff you’d like to give away.

Select the friends you’d like to tell.

And then it builds you an email combining the images and tagged with the friends you selected.

That’s it! Pretty simple eh? Here’s some of the stuff that’s going behind the scenes.

1. Taking pictures

To build a screen that takes pictures and then displays thumbnails in a table

you need to:

  1. Tag images as they are taken (to separate yours from those taken with the regular camera).
  2. Track which ones are selected/unselected, and
  3. Save that information something to be accessed later.

That’s the minium amount of work required to just make it work. Here are some of the wrinkles.

Image capturing is asynchronous

iOS saves images you take with the camera asynchronously. That means when you take a picture, it’s not instantly there for you to display. You have to wait.

Then when the worker thread is done, you are free to refresh the table on the screen.

Not a big deal, but it complicates things because you need to put refreshes in the appropriate places and it’s not always obvious where. It also requires a lot more manual testing (more on than later) and a fair bit of trial and error.

Table cells are pooled and reallocated dynamically

The engineers to Apple have gone through great lengths to make the iPhone fast. One way they do that is to dynamically allocate and reuse cells used for rows in tables.

For example you might have x100 photos ready for display. But when you bind them to a table, a much smaller number gets shown.

That means you need to create data structures outside of the table itself to track which photos are displayed and whether they are selected.

It’s not super hard but it’s non-trivial work.

And if you collect a lot of metadata for each picture you’ll either have to make use of something complex (like CoreData) or track and synchronize these data structures yourself (through things like Arrays and Dictionaries).

Locations services

Location services are what the iPhone uses to tell apps where they are. Foursquare, Weather, and Maps are obvious services that could use this kind information.

But cameras can use this information too. And while I didn’t require location services but your app might.

That means you need to check at runtime whether the service is turned on, and inform your users of what they should do if it isn’t.

Now let’s take a look at some of the things you need to deal with when compiling a list of friends.

2. Selecting friends

Selecting and adding friends on a simple table like this is a bit of work. You need to:

  • Pop open the address book and detect which friend was selected
  • Extract the friend data and handle all the edge cases of there not being a first or last name, or even an email address
  • Add logic to make sure choosing a friend on screen is synchronized with friends chosen in your data structure
  • Add logic to delete (right swipe) or remove a friend from your list (and again synchronize that with your data source)
  • Handle all the refreshes from your data structure changing to that on the screen

Then you need to popup and hide the appropriate alerts and error messages if your users have data you either didn’t expect or can’t handle.

Again, a fair bit of work for what looks like a dead simple screen.

Now let’s take a look at what it takes to bring it all together in an email.

3. Building the email

Here we grab the photos selected by the user from the first screen and combine them with the email addresses selected from the second. Here’s how that works:

  • First you need to walk the assets library and extract all the photos you tagged with your application (more asynchronous programming).
  • Then you need to grab and parse the email address of the friends.
  • Then you need to combine those and create the email.
  • If they choose not to send the email, you need to handle the ‘Cancel’ button being pressed and make sure you transition them back to their previous views (which you also need to track.

And then you need to handle the cases of where they didn’t select any photos or they didn’t select any friends.

Other stuff that needs to be done

Art work

This application doesn’t have a lot of of art but I still needed to create some icons for the tab bar and one for the application itself.

Deploying to the app store

This was (still is) probably the most painful part of the iOS development process. Trying to get your work on other peoples devices.

I won’t go into all the details but you need to jump through a lot of hoops just to test your app on another device.

  • You need to get their device ids.
  • You need to create certificates.
  • You need to create deployment profiles
  • You need to copy/email files around with complex instructions for how you testers are supposed to load you app.
  • And then after doing all that it still doesn’t work with little or no feedback as to why

It’s a tough, frustrating process it’s no wonder startups are forming just to help you deploy your iOS application.

If that’s not a wake up call to Apple that their deployment process is overly complicated I don’t know what is.

A lot more manual testing

With so much application code directed to the UI, mobile apps require a lot more manual labor intensive testing.

Yes there are frameworks and tools that can help but I have yet to see anyone favor these over repeatedly clicking through your app and making sure everything works.

And while you can unit test, it doesn’t deliver the payback we’ve grown accustomed to on server side frameworks like Rails and Java/.NET.

And we aren’t even doing anything complicated!

If the makers of Path and PlantsVsZombies were reading this they’d laugh. Complicated! You ain’t seen nothing yet! And they’d right.

This app doesn’t do any:

  • custom graphics
  • sound and audio
  • custom UI
  • push notification
  • in app purchases

All areas of hairiness and complexity and hairiness. Not to mention that there’s no backend to this app. It’s all local to the phone (which simplifies things greatly but limits what the app can do).

In summary

Writing great software isn’t cheap or easy. It takes a lot of hard work, dedication, and persistence.

And everything I’ve said here about mobile apps applies equally (though in different ways) to enterprise apps and backend systems (just imagine the complexity Google or the guys at DropBox regularly handle).

So the next time someone tells you that building that screen shouldn’t take more than a couple hours, smile, be patient, and understand where they are coming from. To our users much of what we do is magic. It looks easy and they expect it to work.

Explain to them some of the challenges. Be upfront and honest about the cost. And know that you are not alone. Managing expectations is a big part of being a software professional and setting expectations on software projects ain’t easy (though I know a book that can help).

Happy coding.

Test Automation (UIAutomation) example with XCode Instruments

14 Comments

UIAutomation is the library/tool Apple added in iOS SDK 4.0 to help with test automation on the iOS platform.

Scripts are written in JavaScript and executed through the Instruments Automation Instrument.

Here a quick walkthrough of how to record a script from your iPhone and play it back on your device.

Open Instruments

You open instruments through XCode -> Open Developer Tool -> Instruments.

Add Automation Instrument

There’s lots of instruments to choose from. The one we want is the ‘Automation’ instrument.

Add a script

Choose your target

Your target isn’t your device. It’s the device and your application.

Hit the record button

Record your script by hitting the ‘record’ button at the top or bottom of the tool and then click through your app.

Play it back

To see your script hit the funny looking Trace Log/Editor Log drop down and switch to scrip log view.

You should then see your recorded script.

You can play it back by clicking the ‘play’ button beside the record button at the bottom and you should see the same script played back against your phone. Output should look like this.

Save your script

This simple script doesn’t do too much, but it’s a start and if you want to save it and play it back later you can create a directory and story it in your app like this:

Much more

There’s a lot more we can do with UIAutomation. This is just a start and we’ve only scratched the surface.

But you can do a lot with this tool, and this is merely the first step.

Note: If your tests don’t run, make sure you aren’t connected and running your device from XCode. Can’t run both simultaneously so stop the XCode one then try running your tests again.

Links that help:

http://alexvollmer.com/posts/2010/07/03/working-with-uiautomation/

UIAutomation reference

Older Entries

%d bloggers like this: