zero one zero dave wraights blog …

24Jun/120

Article: Startup Pack


Startup Pack


What will you learn?

This article documents one approach I use when setting up new iOS applications, the process saves me time and future frustration and I’d like to share it with you in the hope that it will save you time, frustration and maybe even make your life easier.

By the end of the article you’ll have learnt how to setup unit testing, add a logging framework and integrate a ratings library into your applications.

Introduction

As developers we all have at some point in time setup a project or application and added a bunch of libraries that make our lives easier.

We do it so we can reduce our data access code, make logging easy or even a bunch of UI libraries that we’ve written over the years, regardless of the type of pack, the reason is the same; we are lazy - and in this case that’s a good thing.

Because everyone’s toolset and process is different this is a guide for integrating a generic set of libraries and controls into a new project to make the developer and even independent software vendor’s life easier; basically to allow our laziness to continue.

I call it the startup pack since I generally add the same libraries and toolkits each time I start a new project or inherit an existing codebase.

For those wanting to ease the difficulty of using CoreData you might want to investigate MagicalRecord, I have used some of their helper methods throughout this article and I use them on a regular basis with my own applications.

The Usual Pack Members

My applications usually contain the same things: testing framework, custom UI control libraries, a ratings library a feedback and a logging systems.

Let’s walk through the setup of each of these in a single view application.

Creating the Sample Project

For the purposes of demonstrating the startup pack in an actual project, let’s create a sample single view application.

Steps
1. From the File menu choose New > 'Project …' (CMD+SHIFT+N).

2. Choose the 'Single View' application (iOS > Applications) (the components in this startup pack can be used in most project types).

3. Click Next

4. Enter a product name and your company identifier and click Next (Remember not to select the 'Include Unit Tests' checkbox if you intend to add the custom unit testing framework from this tutorial).

5. Optional:  Select the 'Create Git Repository'.

6. Save the Project.

Testing Framework

Whenever you create a project you can add the SenTest/OCTest unit testing framework to your project. In fact you can add this post project startup/creation, although it is a little harder.

I have used this framework in a few past projects however I wasn’t really happy with how it works. For example, OCUnit won’t allow me to run a single unit test (or class of tests), instead it forces me to run ALL of the unit tests every time. The thing that really bugged me is that there isn’t a separate test runner, something I have become very used to in other development environments such as Eclipse, Visual Studio and even Netbeans.

Having this separate runner gives me the ability to structure releases with specific tests, creating a future way to run unit tests for specific versions to find regressions / breaking feature changes.

The testing framework that I use almost every time is the GHUnit testing framework. It addresses the biggest issues I have with OCUnit (such as the test runner), plus its open source so if I need to add an assertion type or understand what the library is doing I simply peak under the head and check out the macros.

The following steps are needed in order to get GHUnit up and running; oh and I’m assuming that you didn’t check that little ‘Include Unit Tests’ setting when you created the project (both can exist in the same project, though it might get confusing).

Downloading The Framework

There are two things we need to download, firstly we need to download the most current packaged version of the framework (at the time of writing that was version 0.4.33GHUnitIOS-0.4.33.zip’) from here.

The second and required file is the GHUnit main class. I’m not sure why this isn’t included in the archive, but it’s a standard main class which you can create yourself or you can find it by browsing the source here and creating a file called GHUnitIOSTestMain.m (or similar). Copy/Save this file into your projects Test folder.

Unzip GHUnitIOS-0.4.33.zip and and copy the entire folder into your project’s Test folder. We’ll add it to the XCode project fully in just a moment.

Creating the Target

The way to separate the application from other output types for your project is to use a Target. In XCode a target is (simplistically talking) a collection of compiler directives and settings with the specific outcome of having some output from running a build.

The target could be a static library, the application or even a separate third party project that your project depends upon. GHUnit uses a separate test target to configure the dependencies etc and compiles into a separate library for your application.

Steps
1. From the Project Navigator (CMD+1), select your primary project.
2. At the bottom of the screen on the right is a large button with a plus sign inside a circle titled 'Add Target'.
3. Selection, Applications and choose 'Single View' application and click 'Next'.
4. Name the Testing application (by convention I add the word 'Testing' to the project name).
5. Click Finish.
6. From the Project Navigator right click the folder that was just created and choose 'Add Files to 'Test' …' ('Test' will be replaced with the name you chose).
7. Select the 'GHUnitIOS.framework' folder (single click).  (Important) UnCheck the project target and CHECK the Test applications target.
8. Click 'Add'.

We need to have a project main class (the GHUnitIOSTestMain.m) so let’s add this file in and ensure it belongs to the Test Target’s project membership.

