Advertisements

Archive

Archive for the ‘iOS’ Category

PhoneGap Tutorial Series – #3 Extending the PhoneGap API

March 28, 2011 8 comments

 

Extending the PhoneGap API

 
If you’ve had a chance to play with PhoneGap a bit, chances are you have wanted it to do something that it doesn’t already do. Alas, don’t worry! You don’t have to put in a ticket and hope that the PhoneGap developers agree with you and implement it in some future release, if you can write a little Objective-C then you can do it yourself and use it in your own iOS project.

The topic at hand is all about extending the PhoneGap API whether it’s by editing existing PhoneGap classes, downloading a third-party plugin from somewhere, or by writing your own plugin from scratch. For this post I will concentrate on editing the PhoneGap classes, in later posts I’ll give step-by-step instructions on using a third-party plugin and how to create your own plugin.

If you haven’t already had a chance to read my earlier posts on PhoneGap internals and using the PhoneGap API – you may want to peruse them before reading on.
 

Adding Functionality to the PhoneGap Classes

 
Off the top of my head, one of the things that I want PhoneGap to do is to take a picture (which it already can) and save it in the photo library (which it doesn’t do). Who knows why this isn’t already in the API but it’s something that I would like. So I could just write all my own Objective-C to take the picture and save but I don’t really want to redo something that is there, I just want to add a little something more to it.

How to Save a Photo to the Library

So the first thing that I need to find out is how to actually save an image to the photo library. After googling around a bit I found that the following UIKit reference from Apple indicates that this method: UIImageWriteToSavedPhotosAlbum should accomplish what we want.

//Adds the specified image to the user’s Camera Roll album.
void UIImageWriteToSavedPhotosAlbum (
   UIImage  *image,
   id       completionTarget, //optional
   SEL      completionSelector, //optional
   void     *contextInfo //optional
);


How Does the Camera API Work?

Next we need to take a look under the hood at what the PhoneGap Camera API already does when we tell it to take a picture and find an appropriate place to inject our own code. Since PhoneGap is open source we can do this and make all the changes that we want on our own behalf – but keep in mind that if you upgrade you will have to make your changes again.

This is one of many reasons to write a new plugin instead of editing PhoneGap directly – the instructions for which will be in a future post.

In the Camera.h file from the PhoneGapLib project (version 0.9.4) we see that they define a CameraPicker interface that extends the UIImagePickerController and that the Camera implements the UIImagePickerControllerDelegate.

The delegate defines the imagePickerController:didFinishPickingMediaWithInfo method that is called when the UIImagePickerController has selected an image.

@interface CameraPicker : UIImagePickerController
//removed ....
@end
@interface Camera : PhoneGapCommand<UIImagePickerControllerDelegate, UINavigationControllerDelegate>
{
	CameraPicker* pickerController;
}

For those that are interested, the Camera Programming for iOS Guide from Apple explains in detail how to use the UIImagePicker API to interact with the camera and the photo library.

The following excerpt is from the Camera.m file from the PhoneGapLib project (version 0.9.4):

- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary*)info
{
	CameraPicker* cameraPicker = (CameraPicker*)picker;
	CGFloat quality = (double)cameraPicker.quality / 100.0; 
	[picker dismissModalViewControllerAnimated:YES];
	NSString* mediaType = [info objectForKey:UIImagePickerControllerMediaType];
	if ([mediaType isEqualToString:(NSString*)kUTTypeImage])
	{
		if (cameraPicker.successCallback) {
			
			NSString* jsString = NULL;
							// get the image
				UIImage* image = nil;
				if (cameraPicker.allowsEditing && [info objectForKey:UIImagePickerControllerEditedImage]){
					image = [info objectForKey:UIImagePickerControllerEditedImage];
				}else {
					image = [info objectForKey:UIImagePickerControllerOriginalImage];
				}

				NSData* data = UIImageJPEGRepresentation(image, quality);
				if (cameraPicker.returnType == DestinationTypeFileUri){
					// write to temp directory and reutrn URI
					// removed for brevity...
				}else{
					jsString = [NSString stringWithFormat:@"%@(\"%@\");", cameraPicker.successCallback, [data base64EncodedString]];
				}
			[webView stringByEvaluatingJavaScriptFromString:jsString];
		}
	}
}

