Tuesday, January 6, 2009

Creating a User Configurations on the iPhone

Reviewing some applications for the iPhone I noticed that some were making use of the Settings area to hold some User Configuration preference, which looked fantastic. I decided to investigate how this could be done in my own application and discovered that it is relatively east to create.

First within the XCode project we create a Settings.bundle file, by selecting File/New from the menu and selecting the Settings section on the left hand side of the dialogue.


This will create two new files in your project Root.strings and Root.plist. Open the Root.plist file by double clicking and you can see the editor dialogue appear with four default settings already entered under the 'PreferenceSpecifiers" section. You can modify or delete these as required.



There are a number of difference options open to the developer:
PSGroupSpecifier
This has only the one properties defined which is Title. This is used to group a number of related fields together.

PSTitleValueSpecifier
This displays a "Read Only" setting it cannot accept input from the user but can be used to display settings changed elsewhere.
Valid properties are: "Key", which is a string is used as a key to map to the value that a user will access the control. "DefaultValue" Another String property which stores the text that will be displayed in the specifier.

PSTextFieldSpecifier
Displays User enterable field. Properties include "Key" the name used to access the setting, "DefaultValue" a String property which is the initial value. "IsSecure" a boolean which defines if the charecters of the text field will be hidden, used for password fields. "KeyboardType" a String property which can have one of the following values: Alphabet, NumbersAndPunctuati on, NumberPad, URL, EmailAddress. This property specifies which type of keyboard to display when text field is selected. "AutocapitalizationType" a String property which can have one of the following values: None, Sentences, Words, AllCharacter. "AutoCorrectionType" string property which can have one of the following values: Default, No, Yes

PSSliderSpecifier
Displays a slider on a bar which allows the user to select a number in a specified range. "Key", a String property which holds the location of the control. "MinimumValue" and "MaximumValue" are numerical values which corresponds to the slider range. "DefaultValue" a numerical property which defines the first position of the slider.

PSToggleSwitchSpecifier
Displays a toggle switch which can be either On or Off. "Key", the name of the control. "TrueValue" and "FalseValue" defines the value of the control when it is in the On or Off position.". "DefaultValue", boolean property which defines the position of the Toggle button the first time it is loaded.

PSMultiValueSpecifier
Displays a list in a second view and allows the user to select one element from the list. "Key", defines the name of the key. "Values" of type Array which stores sub-elements of type String, each element provides a a possible value for the control to take. "DefaultValue" a String property that defines the initial value of the control, should be one of the elements of the Values list.

PSChildPaneSpecifier
Displays the contents of another Settings file which is useful if you want to break teh configuration up into various related sections. "Key", is the name of the control. "File" is the name of other plist file without the extension.


Testing the values in code
The simple way to read teh values in your application is to use the following
NSString *hostServicey_key = [[NSUserDefaults standardUserDefaults] stringForKey:@"hostService_key"];
Which should look through the settings field and retrieve the value of the property with the "Key" of "hostService_key".

I have however noticed that this value was being set to "nil" if the application was loaded onto the iPhone for the first time. The only way around this was to check the value before using it. If however the User goes into the Settings area first there is no problem.

// The URL of the Webserver
NSString *hostServicey_key = [[NSUserDefaults standardUserDefaults] stringForKey:@"hostService_key"];
//If the first value is nil, then we know that the defaults are not set.
if(hostServicey_key == nil)
{
//Get the bundle path
NSString *bPath = [[NSBundle mainBundle] bundlePath];
NSString *settingsPath = [bPath stringByAppendingPathComponent:@"Settings.bundle"];
NSString *plistFile = [settingsPath stringByAppendingPathComponent:@"Root.plist"];

//Get the Preferences Array from the dictionary
NSDictionary *settingsDictionary = [NSDictionary dictionaryWithContentsOfFile:plistFile];
NSArray *preferencesArray = [settingsDictionary objectForKey:@"PreferenceSpecifiers"];

//Loop through the array
NSDictionary *item;
for(item in preferencesArray)
{
//Get the key of the item.
NSString *keyValue = [item objectForKey:@"Key"];

//Get the default value specified in the plist file.
id defaultValue = [item objectForKey:@"DefaultValue"];

if([keyValue isEqualToString:@"hostService_key"])
hostServicey_key = defaultValue;
}
}