Steps
1. Right click the 'Test' folder and select 'Add Files to 'Test' …'
2. Select the GHUnitIOSTestMain.m file and CHECK the 'Test' project target and UNCheck the applications project target membership.
3. Click Add.
4. Select the 'ViewController', 'AppDelegate' and 'main' files (.m/.h/.xib) from the Test folder and delete them (GHUnit has it's own).

There are two compiler linker flags needed in order to build the Test target successfully ‘-ObjC’ and ‘-all_load’, lets add them now:

Steps
1. From the Project Navigator, select the Project, and then choose the 'Test' target from the list on the right.
2. Click the 'Build Settings' section, scroll down to find 'Other Linker Flags', click the '+'/Add button and enter '-ObjC -all_load'.  This will be added to both Release and Debug configurations. (If you have more you will need to add it to those also).
SDK Version

Change the Deployment for the Test Project to 4.3, by:

1. From the Project Navigator, select the Project, and then choose the 'Test' target from the list on the right.
2. Click the 'Summary' section and change the Deployment target to '4.3'
ARC/NON-ARC

If your project is using ARC (Automatic Reference Counting), the GHUnitIOSTestMain. you get from the GitHub repository isn’t ARC compatible. You will need to perform a couple of extra steps either to make it ARC compatible or set the Target to be a non-ARC project.

Non-Arc:
1. From the Project Navigator, select the Project, and then choose the 'Test' target.
2. Click the 'Build Settings' section, scroll down to find 'Objective-C Automatic Reference Counting' and switch it to 'NO'.
ARC:
1. Open the GHUnitIOSTestMain.m file, replace it's contents with the following ARC friendly AutoRelease Pool

int main(int argc, char *argv[])
{
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, @"GHUnitIOSAppDelegate"));
    }
}

GHUnit should now be up and running, select it from the Scheme chooser and Build/Run.

Logging

Over the years I’ve found that certain tools can make a difference, either during the development of the project or after, once the application has gone live. An example of this is logging.

In the beginning I never cared much about logging; it seemed like just one of those things that took too much time to setup and the benefit seemed negligible.

It wasn’t until I had written an application that was intermittently crashing. What made it harder to resolve was the fact it was happening on iOS 4 and 5 and across 3GS, 4 and 4S. I began with the usual questions like “What were you doing when it …”, and “How many items did you have at the time…” and of course “Were you connected to the server at the time…” and my personal favourite “How many fingers did you have on the screen at the time…”.

More often than not this leads nowhere and your users can quickly loose faith in your ability to find and fix the problem.

In this instance it wasn’t until I actually saw the problem occur was I able to resolve it - almost two weeks AFTER the issue had been initially reported. Adding on the time to go back through the stores' approval process it was a 4 week turnaround. I wasn’t happy and neither was the customer.

What should I have done? Logging. If I had put a mechanism in place to trace and retrieve the logging information I would have been able to find and resolve the issue in under 20 minutes. When I start any new project now I always add a mechanism to centralise and filter log messages, and I sprinkle my code with logging messages as I develop.

The mechanism I have employed is one of replacing the NSLog statement with my own ZLog version. I’ve also backed in a couple of extra’s; firstly I have a ‘logging level’ parameter which can control what get’s logged and what is ignored (I like to call it controlling the noise) and secondly there’s optional support for writing to a CoreData store.

Setting Up the Logging Framework

The whole idea behind this logging mechanism is to make it simple to call and use; to this end we are going to create a macro that let’s us capture critical information at the right time and place.

We need to define a structure that contains the constants that will define the logging levels. I prefer to create a Constants class to store stuff like this. I also like to include the Constants file in my applications prefix file (*_Prefix.pch - pre-compiled headers) so the Constants are available everywhere in the application without having to include it everytime I start a new class or view controller.

Code - Constants.h
typedef enum
{
    ZDebugLevelNone = 0,
    ZDebugLevelDebug = 1,
    ZDebugLevelInfo = 1 << 1,
    ZDebugLevelWarn = 1 << 2,
    ZDebugLevelError = 1 << 3,
    ZDebugLevelCritical = 1 << 4,
    ZDebugLevelTrace = 1 << 5
}ZDebugLevel;

#define kLOG_TO_CORE_DATA 1
#define kAPPLICATION_DEFAULT_DEBUG_LEVEL 4 // WARN

These logging levels are designed to work from the MOST noise (Debug - 1) to the least noise (Critical - 16). By using a default debug level we can set a bar for what is logged or ignored. If TRACE is set as the kAPPLICATION_DEFAULT_DEBUG_LEVEL it will allow ALL log messages to be logged regardless of the log level parameter value.

I often use this approach to provide a global ‘Turn Tracing On’ technique in the event a customer has a problem they are experiencing and I need to see as much detail about the application as possible (or that I have added logging for).

Now let’s create a new class based on NSObject to hold the code:

ZLog.h
#ifdef DEBUG
    #define ZLog(logLevel,args...) _ZLog(__FILE__,__LINE__,__PRETTY_FUNCTION__,logLevel,args);
#else
    #define ZLog(logLevel,x...)
#endif