The imagePickerController:didFinishPickingMediaWithInfo delegate method is doing a number of things:


  • line 6 – making sure that an image was selected
  • line 9 – making sure that a successCallback was defined
  • lines 13-18 – getting a reference to the selected image
  • lines 20-26 – writing the image to disk or defining an encoded string
  • line 27 – executing the JavaScript successCallback method

Where to Add Your Code?

The most appropriate place for us to inject our code to save the image is at line 19 just after we have gotten a reference to the image. The following code snippet checks to make sure that the image source was from the camera (not the library) and then saves the image into the photo library using the UIKit UIImageWriteToSavedPhotosAlbum method.

                //save the photo to the album
                if (cameraPicker.sourceType == UIImagePickerControllerSourceTypeCamera)
                {
                    UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
                }

Since taking a picture with the PhoneGap API requires that you be running on a device, you must build and deploy to your iPhone in order to test that our code works.

    The expected outcome is:
  1. Without the changes, the Camera API should allow you to take a picture BUT will not save it to the photo library.
  2. With the changes, the Camera API should allow you to take a picture AND will save it to the photo library. You should be able to open the camera roll and see your new pic.

That’s all for now — stay tuned for more upcoming posts on PhoneGap…

Advertisements

Xcode4 – Is your old project hanging in the simulator?

March 10, 2011 4 comments

 

So you took the plunge and upgraded to Xcode4…

 
Do your old projects build but won’t run on the simulator or even on a device? Well we spent a couple hours today trying to figure out why and after uninstalling/reinstalling, recreating projects, comparing settings, and every combination of simulator and SDK we finally stumbled on a solution.

So it seems I had some corrupt settings or project data that was preventing the app from running. By deleting all the xcuserdata associated with project it magically started to work again.

If you are having the same problem — deleting all the Xcode4 project data with your userid may get you up and running again…

    How to go about deleting the user-specific project data
  1. In terminal – navigate to your project directory
  2. cd into the ${PROJECT_NAME}.xcodeproj directory
  3. run this command: find . -name ‘*yourUserName*’
  4. rm -rf any files or dirs that come up
  5. Reopen Xcode4 – build and *hopefully* run your project

Hope it helps!

Xcode3 – Debugging iOS Unit Tests

March 8, 2011 4 comments

 

So you have some unit tests (yeah!) and they are failing …. now what?

 
Over the course of my career these past few years, I have become a developer that writes more test code than production code in an effort to never have to spend the wee hours of the morning debugging a horrible production issue. As a java developer turned mobile developer, there are several things that I miss about coding in Java but the number one thing is unit tests.

Don’t get me wrong – I know what you are thinking – probably something along the lines of: “Do a little research dummy! Xcode has a nice Unit Test Bundle target that we can use to run unit tests! All you have to do is write them!” Right…..

Or maybe after a little more googling around you may even point me to this excellent Apple documentation that explains the process of setting up and running my newly created unit tests but there is a huge section missing from this guide…

iOS Development Guide: Unit Testing Applications

So I have followed all these steps and everything is running fine and dandy until …I need to debug my tests. Where is the guide for this? What’s the point of having tests if I can’t debug them???

Having spent countless hours reading blog posts and attempting steps I have finally gotten it working with Xcode 3.2 AND iOS 4.2 as of today! Yes today! Not two years ago and not on some old version of Xcode and some weird version of the iOS SDK so to save you from having to experience this same misery keep on reading.
 

Have no fear! Instructions are finally here!

 
This guide assumes that you have the following knowledge, skills, and tools:

  1. Apple computer that has the Developer Tools installed and up to date.
  2. Working knowledge of Xcode 3.2, Xcode projects, Xcode build targets
  3. Working knowledge of Objective-C and iOS development

With the above, you should be able to follow these steps fairly easily so I won’t go into details on the typical tasks. If you need more information on how to use Xcode or setting up targets please see the following guide: Understanding Xcode Projects