Monday, December 15, 2008

Dynamic SQL Statements with SQLite

Recently I tried to build a dynamic SQL Statement within XCode and it proved more difficult than expected. As the parsing function used my SQLite enjoys getting a Const Char data type, building that from a NSString was not a simple matter. After some investigation my first attempt came up with using the int sqlite3_bind_text(sqlite3_stmt*, int, const char*, int n, void(*)(void*)); function while passing a sql statement like this "Select * from table1 where name=?". The idea was that this would replace the "?" with the value I passed through, however it never seemed to bring back any records. SQLite does not make it easy to see the actual statement (Oh the joys of MS SQL Profiler!), so I could not see what was going wrong.

An alternative I came up with was to use the following:


NSString *sql1 = [[NSString alloc] initWithFormat:@"Select * FROM Table1 where name='%@'", paramName];
NSString *sql = [NSString stringWithUTF8String:[sql1 UTF8String]];
sqlite3_stmt *selectstmt = nil;
if(sqlite3_prepare_v2(database, [sql UTF8String], -1, &selectstmt, NULL) == SQLITE_OK)
{
while(sqlite3_step(selectstmt) == SQLITE_ROW) { ....... }
}


This worked fine, however if someone has an alternative, please let me know.

Sunday, December 14, 2008

Unit Testing in XCode

With all previous applications I've written I have tried to implement a TDD (Test Development Development) process, as this leads to a more concise product which is also easier to support. With Microsoft Visual Studio I've tended to use NUnit rather than the built in option, however I've not seen anything out there for XCode. Recently I've come across a project in Google which does exactly the type of Unit Testing I was looking for. gives a number of simple steps for implementing TDD for use with the iPhone.

Here is another simple example.

Download the latest google-toolbox-for-mac file from the site. Extract the content of the ZIP file to your Hard Drive.

First we create an iPhone Window-Based Application, by choosing "File/New Project" from the xCode Menu. Click Choose and enter the name "Calculation" and click Save. This will be a simple non-functional harness that we can test against.


Add a simple object to the project by selecting "File/New" from the menu and selecting a NSObject subclass. Click Next and give it a filename of "Calculation" with a tick in the "Also create Calculation.h" box and click Finish.

Within Calculation.m we add a new method which takes an integer, add 5 to this and returns the result. The code would look like the following:
#import "Calculation.h"
@implementation Calculation
- (int) add5: (int) num
{
int result;
result = num 5;
return result;
}
@end



Within Calculation.h we the following:

#import <Foundation/Foundation.h>

@interface Calculation : NSObject {
}
- (int) add5: (int) num;
@end



Adding the Unit Test Framework


Next we add a UnitTest and supporting Framework to the project by adding a new target.

Right Click the Targets folder in XCode and select "Add/New Target" and select a Cococ Touch Application and give it the name "UnitTests", adding it to the Calculation project.


Create a new File Group within the XCode project by selecting "Project/New Group" from the menu and giving it the name UnitTests. Add the following files to the Group which are all part of the Google ToolBox for Mac ZIP file you extracted earlier. Be sure to set the target as UnitTets and not Calculation
  • GoogleToolsForMac/GTMDefines.h

  • GoogleToolsForMac/UnitTesting/GTMIPhoneUnitTestDelegate.h

  • GoogleToolsForMac/UnitTesting/GTMIPhoneUnitTestDelegate.m

  • GoogleToolsForMac/UnitTesting/GTMIPhoneUnitTestMain.m

  • GoogleToolsForMac/UnitTesting/GTMSenTestCase.h

  • GoogleToolsForMac/UnitTesting/GTMSenTestCase.m




Finally add a new Build Script to the project by right clicking the "UnitTests" Target and selecting "Add/New Build Phase/New Run Script Build Phase ".



