Super Easy Apps, Brew Coffee App, and Row the Aussie

What I'm working on:

1. Super Easy Apps

Learn how to make an iPhone app in one hour from a wholistic and approachable training on design, code, marketing, revenue, and customer service.


2. Brew Coffee App

Make amazing pour-over coffee at home using your iPhone and Apple Watch.

3. Row the Australian Shepherd (frisbee dog!)

Row an australian shepherd

Mocking a UIKit Delegate Protocol with Kiwi

I'm experimenting with mock objects in Kiwi and decided to try and mock the UIAlertViewDelegate. I ran into two issues. 1. Order is important when creating the mock delegate object. It needs to be setup with all the delegate method receive calls before you use the object associated with the delegate protocol.

2.  The delegate protocol methods need to be mocked in the order they are invoked, or you'll receive a test failure. This can be trial and error, if you don't know how the delegate protocol works.

a. For each mock delegate test, I just kept running until I was able to get the mock delegate calls passing.

b. I also discovered that the didDismissWithButtonIndex and  clickedButtonAtIndex were not invoked after using the dismissWithClickedButtonIndex method call.

[objc] #import "Kiwi.h" #import "PSMessages.h" #import "PSMessageConstants.h"

SPEC_BEGIN(PSMessagesTest)

describe(@"Create an alertview", ^{ __block PSMessages *messages = nil; beforeEach(^{ messages = [[PSMessages alloc] init]; });

NSDictionary *urlMessage = @{ @"type" : @"url", @"url" : @"http://www.PhotoTableApp.com", @"message" : @"Create a collage for the next holiday.", @"title" : @"Design Collages", @"buttons" : @[@"Yes", @"No"] };

context(@"with a url message", ^{ __block UIAlertView *alert = nil; beforeEach(^{ alert = [messages alertForMessage:urlMessage]; }); it(@"the cancel button is pressed", ^{ id delegateMock = [KWMock mockForProtocol:@protocol(UIAlertViewDelegate)]; alert.delegate = delegateMock;

int buttonIndex = alert.cancelButtonIndex;

[[[delegateMock shouldEventually] receive] alertViewShouldEnableFirstOtherButton:alert]; [[[delegateMock shouldEventually] receive] didPresentAlertView:alert]; [[[delegateMock shouldEventually] receive] willPresentAlertView:alert]; [[[delegateMock shouldEventually] receive] alertView:alert willDismissWithButtonIndex:buttonIndex]; [[[delegateMock shouldEventually] receive] alertView:alert willDismissWithButtonIndex:buttonIndex];

// Note: delegate methods not called for programatic UIAlertView dismiss [[[delegateMock shouldEventually] receive] alertView:alert didDismissWithButtonIndex:buttonIndex]; [[[delegateMock shouldEventually] receive] alertView:alert clickedButtonAtIndex:buttonIndex];

// Invoke methods after the delegateMock object is setup [alert show]; [alert dismissWithClickedButtonIndex:buttonIndex animated:NO];

});

it(@"the ok button is pressed", ^{ id delegateMock = [KWMock mockForProtocol:@protocol(UIAlertViewDelegate)]; alert.delegate = delegateMock;

int buttonIndex = alert.firstOtherButtonIndex;

[[[delegateMock shouldEventually] receive] alertViewShouldEnableFirstOtherButton:alert]; [[[delegateMock shouldEventually] receive] didPresentAlertView:alert]; [[[delegateMock shouldEventually] receive] willPresentAlertView:alert]; [[[delegateMock shouldEventually] receive] alertView:alert willDismissWithButtonIndex:buttonIndex]; [[[delegateMock shouldEventually] receive] alertView:alert willDismissWithButtonIndex:buttonIndex];

// Invoke methods after the delegateMock object is setup [alert show]; [alert dismissWithClickedButtonIndex:buttonIndex animated:NO];

}); });

}); SPEC_END [/objc]

Unit Testing Static Libraries with Kiwi for iOS Development

I've been playing with Kiwi and I'm trying some BDD (Behavior Driven Development) for a new static library component I wanted to build. I began with a new Xcode project using the Static Library template, but ran into issues with the difference between "logic tests" and "application tests". In short, all my non-UIKit code worked great, until I started to test my UIKit related functions.

The code crashed and makes it frustrating to write unit tests. If you've never experienced it before, it'll make your unit test experience unproductive. To solve the issue you'll need to create a new target (empty iOS application) and include unit tests. Xcode will automagically setup the unit tests to be "Application Tests" instead of "logic tests."