Here we go…

    Create a new Xcode project

  1. Open Xcode 3 (instructions coming later for Xcode4)
  2. Create a new Xcode project (iOS window-based application template) — mine is called “MyNewApp”
  3. Build and Run the project for the simulator – if its “just working” – and it should – you should see a blank white screen in the simulator.

    Create a new target and run some tests

  1. Select “Targets” and add a new target (Unit Test Bundle template) — mine is called “MyNewAppTests”
  2. Expand the build phases of the new target and take note of the last step – “Run Script” – this is the step that runs your tests as part of the build.
  3. Add a new Objective-C Test class to the project, make sure you add it to the “MyNewAppTests” target.
  4. Within the new “MyNewAppTests.m” file add the following code:
    - (void) testFail {
    STFail(@"Must fail to succeed.");
    }
    
  5. Select the “MyNewAppTests” as the active target and run the build – it should fail

These steps are a shortened version of this guide: iOS Development Guide: Unit Testing Applications. If you need more detail to get your initial units tests building and running – please see the full documentation.
 

It’s failing … now its time to debug!

 
To debug your tests you need to set up an alternate method to run the tests – the basic “Run Script” in the typical test bundle target actually runs a “RunUnitTests.sh” which ultimately launches an instance of “otest” to run the tests — but its not debuggable and runs standalone.

In order to create your own executable to run “otest” and debug it – you will need to run the following command to find the right otest executable for your current iOS SDK.

Run this in the Terminal: find /Developer -name otest

When I run this I see a number of options — select the one from the latest iPhoneSimulator#.#.sdk — and take note of the path.

[hiedi:/Developer/Tools] % find /Developer -name otest
/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator3.2.sdk/Developer/usr/bin/otest
/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator4.0.sdk/Developer/usr/bin/otest
/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator4.1.sdk/Developer/usr/bin/otest
/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator4.2.sdk/Developer/usr/bin/otest
/Developer/Source/OCUnit/SourceCode/otest

 

    Create another new target and an executable

  1. Select “Targets” and add a new target (Unit Test Bundle template) — mine is called “MyNewAppTestsDebug”
  2. Expand the build phases of the new target — delete the last build phase — “Run Script”
  3. Right click and “Get Info” on the “MyNewAppTests.m” — add “MyNewAppTestsDebug” to the list of included targets.
  4. Select “Executables” and add a new custom executable with any name — mine is “Debuggable Otest” and fill in the path that you found above.
  5. On the arguments tab — add an argument that specifies the test bundle to use — mine is “MyNewAppTestsDebug.octest”.

On the arguments tab — also add the following environment variables:

Variable Value
DYLD_FRAMEWORK_PATH ${SDKROOT}/Developer/Library/Frameworks
DYLD_LIBRARY_PATH ${BUILD_PRODUCTS_DIR}:${DYLD_LIBRARY_PATH}
DYLD_NEW_LOCAL_SHARED_REGIONS YES
DYLD_NO_FIX_PREBINDING YES
DYLD_ROOT_PATH $SDKROOT
IPHONE_SIMULATOR_ROOT $SDKROOT

    Run the new target and executable

  1. Set the new target and the new executable as active and then Build/Run
  2. Open the console – you should see messages relating to the running of your tests and their failure.
  3. Now set a breakpoint in the “MyNewAppTests,m” file then Build/Debug
  4. In the console you should see messages about resolving your breakpoint and then it should stop in the debugger.

Note: These steps have been adapted from an original blog post by the author of Grokking Cocoa and can be found here: How to Debug iPhone Unit Tests

Activity Indicators

Today I spent a fair amount of time trying to write my own UIView that I could use to display a UIActivityIndicator and a message to a user when I am loading some things in the background. At first this didn’t seem all that hard and I had a working prototype in a matter of minutes using a standard nib and controller and I wired everything together. Then came the wrinkle — I need to show this new view over top of an existing view and have it paint on the main thread while I am downloading some content. Problem is that I tied up the main thread trying to do my download.

Once I finally figured out how to get my view to paint and then do the download I then found that I couldn’t make my view completely transparent — it was always black! Arrgh. So rather than spend anymore time trying to display a separate view I decided to change tactics and just add subviews onto my current view controller and I went searching around and found a GREAT project that is easy to use and saved me an afternoon of coding myself.

So if you are struggling to show a UIActivityIndicator in an elegant fashion then look to this site for some help: DS Activity View Project