The Shell should be "/bin/sh" and the Script should be the location of the "RunIPhoneUnitTest.sh" file located in the Google ToolBox for Mac ZIP file you extracted earlier. In my case it was:
/Volumes/Windows/Development/GoogleToolsForMac/UnitTesting/RuniPhoneUnitTest.sh


Change the Active Target in XCode to "UnitTests" by using the drop down box in the top left of the Project Window and click Build. Everything should compile without error. Now click "Build and Go" and the iPhone Simulator should start and the Debug Console will display a list of Unit Test messages ending with "Executed 0 tests, with 0 failures (0 unexpected) in 0.001 (0.001) seconds". This means that the Framework has been installed and is ready for our custom tests.

Adding a custom Unit Test


Create a new class in XCode, by right clicking the UnitTests group and selecting "Add/New File". Choose a Cocoa Objecttve-C test case class" from the list shown and call it "CalculationTest" making sure that the target is set to UnitTests and unticking the "Also create CalculationTest.h" box and click Finish.



The code for our class would be the following:
#import "GTMSenTestCase.h"
#import "Calculation.h"

@interface CalculationTest : GTMTestCase {
}
@end

@implementation CalculationTest
- (void) testCalculation
{
Calculation *calculation = [[Calculation alloc] init];
STAssertEquals(16, [calculation add5:12], @"Not Equal! %d", [calculation add5:12]);
[calculation release];
}
@end

Finally we need to add Calculation.m and Calculation.h to our new UnitTests target and the easiest way to do this is to select them in XCode and Drag them down to the "Targets/UnitTests/Compile Sources" folder.

Running the custom Unit Test


Clicking Build in Xcode should produce the following:



This is telling us that the UnitTest which called the add5 method was passed 12 and returned 17, which is correct, however our Assertion was expecting a 16, so the test failed. We correct this by editing CalculationTest.m and changing the line:
STAssertEquals(16, [calculation add5:12], @"Not Equal! %d", [calculation add5:12]);
with
STAssertEquals(17, [calculation add5:12], @"Not Equal! %d", [calculation add5:12]);

Following this change we recompile and everything builds correctly.



This is a fairly trivial example, but it shows the steps needed to create a TDD process using XCode.

Saturday, December 13, 2008

Folders for Source Files in XCode

Having to been used to Microsoft Visual Studio I'm more familiar with organising my source code into folders. From what I can see XCode has the same functionality, however this structure has no effect on the underlying structure stored on the disc. So to create a new folder in xCode you select the files you with to place in the folder and click "Project/Group" from the menu bar. Enter a name for the New Group and that's it. You can also create new groups by selecting "Project/New Group" from the menu bar.

Tuesday, December 9, 2008

SQLite Error Codes

I've been getting problems on how to debug SQL errors on the iPhone and found a list of all error codes generated:

#define SQLITE_OK 0 /* Successful result */
/* beginning-of-error-codes */
#define SQLITE_ERROR 1 /* SQL error or missing database */
#define SQLITE_INTERNAL 2 /* Internal logic error in SQLite */
#define SQLITE_PERM 3 /* Access permission denied */
#define SQLITE_ABORT 4 /* Callback routine requested an abort */
#define SQLITE_BUSY 5 /* The database file is locked */
#define SQLITE_LOCKED 6 /* A table in the database is locked */
#define SQLITE_NOMEM 7 /* A malloc() failed */
#define SQLITE_READONLY 8 /* Attempt to write a readonly database */
#define SQLITE_INTERRUPT 9 /* Operation terminated by sqlite3_interrupt()*/
#define SQLITE_IOERR 10 /* Some kind of disk I/O error occurred */
#define SQLITE_CORRUPT 11 /* The database disk image is malformed */
#define SQLITE_NOTFOUND 12 /* NOT USED. Table or record not found */
#define SQLITE_FULL 13 /* Insertion failed because database is full */
#define SQLITE_CANTOPEN 14 /* Unable to open the database file */
#define SQLITE_PROTOCOL 15 /* NOT USED. Database lock protocol error */
#define SQLITE_EMPTY 16 /* Database is empty */
#define SQLITE_SCHEMA 17 /* The database schema changed */
#define SQLITE_TOOBIG 18 /* String or BLOB exceeds size limit */
#define SQLITE_CONSTRAINT 19 /* Abort due to constraint violation */
#define SQLITE_MISMATCH 20 /* Data type mismatch */
#define SQLITE_MISUSE 21 /* Library used incorrectly */
#define SQLITE_NOLFS 22 /* Uses OS features not supported on host */
#define SQLITE_AUTH 23 /* Authorization denied */
#define SQLITE_FORMAT 24 /* Auxiliary database format error */
#define SQLITE_RANGE 25 /* 2nd parameter to sqlite3_bind out of range */
#define SQLITE_NOTADB 26 /* File opened that is not a database file */
#define SQLITE_ROW 100 /* sqlite3_step() has another row ready */
#define SQLITE_DONE 101 /* sqlite3_step() has finished executing */
/* end-of-error-codes */