EmptyApp

#0 0x00a40881 in __HALT () #1 0x0097a971 in _CFRuntimeCreateInstance () #2 0x01337cc1 in GSFontCreateWithName () #3 0x05c32281 in UINewFont () #4 0x05c323ec in +[UIFont systemFontOfSize:traits:] () #5 0x05c32438 in +[UIFont systemFontOfSize:] () #6 0x05be24ee in +[UILabel defaultFont] () #7 0x05be32e5 in -[UILabel _commonInit] () #8 0x05be3424 in -[UILabel initWithFrame:] () #9 0x05e7cc67 in -[UIAlertView(Private) _createTitleLabelIfNeeded] () #10 0x05e8b4b9 in -[UIAlertView setTitle:] () #11 0x05e8bb37 in -[UIAlertView initWithTitle:message:delegate:cancelButtonTitle:otherButtonTitles:] () #12 0x02605831 in __block_global_23 at /Users/paulsolt/dev/Photo-Slide-Show/PSMessages/PSMessagesTests/PSMessagesTest.m:165 #13 0x0261b584 in __25-[KWExample visitItNode:]_block_invoke_0 at /Users/paulsolt/dev/Photo-Slide-Show/Frameworks/Kiwi/Kiwi/KWExample.m:220 #14 0x0261a11e in __42-[KWContextNode performExample:withBlock:]_block_invoke_0 at /Users/paulsolt/dev/Photo-Slide-Show/Frameworks/Kiwi/Kiwi/KWContextNode.m:116 #15 0x0261a11e in __42-[KWContextNode performExample:withBlock:]_block_invoke_0 at /Users/paulsolt/dev/Photo-Slide-Show/Frameworks/Kiwi/Kiwi/KWContextNode.m:116 #16 0x0261a11e in __42-[KWContextNode performExample:withBlock:]_block_invoke_0 at /Users/paulsolt/dev/Photo-Slide-Show/Frameworks/Kiwi/Kiwi/KWContextNode.m:116 #17 0x0261a03e in -[KWContextNode performExample:withBlock:] at /Users/paulsolt/dev/Photo-Slide-Show/Frameworks/Kiwi/Kiwi/KWContextNode.m:132 #18 0x0261a05d in -[KWContextNode performExample:withBlock:] at /Users/paulsolt/dev/Photo-Slide-Show/Frameworks/Kiwi/Kiwi/KWContextNode.m:135 #19 0x0261a05d in -[KWContextNode performExample:withBlock:] at /Users/paulsolt/dev/Photo-Slide-Show/Frameworks/Kiwi/Kiwi/KWContextNode.m:135 #20 0x0261b539 in -[KWExample visitItNode:] at /Users/paulsolt/dev/Photo-Slide-Show/Frameworks/Kiwi/Kiwi/KWExample.m:216 #21 0x0261a553 in -[KWItNode acceptExampleNodeVisitor:] at /Users/paulsolt/dev/Photo-Slide-Show/Frameworks/Kiwi/Kiwi/KWItNode.m:41 #22 0x0261ae22 in -[KWExample runWithDelegate:] at /Users/paulsolt/dev/Photo-Slide-Show/Frameworks/Kiwi/Kiwi/KWExample.m:113 #23 0x02618c95 in -[KWSpec invokeTest] at /Users/paulsolt/dev/Photo-Slide-Show/Frameworks/Kiwi/Kiwi/KWSpec.m:105 #24 0x2010405b in -[SenTestCase performTest:] () #25 0x201037bf in -[SenTest run] () #26 0x2010792b in -[SenTestSuite performTest:] () #27 0x201037bf in -[SenTest run] () #28 0x2010792b in -[SenTestSuite performTest:] () #29 0x201037bf in -[SenTest run] () #30 0x201063ec in +[SenTestProbe runTests:] () #31 0x0072f5c8 in +[NSObject performSelector:withObject:] () #32 0x00002342 in ___lldb_unnamed_function11$$otest () #33 0x000025ef in ___lldb_unnamed_function13$$otest () #34 0x0000268c in ___lldb_unnamed_function14$$otest () #35 0x00002001 in ___lldb_unnamed_function4$$otest () #36 0x00001f71 in ___lldb_unnamed_function1$$otest ()

Solution

1. Add a new target with it's own unit tests. Creating unit tests with a "static library" template gives you "logic tests", while creating unit tests with a "iPhone application" gives you "application tests." The difference is that you can't use UIKit classes in logic tests, but you can in application tests.

EmptyApp