void _ZLog(const char *file, int lineNumber, const char *funcName, int logLevel, NSString *format,...);

In the header we first check if we are running in DEBUG configuration (another defined constant), we then create a definition for the ZLog method that points to the internal _ZLog method. When running in the Debug configuration we get the luxury of debug symbols which gives us the file name, line number and name of the function the log message was written in. In Release (or Archive) configuration we don’t have symbols so we define a macro that passes just the information we have.

You might be wondering how we can have a definition for a macro pointing to the same method but with different number of parameters. The trick is in the three dots ‘’; they tell the compiler the method is a variadic, in other words the method can take a variable number of arguments.

One important thing to note is that when we call the logging method we don’t need to terminate the call with a ‘nil’, the compiler does that for us.

We’ll look more closely at the implementation and how we make use of those variables.

ZLog.m
void _ZLog(const char *file, int lineNumber, const char *funcName, 
           int logLevel, NSString* format,...) 
{
    int applicationDebugLevel = kAPPLICATION_DEFAULT_DEBUG_LEVEL;

    // Dont log it if we dont need it, but do it all the time if Tracing is turned on.
    if(logLevel < applicationDebugLevel && 
       applicationDebugLevel != DebugLevelTrace) 
            return;

    va_list ap;
    va_start (ap, format);

    // Let's add the EOL if it's not there
    if (![format hasSuffix: @"\n"]) 
    {
        format = [format stringByAppendingString: @"\n"];
    }

    // NSString's initWithFormat is also a variadic method.
    NSString* body =  [[NSString alloc] initWithFormat: format arguments: ap];
    va_end (ap);

    const char* threadName = [[[NSThread currentThread] name] UTF8String];
    NSString* fileName = [[NSString stringWithUTF8String:file] lastPathComponent];

    // Write to the Console
    NSString* logString = nil;
    if (threadName) 
    {
        logString = [NSString stringWithFormat:@"%s/%s (%s:%d) %s",threadName,funcName,[fileName UTF8String],lineNumber,[body UTF8String]];
        fprintf(stderr,logString);
    }
    else
    {
        logString = [NSString stringWithFormat:@"%p/%s (%s:%d) %s",[NSThread currentThread],funcName,[fileName UTF8String],lineNumber,[body UTF8String]];

        fprintf(stderr,logString);
    }
    //[body release];   //UnComment for non-ARC apps

    if(kLOG_TO_CORE_DATA)
    {
        AppLog* appLog = [AppLog MR_createEntity];
        appLog.lastModifiedByUser = @"Dave"; // Make this dynamic based on your needs
        if(logString)
        {
            appLog.logMessage = [logString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
        }
        else
            appLog.logMessage = @"";

        appLog.creationDate = [NSDate date];
        appLog.createdBy = @"Dave";
        appLog.logLevel = [NSNumber numberWithInt:logLevel];

        [[NSManagedObjectContext MR_defaultContext] MR_save];
    }
}

Much of the code is straightforward so I’ll cover the lines which might not be immediately clear:

  1. Check the passed in Debug level against the application debug level, if it’s lower then there’s no point continuing - unless Tracing is on.
  2. Next we extract the passed in (variable number of) arguments and create a string using the format string supplied and the list of arguments in the ‘…’. (See below for an explanation on how variadic variables are parsed)
  3. The next part formats the variables and prints it to the console (just like NSLog does), if there’s a thread it also extracts that information for displaying and logging.
  4. The last part I’m just checking if we are writing to the CoreData entity.

Other than the logging to CoreData the logging method doesn’t write to disk or return the string for further usage (such as writing to disk or sending via emails.) I’ll leave that to your imagination and modification.

Writing a Log Entry

After all of that we can now log errors, information or debug data to the console (and optionally CoreData). Anywhere in our code that we would like to write a log entry we can do the following:

ZLog(ZDebugLevelWarn,@"The user did something to cause this warning.");
Variadic Methods
va_list

This is a type of object for holding the list of arguments.

va_start

This macro knows the starting address of the list of parameters and puts it into the supplied va_list variable. The second parameter to the va_start function is the format used for the other arguments.

va_arg

Although we don’t use this macro (we pass the entire list to NSString’s own variadic method), it provides a way to iterate over each argument, each time it’s called it moves to the next argument.

va_end

Once we are done getting the arguments out from the va_list variable and creating the string from all of the arguments ([[NSString alloc] initWithFormat: format arguments:ap]) we need to set the variable defined by va_list to nil, va_end does this for us.

Zombies

Whilst we are on the subject of logging there’s an environment setting you can set that can make a big difference whilst you are developing. These are the ZombieEnabled and AutoreleaseFreedObjectCheckEnabled environment variables.

When an object is deallocated it is automatically turned into an _NSZombie and it’s memory not actually freed, anytime you then use the _NSZombie the application wont crash and instead it will give you a chance to set a breakpoint on subsequent runs and also print a message to the console.

Now the big gotcha here is that this feature is great whilst you are developing, but it’s a pretty good idea to turn it off when you go live.s

Turning it on:
1. Choose ‘Edit Scheme’ from the scheme chooser
2. Select the ‘Run’ step, and click the ‘Arguments’ section
3. Click the ‘Add’ button under the ‘Environment Variables’ area.
4. Type NSZombieEnabled, with a value of ‘YES’
5. Click the ‘Add’ button again
6. Type ‘AutoreleaseFreedObjectCheckEnabled’ with a value of YES
5. Click OK

One more thing. We need to give ourselves a reminder that Zombie catching is turned on.

  1. Open up the AppDelegate for your application
  2. As one of the first lines in the ‘application:didFinishLaunchingWithOptions’ method add the following:

    if(getenv("NSZombieEnabled") || getenv("NSAutoreleaseFreedObjectCheckEnabled"))
    NSLog(@"NSZombieEnabled/NSAutoreleaseFreedObjectCheckEnabled enabled!");

Ratings

The AppStore is based on ratings. Like it or not, the more ratings your apps have the greater the chance they will have to be seen by potential new customers.
There are a lot of ways to get customers to rate your apps, but what if they forget to rate your application? A little reminder would help right?

Enter Appirater. This third party library (available here) integrates seamlessly into your application and prompts your users to rate your application.

You configure Appirater by modifying the supplied header file Appirater.h, with your application’s name and Application ID. After a pre-set (but configurable) number of startups, Appirater automatically prompts the user to rate your application on the AppStore by popping up an Alert View. There are three buttons in the alert ‘Rate’, ‘Remind’ and ‘Cancel’. If the user chooses to rate your application they are automatically taken to the AppStore page to rate your app.

Steps
  1. Download (or clone) the toolkit from here (git clone http://github.com/arashpayan/appirater/).
  2. Copy/Add the Appirater.m/.h files into your application’s project.
  3. Open your AppDelegate.h file and add the import for the Appirater.h to the top (#import “Appirater.h”).
  4. In the application:didFinishLaunchingWithOptions method right before the ‘return YES’, add the following call to tell Appirater your application has started up.

    [Appirater appLaunched]; return YES;

Next we need to configure Appirater:

Steps
  1. Open the Appirater.h file
  2. Change the value assigned to the APPIRATER_APP_ID define to your own applications ID.

Note: The name that Appirater shows comes from your bundle display name as set in your targets' Information.

One last thing about testing, in order to test the Appirater is working and going to the correct URL when you tap the ‘Rate’ button you don’t have to wait - simply edit the Appirater.h file and change the APPIRATER_DEBUG define to a YES.

Feedback

I believe that if you genuinely want to make your products better you will need your customers feedback. There are lots of ways to capture feedback from users, but by far the simplest way I have found (but not the only mechanism I use) is to provide a button somewhere inside the user interface to allow users to give you feedback.

There are two simple reasons I use email:

  1. I get their email address so I can (if they permit me) have a conversation after the initial email to understand the need or problem they are experiencing and;
  2. I find automated systems can sometimes restrict and constrain the customers' response - they can become far too automated in their quest to gather information from the user.

An added benefit of email is that the user can see what information is being sent to us. Bear in mind that this might not be a great solution if the information being sent is sensitive, in those cases I would build an encryption system to allow me to encrypt the data in way that I can get it out on the other side (server or my local development machine).

Often it’s the little things people say in emails that lead to good ideas, or better understandings of why they have started the conversation.

I take a four tiered approach with my feedback email system:

  1. Personalised Email. I create a personalised email containing a ‘Feedback/Comments’ section and usually a section for ‘What I was doing when the issue occurred’ and a statement explaining that any information they share is confidential and that I will not share their email address with anyone - ever (which I wont); sometimes I will ask if email is the preferred medium for getting back to them.
  2. Screenshot. The feedback system takes a screenshot of what was on screen prior to the Feedback button being pressed. (In some apps this might not be safe due to privacy reasons)
  3. System Logs. I bundle up the system logs. I create a file (optionally encoded using something like Base64) of any relevant system logs I have in Core Data. In some cases I prompt and ask permission prior to attaching the logs.
  4. Device Metrics. I insert into the body of the message (so the user can see what I’m sending) a list of Key/Value pair information regarding the device, it’s OS version, screen resolution, application version/build and so on.

Let’s walk through each part.

Personalised Email

In order to send email’s we need to include the MessageUI.framework; once included we add the protocol to our class’s interface/header to allow us to receive messages from the MessageUI delegate methods defined in MFMailComposeViewControllerDelegate. This protocol’s method will allow us to respond to the outcome of attempting to send the email.

Steps
  1. From the Project Navigator select your Project and choose your application target.
  2. Select the Build Phases, and click the ‘+’ (Add) button to add the ‘MessageUI.framework’ library.
  3. Open the Header (.h) file for either an existing class or view controller OR create a new class.
  4. Add the import statement for the MessageUI.framework to the top of your class:

    #import <MessageUI/MessageUI.h>
  5. After the interface declaration add the protocol declaration:

    <MFMailComposeViewControllerDelegate>
  6. Go to your classes implementation (.m) and add the required protocol’s method:
Code - Protocol Implementation
 - (void) mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error
{
    if(result == MFMailComposeResultCancelled)
    {
        // Let the user know it was canceled
    }
    else if(result == MFMailComposeResultSent)
    {
        // Let the user know the email was sent
    }
    else if(result == MFMailComposeResultFailed)
    {
        // Let the user know the email failed to send, even though a valid account was found
    }

    [controller dismissModalViewControllerAnimated:YES];
}

7 . Wire up a button or other element in your applications UI to trigger a method in the same class as the protocol implementation above (in my case I’ve called it generatedFeedbackEmail):

Email Generation
// As a rule this code would not reside inside the event handle for the button, but for shortness' sake it is.
- (void) generateFeedbackEmail:(id)sender // called by the UI Button etc
{

    if([MFMailComposeViewController canSendMail])
    {
        // Grab the entire screen as a UIImage and wrap that into NSData for sending
        // via email as an attachment
        UIImage* screenShot = [self generateScreenshot];
        NSData* imageData = UIImagePNGRepresentation(screenShot);

        MFMailComposeViewController *mailComposer = [[MFMailComposeViewController alloc] init];
        mailComposer.delegate = self;
        NSMutableString* body = [[NSMutableString alloc] init];

        NSString* dashes = @"<br/>------------------<br/>";
        NSString* CR = @"<br/>";

        // The feedback Section
        [body appendFormat:@"Section/Area/Function%@%@%@%@%@%@",CR,dashes,CR,CR,CR,dashes];
        [body appendFormat:@"Comments/Feedback/Suggestion%@%@%@%@%@%@",CR,dashes,CR,CR,CR,dashes];

        // The privacy statement
        [body appendFormat:@"%@%@Your email and data are considered private and will never be shared.%@",CR,dashes,CR];

        // Add the Log Data
        [body appendFormat:@"%@%@%@%@", dashes,CR,[self generateMetricsString],dashes]

        [mailComposer setMessageBody:body isHTML:YES];
        [mailComposer setToRecipients:[NSArray arrayWithObject:@"feedback@yourdomainname.com"]];
        [mailComposer setSubject:@"Application Feedback"];

        // Add the Image as an image in PNG format.
        [mailComposer addAttachmentData:imageData mimeType:@"image/png" fileName:@"screenshot.png"];

        // Add the Logs
        [mailComposer addAttachmentData:[self generateLogData] mimeType:@"text/plain" fileName:@"AppMetricsandLogs.csv"]; 

        [self presentModalViewController:mailComposer animated:YES];
        [mailComposer release];

    }
    else
    {
        // Let the user know there's no email account setup.
    }  
}
Supporting Methods

The above code constructs a Mail Composer form and set’s it properties, including the body content (set as HTML). The screenshot is also added as an attachment to the email along with any Logs you might have collected (again as an attachment).

Creating the Screenshot

Something that has helped me in the past several times has been a capture of what the user was doing prior to tapping the feedback button.

In order to grab the screen we need to create an empty graphics context and iterate over ALL of the controls on the screen rendering each control’s layer to the graphics context. Once we have a copy of each controls layer we can turn the graphics context into a UIImage and return it for use inside the email.

Note: This code was written by (and copyright of) Apple as per here.

Steps
  1. Add an import for the QuartzCore framework to the top of your implementation (Add it in your Target’s Library if not included already):
    #import <QuartzCore/QuartzCore.h>
    (This allows the manipulation of layers amongst other functions).
  2. Next add a method (and it’s declaration in the header) to be called from the email generation method:
Code - generateScreenshot
- (UIImage*) generateScreenshot 
{
    // Create a graphics context with the target size
    // On iOS 4 and later, use UIGraphicsBeginImageContextWithOptions to take the scale into consideration
    // On iOS prior to 4, fall back to use UIGraphicsBeginImageContext
    CGSize imageSize = [[UIScreen mainScreen] bounds].size;
    if (NULL != UIGraphicsBeginImageContextWithOptions)
        UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0);
    else
        UIGraphicsBeginImageContext(imageSize);

    CGContextRef context = UIGraphicsGetCurrentContext();

    // Iterate over every window from back to front
    for (UIWindow *window in [[UIApplication sharedApplication] windows]) 
    {
        if (![window respondsToSelector:@selector(screen)] || 
             [window screen] == [UIScreen mainScreen])
        {
            // -renderInContext: renders in the coordinate space of the layer,
            // so we must first apply the layer's geometry to the graphics context
            CGContextSaveGState(context);
            // Center the context around the window's anchor point
            CGContextTranslateCTM(context, [window center].x, [window center].y);
            // Apply the window's transform about the anchor point
            CGContextConcatCTM(context, [window transform]);
            // Offset by the portion of the bounds left of and above the anchor point
            CGContextTranslateCTM(context,
                                  -[window bounds].size.width * [[window layer] anchorPoint].x,
                                  -[window bounds].size.height * [[window layer] anchorPoint].y);

            // Render the layer hierarchy to the current context
            [[window layer] renderInContext:context];

            // Restore the context
            CGContextRestoreGState(context);
        }
    }

    // Retrieve the screenshot image
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    return image;
}
Adding the Logs and Metrics

We need several methods to retrieve the logs and device metrics information. Firstly we need to gather our log entries from CoreData (or other source), write them to a file and add the file to the outgoing email as an attachment, then we need to create a string containing the metrics you want to gather from the device (these can be anything you like or is pertinent to your application).

Code - cacheDirectoryFilePath
- (NSString*) cacheDirectoryFilePath:(NSString*) documentFileName
{
    NSArray *arrayPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory,NSUserDomainMask,YES);
    NSString *docDir = [arrayPaths objectAtIndex:0];
    NSString *fileName = [docDir stringByAppendingString:[NSString stringWithFormat:@"/%@",documentFileName]];
    return fileName;

}
Code - generateConcatenatedLogString
- (NSString*) generateConcatenatedLogString
{
    NSMutableString* logString = [[NSMutableString alloc] init];
    [logString appendFormat:@"\"%@\",\"%@\",\"%@\",\"%@\",\"%@\"\n",
     @"Log Date",
     @"Log Level",
     @"Log Message",
     @"Log Thread",
     @"Log Method"];

    NSArray* zLogsFromCoreData = [ZLog MR_findAll]; // MagicalRecord helper method
    for(ZLog* zLog in zLogsFromCoreData)
    {
        [logString appendFormat:@"\"%@\",\"%@\",\"%@\",\"%@\",\"%@\"\n",
         zLog.logDate,
         zLog.level,
         zLog.message,
         zLog.thread,
         zLog.methodName];
    }

    return logString;
}
Code - generateMetricsString
// All pre-calculated strings are stored/defined in 'Constants.h/.m'
- (NSString*) generateMetricsString
{
    NSMutableString* metricsString = [[NSMutableString alloc] init];
    [metricsString appendFormat:@"Application Version:&nbsp;&nbsp;%@<br/>",[NSString stringWithFormat:@"%@ [%@]",[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"],[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"]] ];

    [metricsString appendFormat:@"OS Versions:&nbsp;&nbsp;%f / %f / %f<br/>",kCFCoreFoundationVersionNumber, kCFCoreFoundationVersionNumber_iOS_4_2,kCFCoreFoundationVersionNumber_iPhoneOS_5_0];
    [metricsString appendFormat:@"Screen Height Portrait:&nbsp;&nbsp;%f<br/>",SCREEN_HEIGHT_PORTRAIT];
    [metricsString appendFormat:@"Screen Height Landscape:&nbsp;&nbsp;%f<br/>",SCREEN_HEIGHT_LANDSCAPE];
    [metricsString appendFormat:@"Screen Scale:&nbsp;&nbsp;%f<br/>",SCREEN_SCALE];
    [metricsString appendFormat:@"Screen Width Portrait:&nbsp;&nbsp;%f<br/>",SCREEN_WIDTH_PORTRAIT];
    [metricsString appendFormat:@"Screen Width Landscape:&nbsp;&nbsp;%f<br/>",SCREEN_WIDTH_LANDSCAPE];
    [metricsString appendFormat:@"System Name:&nbsp;&nbsp;%@<br/>",SYSTEM_NAME];
    [metricsString appendFormat:@"System Version:&nbsp;&nbsp;%i<br/>",SYSTEM_VERSION];
    [metricsString appendFormat:@"Device Model Name:&nbsp;&nbsp;%@<br/>",DEVICE_MODEL];
    [metricsString appendFormat:@"Device Name:&nbsp;&nbsp;%@<br/>",DEVICE_NAME];
    [metricsString appendFormat:@"Device Orientation:&nbsp;&nbsp;%i<br/>",DEVICE_ORIENTATION];
    [metricsString appendFormat:@"Device Type:&nbsp;&nbsp;%@<br/>",DEVICE_TYPE];
    [metricsString appendFormat:@"iPad:&nbsp;&nbsp;%i<br/>",IS_IPAD];
    [metricsString appendFormat:@"iPhone:&nbsp;&nbsp;%i<br/>",IS_IPHONE];

    return metricsString;
}
Code - generateLogData
- (NSData*) generateLogData
{
    NSString* logString = [self generateConcatenatedLogString];

    [logString writeToFile:[self cacheDirectoryFilePath:@"AppMetricsandLogs.csv"] atomically:YES encoding:NSUTF8StringEncoding error:nil];

    NSData* attachmentData = [NSData dataWithContentsOfFile:[self cacheDirectoryFilePath:@"AppMetricsandLogs.csv"]];

    return attachmentData;
}

These methods work together to supply the data for the ‘generateFeedbackEmail’ method and present the user with a single modal email window where all the user has to do is tap the ‘Send’ button.

Summary

In this article I discussed and documented several ideas and techniques for making your development life easier both during the development lifecycle and after once your application has been released to your users. I also explained a technique for ‘documenting’ your applications code when it is running on your users devices using a logging framework.

Dave Wraight

With over 15 years software engineering experience across many of the popular programming frameworks and platforms I’ve really come to enjoy the mobile application development space. In particular the design and development of iOS applications allows me the freedom to design from paper and create visually rich and highly interactive applications. As founder of Zero One Zero software (www.010.com.au / blog.010.com.au) and developer of several existing iOS/Web/Desktop applications I am available for hire both locally and for remote work.

My Apps

  1. Landing Soon, LandingSoonApp.com
  2. reFuelr (WA/Australian residents only), reFuelrApp.com

Favourite iOS applications

  1. TouchDocs, from KabukiVision / AppStore
  2. Dooo, from Figtree Labs
  3. Flipboard, from Flipboard
17May/120

Core Data / Magical Record presentation

here's a presentation I recently gave on using Magical Record and Core Data.

If anyone would like the sample project I can upload those also.

Data Access Presentation

Filed under: iOS No Comments
3Apr/121

OTA: Distribution of iOS Apps without iTunes (Pt1)

I recently had to revisit my process for distributing applications over the air and since the process has changed slightly since Xcode v3 it seemed like a good idea to document it publicly in the event anyone else gets some benefit from it.

The big change from XCode 3 is you no longer need to fart with the entitlements.plist file and it's associated keys (get-task-allow, application-identifier etc).  XCode automatically set's these during the build process.

I'm assuming you have your Public/Private Certificates setup already, including the WWDR certificates provided by Apple.  If not refer the to documentation in the Portal and use KeyChain to make that happen.

There are basically 4 key parts to providing a version of your application for easy install/distribution via a web server:

  1. Setup the Provisioning Profiles
  2. Configure the Schemes and Targets
  3. Creating the Archive
  4. Configuring the Website

 Setup the Provisioning Profiles

In order for your application to be installed on an end users device you need to gather their UDID and add it to the correct provisioning profile.  There are lots of ways of doing this, but my preferred way is to ask the user to connect their device and run iTunes.

iTunes provides a hidden mechanism for displaying the UDID.  Attach the device and select the Summary tab for the selected device, then click on the Serial number field, which should change to display 'Identifier'.  Have your users select and send this number to you.

Once you have the UDID's  open your developer Portal and select Devices (top left).  Here you can either paste the UDID's in one at a time or import them from a file.  Select either 'Add Device' or 'Upload Devices' and enter your user's UDID's.

 

Once complete this will make those devices 'selectable' when you are creating/editing the provisioning profile.

The next step is to create an Application ID.  This ties the Profile and Applications you create for those devices in the profile together.  This is done by selecting 'App IDs' from the menu on the left and choosing 'New App ID'.

Give the App ID a very descriptive name so you can remember how it's connected to your applications.

Leave the Seed as 'Team ID' and use the suggested method of reverse domain name for the suffix (the part of the application identifier after the randomly generated characters).

I prefer to use WildCards for my Bundle ID's as I find it cleaner in XCode, especially if I'm managing multiple products and versions; but each to their own.   To set a wildcard App ID, simply put an asterisk at the end of the bundle identifier.

You should end up with something like 'XXXXXXXXX.com.companyname.*'.

Provisioning Profile

From here we need to select Provisioning and then 'Distribution' from the top row of tabs (the default is  'Developer').  Select an existing Distribution profile or click 'New Profile'.

Select 'Ad Hoc' as the distribution method - this is a very important step.  Now give the profile a name.   I prefer to be very explicit when naming my profiles to make it really obvious in XCode and during the Archive process which profile I'm using.  I prefer names such as 'Ad-Hoc Distribution Profile for Application XXXXXX' or 'AdHoc Distribution Profile for Test Group 1 on version 1.0.5'.

This process is now connecting the distribution certificate with the devices for specific application(s).  

Once the profile is named you need to configure the profile to allow an application(s) to use it.   At this point we are connecting the bundle ID (App ID) with the profile with the certificate with the devices in the profile.

Now select the devices that you want your release to be made available to.    Click the Submit button.

You will notice that the page will show 'Pending' for this profile.  This process generally takes about 5 seconds to generate,  I normally wait the 5 seconds and click the 'Distribution' link at the top again.  Once completed the provisioning profile will show a 'Download' link.

Click to download the Provisioning profile.

Now we need to get this into profile into XCode.  Open XCode and select the Organizer from the Window menu (CMD+SHIFT+2).   Pick the 'Devices' section and select from the top left the 'Provisioning Profiles' link.

 

Navigate back to finder and open the folder that you downloaded the profile into.  Select and drag the provisioning profile from the finder window into the Provisioning Profiles section in the Organizer.

Xcode will attempt to validate the profile to ensure you are allowed to use it and that it came from your dev account etc, which means you will most likely be prompted for you Apple Dev Account ID and it's password.  Plug those credentials in and let it do the validation.

Once validated the provisioning profile should show a green tick.  If it doesnt the most likely culprit will be your Private Key isn't setup correctly.

So far we have created our distribution provisioning profile, set the devices we want it to be made applicable to, specified the all important Bundle Identifier, downloaded and installed the profile into Xcode.  We are now ready to setup Xcode to make use of our newly installed friend.

Configuring the Schemes and Targets

Coming from the land of .Net and having a very simplistic build configuration I was initially confused by the terms Scheme, Target and Configuration.  It wasn't until I used a third party library that leveraged all three did I realise how powerful XCode really is (truth is I HAD to learn).

If I get my explanation wrong please forgive me,  I'm learning too, but in my head it seems right, so there :)

Scheme

Think of the Scheme as the parent container for the Target and Configuration.  The scheme controls what is built and what type of stuff is built depending on the action you take in Xcode.  There are built in Schemes (corresponding to Xcode actions) for Build, Run, Test, Profile, Analyze and Archive.  Each scheme points to a specific configuration inside the Target that is being built (for example Build, Run and Test are all using the Debug Configuration).

 

Target

I imagine this to be the same as saying 'I'm going to build this part of the app' or 'I'm going to build just the static library' or 'I want to build the entire application but include the debug symbols'.  Basically the target is the grouping of settings and compiler directives for the output type you select.

Configuration

Each Target can have multiple configurations.  By default you are given 'Release' and 'Debug', but you can clone these or create them from scratch to suit your needs.   Remeber that each scheme can point to a specific configuration inside the Target so when you select the Scheme you are basically saying 'For whichever Target I pick to build, use these bunch of settings'.

So why is this important?  It's important because you need to setup and create all three of them in order to get the correct output.

 

Stay tuned for Part Two:  Creating the Archive...

 

3Apr/120

Seriously check out Mojito!

I'm a big fan of ExtJS and have followed YUI for a very long time (since before ExtJS leveraged it), the whole concept behind Mojito is akin to a completely pluggable architecture that makes use of the best parts of Node.js and integrates (using the MVC pattern) with YUI 3.

Check it out even just for a look se:

http://developer.yahoo.com/cocktails/mojito/docs/intro/mojito_quicktour.html

Filed under: Uncategorized No Comments
16Jan/120

Universal App – One Price, Two Code bases – Merging nightmare

I'm working on the iPhone release of Landing Soon and I've been struggling with how to release two apps from a single (currently) universal project.  I started the app with the intention of selling a single binary with both apps packaged inside it; however I came across a few issues with this approach.

The problem comes about when you upload a single package to iTunes Connect with both binaries;

  1. I can't set separate prices for each binary (iPhone/iPad)
  2. It's not clear that when a sale is made of the universal that it's because they wanted it on the phone or for the iPad
  3. There's no way to release different versions separately, if you update the iPad code it automatically updates the codebase for the iPhone.

The first reason I would wont two products is simple - the iPad is my primary market and hence I'll be devoting significantly more resources on this platform (mostly because of screen size affords me the ability to create a much more simplified UI) and therefore I feel I can warrant a slightly higher price tag.

There are simply some things a larger screen size lets you do that the iPhones' screen cannot - the Landing Soon dashboards are a perfect example.

On the second point regarding "who's downloading what" - if I knew that people are buying the iPhone app over the iPad version then I'd have to rethink my allocation of effort.

Until this afternoon when someone suggested I use two build targets and restructure my build process I wouldn't have even thought it possible to get two separate products from the same code base - so that's what I'll be doing over the next week...

The iPhone version will most likely be a whole dollar cheaper than the iPad version but will contain most (80%) of the feature set.

14Jan/120

Landing Soon has Landed!

This morning our app Landing Soon for iPad went live and is ready for sale.

Stay tuned for many exciting new features in the coming months - including an iPhone version.

As usual we LOVE feedback.

12Jan/120

Landing Soon hopefully Landing Soon

My latest app is an iPad application that captures project status information for activities, software releases or projects that that you are working on.

I had originally planned a much larger feature set for the first release but after reading two Agile/Scrum articles I decided to stick with the philosophy 'release early, release often'.

As much as I like WordPress (and I do) I ditched my previous installation and hand coded the Landing Soon website - www.landingsoonapp.com