The code to retrieve this would be used like so:

if (sqlite3_open([dbPath UTF8String], &database) == SQLITE_OK) {
const char *sql = "select tbl_name from sqlite_master";
sqlite3_stmt *selectstmt;
NSLog(@"SQLite= %d", sqlite3_prepare_v2(database, sql, -1, &selectstmt, NULL));

Monday, December 8, 2008

Creating a database project - iPhone

This is the second part of an article outlining the code to implement a Client/Server application between the iPhone and a Windows WCF Service. The first part focused on re-factoring an existing webservice to use a back end database. Everything in this article assumes that you have successfully implement the changes described.

Creating our local iPhone database


The easiest way to create a SQLite database is to download the SQLite Firefox extension. This provides a GUI to create and manage any SQLite databases. Simply select "Database/New Database" from the menu and type the name JokeGenLocal as the name. You will be asked for a location of the database, i'd recommend not having it within your XCode Application, especially if ypu're using SVC for source control. I simply created the file in a folder called iWCFDemoDB at the same level as the original iWCFDemo project folder.



The local database format will mirror the server database format (created previously) as much as possible at this stage of development so I create two tables Joke and JokeCategory.



JokeCategory


Joke


We should also create indexes for these database table, but as our record set is very low at this stage I'm not going to do that.

Add the Database File to Your XCode Project


Now we have a database we want to include this as part of the XCode project. Right click the Resources folder in XCode and select "Add/Existing File..". Browse to the location where you saved the JokeGenLocal.sqlite file and click OK. I would not copy the file as local resource, but that decision is up to you.

Next we need to link to the library that contains all of the SQLite functions. To link to that library, from your XCode project right click the frameworks folder and select “Add/Existing Frameworks…”. Browse to the iPhoneSDK folder (mine was in/Developer/Patforms/iPhoneOS.platform/Developer/SDKs/iPkoneOS2.2.sdk/usr/lib/) and select the libsqlite3.0.dylib file.

Adding classes to xCode to hold access the database information


Once you have your database added to the application we need to create proxy classes which mirror the database structure and provide a mechanism for reading the records from the DB File. These are much the same as the Entity Framework classes which are created in Visual Studio, except Microsoft make it far easier for the developer. We also need a class that checks whether the iPhone has the database or not, if not then we copy it to the phone this will be the job of the SQLAppDelegate.

The boolean "isDirty" tells the application if the object was changed in memory or not and "isDetailViewHydrated" tell the application, if the data which shows up on the detail view is fetched from the database or not. Our application allows for very little in the way of editing at present however I'm leaving these in there as best practice and to assist in future development.

Create three new classes with headers and add the following code to each:
Category.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <sqlite3.h>
@interface Category : NSObject {
NSInteger categoryID;
NSString *categoryDescription;

//Intrnal variables to keep track of the state of the object.
BOOL isDirty;
BOOL isDetailViewHydrated;
}

@property (nonatomic, readonly) NSInteger categoryID;
@property (nonatomic, copy) NSString *categoryDescription;
@property (nonatomic, readwrite) BOOL isDirty;
@property (nonatomic, readwrite) BOOL isDetailViewHydrated;

@end


Category.m


#import "Category.h"
@implementation Category
@synthesize categoryID, categoryDescription, isDirty, isDetailViewHydrated;

- (void) dealloc {
[categoryDescription release];
[super dealloc];
}
@end


Joke.h

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <sqlite3.h>

@interface Joke : NSObject {

NSInteger jokeID;
NSInteger jokeCategory;
NSString *jokeText;
NSString *jokeSource;
NSString *jokeGraphic;

//Intrnal variables to keep track of the state of the object.
BOOL isDirty;
BOOL isDetailViewHydrated;
}

@property (nonatomic, readonly) NSInteger jokeID;
@property (nonatomic, readonly) NSInteger jokeCategory;
@property (nonatomic, copy) NSString *jokeText;
@property (nonatomic, copy) NSString *jokeSource;
@property (nonatomic, copy) NSString *jokeGraphic;

@property (nonatomic, readwrite) BOOL isDirty;
@property (nonatomic, readwrite) BOOL isDetailViewHydrated;

@end


Joke.m
#import "Joke.h"
@implementation Joke
@synthesize jokeID, jokeCategory, jokeText, jokeSource, jokeGraphic, isDirty, isDetailViewHydrated;
- (void) dealloc {
[jokeText release];
[jokeSource release];
[jokeGraphic release];
[super dealloc];
}
@end


SQLAppDelegate.h

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@class Category;
@class Joke;

@interface SQLAppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
UINavigationController *navigationController;
//To hold a list of Category objects
NSMutableArray *categoryArray;
NSMutableArray *jokeArray;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet UINavigationController *navigationController;
@property (nonatomic, retain) NSMutableArray *categoryArray;
@property (nonatomic, retain) NSMutableArray *jokeArray;

- (void) copyDatabaseIfNeeded;
- (NSString *) getDBPath;

@end


SQLAppDelegate.m




Creating the contracts


We know the format of the message to be sent and how it will be received from the previous run through and I've bold the important areas.
Client Sends:
WSGetAllRequest
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<GetJokes xmlns="http://tempuri.org/">
<wsJokeRequest>
<RefreshDate>2008-12-09T16:18:36.5706144 08:00</RefreshDate>
</wsJokeRequest>
</GetJokes>
</s:Body>


WSGetAllResponse
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<GetJokesResponse xmlns="http://tempuri.org/">
<GetJokesResult>
<Jokes>
<Joke>
<EntityKey>
<EntitySetName>Joke</EntitySetName>
<EntityContainerName>JokeGenDBEntities</EntityContainerName>
<EntityKeyValues>
<EntityKeyMember>
<Key>ID</Key>
<Value xsi:type="xsd:int">1</Value>
</EntityKeyMember>
</EntityKeyValues>
</EntityKey>
<ID>1</ID>
<Text>"Knock, Knock". "Who's there?", "Sarah", "Sarah Who?", "Sarah doctor in the house."</Text>
<JokeCategoryReference>

<EntityKey>
<EntitySetName>JokeCategory</EntitySetName>
<EntityContainerName>JokeGenDBEntities</EntityContainerName>
<EntityKeyValues>
<EntityKeyMember>
<Key>Category</Key>
<Value xsi:type="xsd:int">1</Value>
</EntityKeyMember>
</EntityKeyValues>
</EntityKey>
</JokeCategoryReference>
</Joke>

... clipped there will be many, many iterations or this .....

</Jokes>
</GetJokesResult>
</GetJokesResponse>
</s:Body>

iPhone Simulator

I found an interesting tool for the Mac which allows for the testing of WebSites via the iPhone interface. Called iPhoney it provides a simple interface and fast way to verify how any website developed will be displayed on the iPhone.
Check out the following site for some other information regarding CSS.