2. In an application test, the runloop of the iPhone app starts, which means all the UIKit goodies are setup. The bad news is that it loads all the default state from your previous app runs. You might need to write some methods to cleanup or reset state. (GHUnit is nice because it's more sandboxed)

3. If you're using Kiwi you'll have to setup the Kiwi environment (library/header paths) again for the application tests.

ApplicationUnitTest

UICollectionView Custom Actions and UIMenuController

The UICollectionView can provide a special UIMenuController with cut, copy, and paste actions. To add UICollectionView custom actions you need to implement a few extra methods for the shared UIMenuController object. The view controller's parent window needs to be the key window and you'll need to respond to UIResponder method canBecomeFirstResponder. In your UICollectionViewController class do the following:

[objc] // ViewController.h @interface ViewController : UICollectionViewController

// ViewController.m -(void)viewDidLoad { [super viewDidLoad]; self.collectionView.delegate = self;

UIMenuItem *menuItem = [[UIMenuItem alloc] initWithTitle:@"Custom Action" action:@selector(customAction:)]; [[UIMenuController sharedMenuController] setMenuItems:[NSArray arrayWithObject:menuItem]];

}

#pragma mark - UICollectionViewDelegate methods - (BOOL)collectionView:(UICollectionView *)collectionView canPerformAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender { return YES; // YES for the Cut, copy, paste actions }

- (BOOL)collectionView:(UICollectionView *)collectionView shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath { return YES; }

- (void)collectionView:(UICollectionView *)collectionView performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender { NSLog(@"performAction"); }

#pragma mark - UIMenuController required methods - (BOOL)canBecomeFirstResponder { // NOTE: This menu item will not show if this is not YES! return YES; }

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender { NSLog(@"canPerformAction"); // The selector(s) should match your UIMenuItem selector if (action == @selector(customAction:)) { return YES; } return NO; }

#pragma mark - Custom Action(s) - (void)customAction:(id)sender { NSLog(@"custom action! %@", sender); } [/objc]

Here's what it looks like:

UIMenuController Custom UICollectionView

Linking to a Facebook Page from an iOS App

The Facebook app broke the old way of creating an iOS facebook page link. If the app isn't installed the old facebook link works, but when it is installed it just opens the Facebook app to the default page. Fix your app links to Facebook using this url format:

Old iOS Facebook Page Link:

http://www.facebook.com/PhotoTableApp

New iOS Facebook Page Link:

https://m.facebook.com/PhotoTableApp?_rdr

The only downside is that the user would have to login via the mobile website in order to "like" your Facebook page. It's certainly better than just seeing the "default" screen in the app. I'm not sure if the new Facebook SDK 3.1 fixes any of these issues, but I haven't seen a solution on Stackoverflow.com

 

iPhone Link to Facebook Page fails with App

Linking to a facebook page doesn't work from iOS if the new Facebook app is installed.

iPhone Link to Facebook Page Works

Linking to the mobile Facebook page with the ?_rdr flag fixes the issue.

Duplicate Code Signing on Xcode with Any iOS SDK

Working with Xcode code signing is a pain. You may have encountered a situation where there are more than two code signing identity's in Xcode. This means that you potentially have to change four different code signing settings to update your app, rather than just two. I think this is cruft from upgrading to the newest Xcode version or something. Here's what it looks like and here's how to fix it. It's very annoying when developing and testing with adhoc and app store builds.

 

Code signing in Xcode 4

 

The issue is visible with the "Any iOS SDK" seen as a subitem to the Debug and the Release builds. Things won't work correctly if the two don't match in my experience. So I end up having to change two at a time.

Solution

You can close Xcode or keep it open. It seems to work either way before you start the next steps.

1.Open your project folder in finder. (Right click project in Xcode and choose "Show in Finder")

Show in finder

2. Right click on the Xcode project file in Finder -> "Show Package Contents"

Xcode project Show package contents

3. Right click on the project.pbxproj and choose "Open With" -> "TextEdit"

Xcode project in Textedit

4. Search for "Sign" and delete any lines that look like the following. Xcode will regenerate, so don't worry about deleting them. I believe the first two are the root of the problem.

"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";

"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";

CODE_SIGN_IDENTITY = "iPhone Developer";

Textedit code sign identity

5. Open the project settings in Xcode and see the duplicate "Any iOS SDK" are removed and Xcode regenerated the default Code Sign Identities.

Fixed code sign duplicates

6. Update with your credentials for Debug/Release.

Update code sign

Enjoy.

Leap Motion - Control your Mouse on Mac OSX

I have a development kit for Leap Motion that arrived last week and I've been working on an Objective-C/C++ wrapper for the C++ Leap Motion SDK. I used opaque pointers to contain the Objective-C/C++ code into one file so that the header can be included in vanilla Objective-C apps. Now that I have a starting point I'm working with the data from Leap Motion. My first goal is to make a functional mouse counterpart that can be used "hands free." Here's a video of my of basic mouse control. I use the position and direction of the finger to cast a ray into the monitor (ray tracing anyone?). Using the intersection point and a lot of math you can start to translate the raw leap data into something meaningful for an application.

http://www.youtube.com/watch?v=SaoKy8bTgT8

Going further I'm investigating the best way to recognize a tap gesture and what that will look like in practice.

Using the social.framework on iOS 6.0

Using the social.framework is real simple on iOS 6.0. Apple only refers to their reference, so I decided to show the code snippet example. To change between twitter and  Sina Weibo, just use the types: SLServiceTypeSinaWeibo SLServiceTypeTwitter. It just takes 10 lines of code. If you used the Twitter.framework, you can remove it and replace your Twitter code with the following code. The Social.framework will manage all social networks moving forward.

if([SLComposeViewController isAvailableForServiceType:SLServiceTypeFacebook]) {

SLComposeViewController *socialSheet = [SLComposeViewController composeViewControllerForServiceType:SLServiceTypeFacebook];

[socialSheet setInitialText:@"posted from @PhotoTable"];

[socialSheet addImage:image];

[socialSheet setCompletionHandler:^(SLComposeViewControllerResult result) {

NSLog(@"Result: %d", result);

}];

[self presentViewController:socialSheet animated:YES completion:^ {

}];

}

Making NSApplicationDelegate and NSDocument Play Together in OSX 10.8

I found it difficult to setup a NSApplicationDelegate for a Document-based Mac 10.8 (Mountain Lion) application (NSDocument) using ARC.

There's a lot of handy app specific methods in the delegate protocol that you'll most likely want in a new Mac app.  However, the project template (Xcode 4.4) does not include the ApplicationDelegate class. This missing puzzle piece meant I had to figure out how to configure it correctly with Xcode's Interface Builder.

Problem with NSApplicationDelegate

I tried adding an Object as my ApplicationDelegate in my PSDocument.xib file. However, that led to random crashing (EXC_BAD_ACCESS objc_msgSend_vtable5) when I closed windows from my app.

1. The reason it crashed is that the document window was deallocated, and the PSApplicationDelegate object was attached to it. This caused the NSApplication to invoke methods on a previously deallocated object. (i.e. PSApplicationDelegate)

2. Zombies to the rescue. See the screenshot on how to enable zombies if you're new to Xcode. Using zombies I was able to figure out a general idea of what happened.

Zombie Objects

Solution

Add the PSApplicationDelegate object to your MainMenu.xib file. It'll persist until you exit your app, rather than your document windows.

1. Drag the Object (blue-box) from the Object Library to your side panel in Xcode's Interface Builder.

2. Create the NSObject class called PSApplicationDelegate.h/m

3. Conform to the NSApplicationDelegate protocol

#import <Cocoa/Cocoa.h>
 
@interface PSApplicationDelegate : NSObject<NSApplicationDelegate>
 
@end

4. Rename the Object (blue-box) to PSApplicationDelegate

Custom Object in Xcode 4 4 Interface Builder

5. Control-click or right-click and drag from the File's Owner (NSApplication) to the new PSApplicationDelegate object (blue-box)

6. Select delegate.

NSApplicationDelegate Example for Document-based Mac OSX apps

 

Now you're golden and use any of the protocol methods from NSApplicationDelegate.

#import "PSApplicationDelegate.h"
 
@implementation PSApplicationDelegate
 
// Delegate methods
- (void)applicationDidFinishLaunching:(NSNotification *)notification {
    NSLog(@"Started app");
}
 
 
@end

SkillShare iPhone Development 101 Classes in September

I'm teaching two sessions of my online iPhone Development 101 class in September. The classes are taught using GoToMeeting.com and cover basic programming in C and Objective-C. At the end you'll learn how to create a simple iPhone app.

Class Dates

Coupon

Limited $10 discount: SAVE10

Topics Covered

  • How to setup your Mac computer with Xcode
  • Basic Objective-C and C programming
  • How to use Interface Builder to design user interfaces
  • How to Run your first app on an iPhone
  • Reinforce learning with a homework assignment

Prerequisites

Xcode 4.4+, Mountain Lion, and a Macbook Pro, Macbook Air, iMac, or Mac Pro

Online Class Streaming

  • Go To Meeting (Mac, iPad, PC)
  • Live whiteboard, Xcode Coding, and Q&A
  • Watch it again after the class

App Rochester - iPhone Programming Presentation

In followup from my iPhone programming presentation I have uploaded the presentation and sample Xcode project (Xcode 4.3.2) The sample converts between temperatures in celsius and fahrenheit. The sample shows how to setup the gestures in Xcode's interface builder, rather than in Objective-C code. Using the UIPanGestureRecognizer we can allow sliding input for the temperature conversion. The only code we need to implement is the action to take in response to a detected gesture.

Download the slides and sample code:

 

iPhone Unit Testing Explained - Part II

Xcode 4 has drastically improved iPhone unit testing and Mac unit testing from my previous post, iPhone Unit Testing Explained - Part I Creating the unit testing target is easy and you can start writing test code in under 5 minutes.

The biggest hassle in testing is setting up the project correctly, and Xcode 4 makes it simple. If you read Part I, I pushed for GHUnit because of the GUI interface, but now Xcode's built in testing is enough to get you started. If you need a GUI, add GHUnit later, but start writing your tests today, since they're compatible with GHUnit when you decide to integrate with it.

Testing is important to start from the beginning or you will never have the motivation to write the tests unless your boss demand

Getting Started

To start writing unit tests you have two options, either create a new project with unit tests or add unit tests to an existing project.

New Project with Unit Tests

Create a new project and make sure the checkbox is enabled for unit tests.

 

New Xcode 4 Project with Unit Tests

Add Unit Tests Target to Existing Projects

Add a unit test target to your project by clicking on your Project (top left) -> Add Target (bottom middle) -> iOS -> Other -> Unit Testing Bundle.

 

Add Unit Tests to Existing Xcode Project

(Optional) Share the Target and Testing Scheme

If you add a unit test target, you'll most likely want to share your testing scheme with your team over version control (git, svn, etc.) Otherwise you're teammates will have to setup for themselves.

Goto Editor -> Manage Schemes -> Click Shared next to the Unit Test

 

Share Xcode Schemes with Teammates

Adding Resources

When you want to test code or import resources like images or data files you'll need to tell the testing target about the resources. There are two ways, you can do it when you first add the resource to the project or you can do it by editing the Build Phases for the unit test target.

 

Adding New Resources to the Unit Test Target

Click on File -> Add Files to "TestProject" -> Click on the checkbox on unit test target and Copy items

 

Copy Items and add to Unit Test Target in Xcode 4

Adding Existing Resources to the Unit Test Target

Click on your project "TestProject" -> Build Phases -> Expand one of three (Compile Sources, Link Binary With Libraries, or Copy Bundle Resources)

 

Build Phases for unit test in Xcode 4

Resource Paths are Different!

Many assumptions that your bundle is the main bundle will cause problems when testing. (Especially when adding tests to existing code) Look at the difference in bundles, the main bundle isn't what you'd expect in a unit test.

NSString *mainBundlePath = [[NSBundle mainBundle] resourcePath];

NSString *directBundlePath = [[NSBundle bundleForClass:[self class]] resourcePath];

NSLog(@"Main Bundle Path: %@", mainBundlePath);

NSLog(@"Direct Path: %@", directBundlePath);

NSString *mainBundleResourcePath = [[NSBundle mainBundlepathForResource:@"Frame.png" ofType:nil];

NSString *directBundleResourcePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"Frame.png" ofType:nil];

NSLog(@"Main Bundle Path: %@", mainBundleResourcePath);

NSLog(@"Direct Path: %@", directBundleResourcePath);

Output:

Main Bundle Path: /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator5.1.sdk/Developer/usr/bin

Direct Path: /Users/paulsolt/Library/Developer/Xcode/DerivedData/PhotoTable-dqueeqsjkjdthcbkrdzcvwifesvl/Build/Products/Debug-iphonesimulator/Unit Tests.octest

Main Bundle Path: (null)

Direct Path: /Users/paulsolt/Library/Developer/Xcode/DerivedData/PhotoTable-dqueeqsjkjdthcbkrdzcvwifesvl/Build/Products/Debug-iphonesimulator/Unit Tests.octest/Frame.png

Problem: My Unit test has a nil image, data file, etc. Why?

The unit test doesn't use the same bundle for resources that you're accustomed to when running an app. Therefore, the resource we're trying to load cannot be found.  You'll need to make changes to the code to support testing external resources (images, data files, etc). For example the following code

- (UIImage *)resizeFrameForImage:(NSString *)theImageName {
UIImage *image = [UIImage imageNamed:theImageName];
// ... do magical resize and return
return image;
}

Solution 1: Change the function parameters

Functions like these are semi-black boxes that aren't ideal for testing. You want access to all your inputs/outputs, especially if we're working with any kind of file resource. To fix it, just pass in the resource from the unit test, rather than having the function load it from a NSString object.

- (UIImage *)resizeFrameForImage:(UIImage *)theImage {
// ... do magical resize and return
return theImage;
}

Solution 2: Change the resource loading inside the function

If you need to load the resource in the function, you can alternatively change the way it is loaded. You need to stop using UIImage's imageNamed: method and switch to imageWithContentsOfFile: This way you can pass in the resource with the correct path, however it'll change logic elsewhere in your app.

- (UIImage *)resizeFrameForImage:(NSString *)theImagePath {
UIImage *image = [UIImage imageWithContentsOfFile:theImagePath];
// ... do magical resize and return
return image;
}

Solution 3: Load resources using the bundle for the current class

- (UIImage *)resizeFrameForImage:(NSString *)theImageName {
// Note: There are several ways you can write it, but make sure you include
//  the extension or you'll have trouble finding the resource
// 1. NSString *imagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"Image.png" ofType:nil];
// 2. NSString *imagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"Image" ofType:@"png"];
NSString *imagePath = [[NSBundle bundleForClass:[self class]] pathForResource:theImageName ofType:nil];
UIImage *image = [UIImage imageWithContentsOfFile:imagePath];
// ... do magical resize and return
return image;
}

My First Test

Code completion in Xcode will make writing tests easy. To test different things you'll use the following macros:

  • STAssertNotNil(Object, Description);
  • STAssertEquals(Value1, Value2, Description);
  • STAssertEqualObjects(Object1, Object2, Description);

Example: MyTest.m

@implementation TestImagePrintHelper
- (void)setUp
{
[super setUp];
// Set-up code here.
}
- (void)tearDown
{
// Tear-down code here.
[super tearDown];
}
- (void)testName {
NSString *testFirstName = @"Paul";
STAssertEqualObjects([person firstName], testFirstName, @"The name does not match");
}
@end

The STAssertEqualObjects macro will invoke the object's isEqual method, make sure you write one. See the section below. If you used the STAssertEquals it will test for primitive/pointer equality, not object equality.

Testable Code

Writing testable code requires that you add some additional functions that might feel optional before you decided to start testing.

1. Create a isEqual method for your class.

Most of the time you'll want to compare if the object is the correct object. This always requires that you write an isEqual method, otherwise you'll be using the NSObject isEqual test and it'll compare address pointers for the objects.

Example: Person.m

- (BOOL)isEqual:(id)other {
if (other == self) { // self equality, compare address pointers
return YES;
}
if (!other || ![other isKindOfClass:[self class]]) { 
// test not nil and is same type of class
return NO;
}
return [self isEqualToPerson: other]; // call our isEqual method for Person objects
}
- (BOOL)isEqualToPerson:(Person *) other {
BOOL value = NO;
if (self == other) { // test for self equality
value = YES;
 } elseif([[selffirstName] isEqualToString:[other firstName]] &&
[self age] == [other age]) {
// Add any other tests for instance variables (ivars) that need to be compared
value = YES;
}
return value;
}

2. Create a description method for your class.

This is what will output on the command line, rather than the objects memory address. It can be also called when you decide to print the value in a tests

Example: Person.m

- (NSString *)description {
return [NSStringstringWithFormat:@"Person Name: %@ Age: %d", [self firstName], [self age]];
}

Example: Test using the description method, and we'll see the first name and age printed like it was formatted in our description method.

STAssertEqualObjects([personfirstName], testFirstName, @"The name does not match %@", person);

Further reading in Apple's Unit Testing Guide is available. Now you have the basics for unit testing. The next part will provide an example project using resources and providing unit tests.

(Part III: Coming Soon)

Learn iPhone Programming via Skill Share in Rochester, NY

I'm teaching a small hands-on course for iPhone development in Rochester, NY on April 21st 10am-12pm.

Signup

Space is limited: http://skl.sh/rIUWEE

About this Class

Learn how to get started with iPhone/iPad development and start making iPhone/iPad apps today. Install the tools and create an app without prior experience.

Lecture: 30-45 minute lecture on getting started

  • Xcode - Code editor
  • Objective-C - Computer language
  • Setting up developer credentials

Workshop: 1:30 hours

  • Learn how to start programming
  • Build your first app - hands on learning

Recommended Reading

About Paul Solt

  • Computer science graduate student at RIT
  • Worked at Apple Inc. and Microsoft.
  • 4 years as a computer science student instructor at RIT
  • 5 apps on App Store
  • 12 years of programming experience

Prerequisites

  • Mac computer (Macbook Pro, Macbook Air, iMac)
  • Install Xcode 4.3+ from the Mac App Store. http://developer.apple.com/xcode/
  • (Optional) Bring iPhone/iPad with usb cables
  • (Optional) Register as a iOS Developer $99/year: https://developer.app

Creating NSDocument using Folder Bundles and UTI Identifiers

I've been working on a Lion NSDocument-based application that deals with multiple data files. I decided to work with the NSFileWrapper and treat a folder like a single file. (i.e. Xcode Project Files .xcodeproj) I wanted to get an icon to display for the folder and to have it appear as a  single file on Finder. In the NSDocument you need to implement the two save/load methods to get started and then you need to edit your Info.plist file.

- (NSFileWrapper *)fileWrapperOfType:(NSString *)typeName
                               error:(NSError *__autoreleasing *)outError {
    // Save data here ...
}

- (BOOL)readFromFileWrapper:(NSFileWrapper *)fileWrapper
                     ofType:(NSString *)typeName
                      error:(NSError *__autoreleasing *)outError {
    // Load data here ...
}

Problems

There's a few gotchas with the .plist file. In order to create a NSDocument using Folder Bundles you'll need to set the UTI Identifier for the Document  Type and the Exported/Imported UTIs. You'll find this setting on the Info page for your Xcode Target.

 

  1. My App doesn't load saved bundles (NSFileWrapper), instead it just looks inside the folder.Can t Load Data
  2. I tried to undo the Export UTI by deleting it and remove the Identifier, but that doesn't help.
  3. By deleting the Identifier I can't load "New documents".

Solutions

There are two solutions, the second one is more robust, but the first will get you back to when it was working.

Solution 1: Revert back to Xcode template defaults

  1. Remove all Exported UTIs
  2. Open your App .plist file and remove the LSItemContentTypes from your App .plist file. You can't see it in the Target -> Info page.
    1. NOTE: if you don't delete an empty LSItemContentTypes array, then you won't be able to open New files.
    2. If LSITemContentTypes is defined, it'll ignore the "Bundle" checkbox (LSTypeIsPackage) See the Apple docs.
  3. Make sure "Bundle" is checked on the Document Types on your Target's Info page, or (LSTypeIsPackage on .plist file)
    1. This option is magic and auto generates a Identifier for you. (check your save file with "mdls" on the Terminal)
  4. Don't use an Identifier if you check the Bundle option (Document is distributed as a bundle)
  5. Clean the Xcode project

 

Solution 2: Use the package UTI

You'll need to add the correct LSItemContentTypes found in Apple's documentation. I took a look at the Xcode project file with the Terminal utility "mdls" (See sample output below)

  1. Set a unique identifier (Reverse DNS name) com.Your_Company_Name.Project_Name
    1. It needs to match theDocument Type and Exported UTIs
  2. For a bundle, you'll need to set the "Conforms To" for the Exported UTIs to "com.apple.package"
    1. Apple Docs: "A package (that is, a directory presented to the user as a file)"
  3. I noticed Xcode project files also include, public.composite-content
    1. Apple Docs:  "Base type for mixed content. For example, a PDF file contains both text and special formatting data"
  4. NOTE: If you mistype com.apple.package, or leave it out, your file folder will be treated like a public.folder, and you'll get folder behavior.
  5. Clean the Xcode project

Com apple package

Working Icon and Bundle File

If all goes well you'll now see the Icon (icns) file that you created in the Open dialog and the folder will appear to be a bundle.

OpenDialogWorks

Using MDLS to inspect your files

The only way it works as a bundle is if the "com.apple.package" ContentType is listed, otherwise something is wrong. Check your save files with the "mdls" command.

mdls MathAndArt.xcodeproj/

Screen Shot 2012 02 02 at 7 57 23 PM

The Potential of Siri

I've been working on iPhone development for several years and I have been very excited to see how the iPhone SDK has progressed. See my original article on how I envisioned the iPad revolutionizing the computing experience. Siri takes the iPhone platform to an entirely new level.  Feature article by MD

If you're curious as to how the potential of Siri will impact iOS App development, then look no further than the video Apple used to demonstrate the technology, despite its somewhat heavy-on-the-awesome slant. The fact of the matter is, this is voice recognition technology that's approaching the level of sophistication you saw in Startrek Voyager as a kid, and it could do wonders for 3rd party App developers.

"Load Angry Birds. New game, please. Aim 45 degrees off the starboard. Lower 3 degrees. Fire. Raise 3 degrees. Fire. Down with the piggies! Fire."

It sounds far fetched, but is it? Voice control to do anything from text messaging to calling, and it's not difficult to imagine it controlling things that are not traditionally based around natural voice input. It'd also be a great idea to work in some camera/augmented reality games, as playing "I Spy" with Siri could only make the device even more intuitive, when you're not ringing your husband, friend or O2 without even touching your handset.

An open API to Siri will help push developers in a new direction building innovative software for the iPhone 4S, given that Siri pushes the boundaries in a way that encourages people to try something new. Google Goggles was a great example of this - it took what QR codes were doing and pushed it farther than anyone had expected it to go - I certainly didn't expect to be able to use my voice to dictate a text message, make two calls and send an email to my boss, did you?

The best thing about it is the potential it awards developers working with voice and hardware interaction, as unless you're paranoid about SkyNet and HAL-9000 becoming a reality, it certainly puts a friendlier face on an already friendly looking phone. All that's left is for us to enjoy the adventures many app programmers are embarking on with Siri, in addition to the talented invidiual who's just ported it to the iPhone 4. Good times to come, ahead.

clang++ is awesome, goodbye g++

I finally got around to reading up on the clang compiler and I love it. The error messages are so helpful. Here's a quick example with a syntax error. Lets compile it on the command line.

#include <iostream>
int main() {
	std::cotut << "Hello World";
	return 0;
}

What happens when I run the code with g++?

paulsolt@~/dev $ g++ test.cpp
test.cpp: In function ‘int main()’:
test.cpp:3: error: ‘cotut’ is not a member of ‘std’

Here's what happens in clang++, notice how it highlights the position and it's actually more helpful in fixing the error.

paulsolt@~/dev $ clang++ test.cpp
test.cpp:4:7: error: no member named 'cotut' in namespace 'std'; did you mean 'cout'?
        std::cotut << "Hello World";
        ~~~~~^~~~~
             cout
/usr/include/c++/4.2.1/iostream:63:18: note: 'cout' declared here
  extern ostream cout;          ///< Linked to standard output

Idea to App Store - How to make, market, and sell your iPhone App

I've posted my presentation from the Computer Science Community at RIT on making iPhone/iPad apps from February 2011. The video is in two parts and I discuss the initial sales, analytics, marketing when releasing an iPhone app.

Update: 8/31/11 Idea to App Store Slides (PDF)

Part 1:

httpvh://www.youtube.com/watch?v=2uza2teEBEQ

Part 2:

httpvh://www.youtube.com/watch?v=iyBis6W28Rw

Transparent UITableView with Custom Background UIView and Tap Gestures

In order to create a custom background for a transparent UITableView you'll need to do a few things. I've got the basic code below after a lot of tinkering. I've also included how to make it so you can hide the UITableView when you tap in the transparent areas below the rows using a UITapGestureRecognizer. In the images below you can see the custom view in action.

UITableView with custom backgroundView
UITableView with Custom Background

[caption id="attachment_1101" align="aligncenter" width="159" caption="Hidden UITableView Showing Custom Background"]Hidden UITableView with Custom Background[/caption]

Key Points:

  • Don't subclass UITableView, instead use it as a instance variable in your own custom UIViewController subclass.
  • Create a custom UIView subclass to use as the background view, this will be visible when the UITableView is hidden or has a transparent background view.
  • On iPad make sure you clear the UITableView's backgroundView and set it to nil in addition to setting the background color to[UIColor clearColor]
  • Register a UITapGestureRecognizer with the viewController's view and then set the attribute cancelsTouches to NO so that the touches from the gesture propagate to both the UITableView and the custom background view.
  • In the -(void)handleTapGesture: method you'll want to send taps that don't touch a row to toggle the UI so that the UITableView hides or unhides.

Notes:

  • I show a UINavigationBar, so my UITableView frame needs to take into account the size of the navigation bar.
  • Set the UILabel's or custom views backgroundColor in the table's cells to have [UIColor clearColor] so that they animate and fade correctly.

See the sample code below: