i’m so full of ideas

Archive for the 'iphone' category

UIImage rotation, part 3

July 21, 2012 8:22 am

UIImage rotate test program

My two earlier entries on UIImage rotation are by far the most popular blog posts I’ve ever written. I’m trying to get back into the blogging habit, so here I am, exercising my weak technical writing muscles. And I’ve decided to host my code at GitHub, like all the cool kids are doing these days.

I’ve revisited my UIImage rotation code, cleaned it up a little bit, written a test program that shows various rotation effects, and put the whole thing up on GitHub.

That’s all I’ve got for now. Further bulletins as events warrant.

Installing Xcode 3 and 4 side by side on Lion

March 19, 2012 10:59 am

Xcode 4 sucks so, so, bad. It kills my productivity like whoa. As a consequence, I’ve been doing most of my work in Xcode 3 on Snow Leopard, venturing into Xcode 4 only for testing and final releases. (I am currently working on a code library that’s used by other iOS programmers, most of whom will be using Xcode 4.)

I know I’ll have to update to Xcode 4, eventually. I can’t hold back time forever. But the longer I wait, the more likely it is that Apple will fix all the glaring issues. Until then, I’m going to cling to Xcode 3 for dear life.

Recently, Apple made things a little more difficult for me by releasing a new version of Xcode 4 that only works on Lion. So I had to update my operating system, finally. But I found a way to install both Xcode 3 and Xcode 4 side by side, without any real problems.

My new Lion setup works better than Snow Leopard did in one significant respect: Xcode 3 is now able to install and debug programs on devices running iOS 5.0 and 5.1, which didn’t work for me before. I’m assuming it’s using device debugging stuff that Xcode 4 installed, but I’m not sure. I’m just glad it works, because I use it a lot.

Here’s the steps I took, which might work for you as well.

Step 0: Remove all existing Xcode versions

I’m assuming you are starting from a fresh install of Lion, which doesn’t contain any developer tools at all. If not, the instructions given here probably won’t work. The order of installation is important.

This command should work to uninstall Xcode 3:

sudo /Developer/Library/uninstall-devtools –mode=all

And this one will uninstall Xcode 4:

sudo /Library/Developer/Shared/uninstall-devtools –mode=all

… then remove Xcode.app from your /Applications folder.

Step 1: Install Xcode 3.2.6

As of this writing, the most recent version of Xcode 3 is still available from Apple’s developer site. Download Xcode 3.2.6, which will require you to log in with your Apple dev credentials.

Alas, Xcode 3 doesn’t really want to be installed on Lion. No matter, its version checks can be defeated. I found out how to make it work from this link. I’ll repeat the instructions here, in case that link goes dead:

1. Mount the Xcode 3.2.6 DMG
2. Open Terminal
3. Enter these commands:

export COMMAND_LINE_INSTALL=1
open "/Volumes/Xcode and iOS SDK/Xcode and iOS SDK.mpkg"

… then run the installation program as usual.

Partway through, the Xcode 3 installer demanded that I shut down iTunes, even though it wasn’t running. On a hunch, I used Activity Monitor to kill iTunes Helper, and that did indeed make it shut up and finish installing.

If you want to create a link to Xcode 3, the app is installed here by default:

/Developer/Applications/Xcode.app

Drag it from that location into the Dock or wherever else you’d like it to be.

After Xcode 3 is installed, you’ll want to launch it to make sure it really works. On my system, Lion declared that it needed to download a Java runtime before this was possible.

Step 2: Install Xcode 4.3.1

Apple has decided to move Xcode 4 into the Mac App Store, so you’ll have to run the App Store app to download it. It doesn’t have an installer like earlier Xcode versions, it’s just a plain old app in your /Applications folder. Start it, and it will declare that it has to install a framework first, so let it do that.

At this point, you’ve got a cosmetic problem: the Xcode 3 and Xcode 4 icons are identical. You can solve that problem by installing Jeff LaMarche’s replacement icon. He wrote that article for a much older version of Xcode 4, but the replacement worked just fine for me.

Step 3: Install the command line tools

Xcode 4 no longer installs command-line tools by default. Assuming you need them, like I do, then you should open the Xcode 4 preferences window, go to the Downloads tab, look for the item labeled “Command Line Tools,” and press the Install button. This will take just a few minutes, and the tools will be installed, inside the Xcode 4 application bundle.

The next problem you’ll discover is that, if you fire up a terminal window and type a command such as xcodebuild, you’ll get the Xcode 3 command line tools by default. That is not what I want. So the next step is to modify your .profile file, or whatever you normally use to control the PATH environment variable, and add these paths near the beginning:

/Applications/Xcode.app/Contents/Developer/Tools
/Applications/Xcode.app/Contents/Developer/usr/bin

Step 4: Device debugging

If you’re like me, you have a pile of devices you use for testing and debugging. Both Xcode 4 and Xcode 3 are able to install and debug apps on all my devices, which are running versions of iOS from 3.1 to 5.1.

The trick is to start on Xcode 4. Connect your device to the 30-pin connector, open Xcode 4, and look in the Organizer window. If this is the first time you’ve connected this device, it will ask you if you want to use it for debugging, and it may have to download some files to make that possible. Eventually, the little LED next to the device should turn green. After that, exit Xcode 4, open Xcode 3, and try it there. It looks like Xcode 3 copies some files from wherever Xcode 4 stashed them, and then it’s off to the races.

Aftermath

I’ve been using this setup for a number of months now. What I’ve discovered is that Xcode 3 is unfortunately pretty crippled when run on Lion. Debugging via GDB is impossible. Source code windows often have bogus titles applied. Various other cosmetic bugs abound. In short, Xcode 3 is not nearly as pleasant to use as it was on Snow Leopard. But it’s still better than Xcode 4, for the most part, in my opinion. So I soldier on.

And … fin

As of this writing, it is now a few months later, and I use Xcode 4 all the time. I still don’t like it, but there are way too many things that Xcode 3 can’t do anymore, and switching back and forth all the time is giving me a headache.

I did save the Xcode 3 versions of Pixie and Property List Editor, however. You can download a more recent version of Pixie from Apple’s developer site, but it’s not as good as the old one, and the icon is uglier. With Xcode 4, you’re expected to edit property lists within the IDE itself, but that’s often not very convenient. If you’re going to follow my lead, be aware that Property List Editor won’t work without PlistEdit.framework, which is installed in your /Developer folder along with all the other Xcode 3 stuff.

And with that, I am officially giving up the fight. You win, Apple. I sure wish Steve J. had been forced to use Xcode as much as I do. I bet it wouldn’t have turned out like this.

UIButton graphics highlighting

January 31, 2011 6:54 am

I have often tried to create a UIButton subclass to do something special, like draw a shadow, but couldn’t make it work. You can’t just override UIView’s drawRect: method and get the results you expect. My usual response to that is: Fine, I’ll make my button a subclass of UIControl, then.

That leaves the problem of how to highlight the button’s image when it is pressed. UIButton does a great job of this, drawing a black mask on top of the button’s image, ignoring any transparent pixels. I’ve been trying to figure out how to reproduce that effect for probably a year, I suspect, without success.

CoreGraphics is powerful. There’s pretty much nothing it can’t do. But it’s not exactly easy to work with. I have finally read enough of other people’s blog posts so that I can cobble together a solution. I’ve included a function below that inputs a UIImage object and outputs a new UIImage with a black mask drawn over it, exactly the same effect that UIButton uses.

UIButton highlighting

I’ve included a screen-shot from the simulator to show what the effect looks like. On the left, you’ll see an image that is similar to the icon for the Calendar program on the iPhone. Next is that same image, highlighted by UIButton. The third image is highlighted with the function shown below. The two highlighted versions are almost exactly the same. You can tell the difference with a color-dropper tool, but I doubt you could tell them apart with the naked eye.

I know I pretty much never use code from other people’s blog posts without modifying it to suit my own uses. Assuming you’re like that as well, here’s some notes that will help you understand the code better.

The alpha value for the black mask, 0.46, was chosen because it produces results nearly identical to what UIButton does. You can make that number bigger or smaller for a lighter or darker mask.

UIGraphicsBeginImageContextWithOptions() is the best function to use to begin an image context. It will get you high-resolution graphics on the iPhone 4’s “retina display.” But that function does not exist on older versions of iOS. Therefore, we test for its existence before calling it, by checking to see if its function pointer is NULL. If so, we fall back to the older method that’s been available since the earliest iOS versions. If you don’t plan to support versions of iOS earlier than 4.0, then you can omit the availability test.

There are a couple of lines of code that transform the coordinates normally used by CoreGraphics functions to those normally used by UIKit objects. Without those two lines, the black mask would be drawn upside-down. I don’t understand this business very well. I just know the function fails without them.

The secret sauce is CGContextClipToMask(). It was easy to figure out how to draw a colored mask over an existing image, but you need this extra step to prevent the mask from being drawn over transparent pixels.

UIImage* WBHighlightImage(UIImage* image) {     const CGSize  size = image.size;     const CGRect  bnds = CGRectMake(0.0, 0.0, size.width, size.height);     UIColor*      colr = nil;     UIImage*      copy = nil;     CGContextRef  ctxt = NULL;         // this is the mask color     colr = [[[UIColor alloc] initWithWhite:0 alpha:0.46] autorelease];     // begin image context     if (UIGraphicsBeginImageContextWithOptions == NULL)     {         UIGraphicsBeginImageContext(bnds.size);     }     else     {         UIGraphicsBeginImageContextWithOptions(bnds.size, FALSE, 0.0);     }     ctxt = UIGraphicsGetCurrentContext();         // transform CG* coords to UI* coords     CGContextTranslateCTM(ctxt, 0.0, bnds.size.height);     CGContextScaleCTM(ctxt, 1.0, -1.0);     // draw original image     CGContextDrawImage(ctxt, bnds, image.CGImage);         // draw highlight overlay     CGContextClipToMask(ctxt, bnds, image.CGImage);     CGContextSetFillColorWithColor(ctxt, colr.CGColor);     CGContextFillRect(ctxt, bnds);     // finish image context     copy = UIGraphicsGetImageFromCurrentImageContext();     UIGraphicsEndImageContext();         return copy; }

iPhone: nibless

February 28, 2010 5:07 pm

I just had a look, and my blog is the number one hit on google for the search phrase “iphone nibless.” That leads to a blog post I wrote on the subject last year. I linked to another blog that explained how to accomplish such a goal, but that blog post has since been taken down. Hrmph. Okay, I guess I have to cover this subject again.

Generally speaking, Apple makes beautiful apps that are elegant and easy to use. Interface Builder is a glaring exception. I can’t stand that thing. So unbelievably complicated. I can’t ever find what I’m looking for.

If you’re writing Mac apps, I guess you’re stuck with it. Most Mac windows contain a lot of controls, and you need a way to design them. If you’re writing iPhone apps, it’s a net loss. Most iPhone views contain a single control that takes up the entire view area, so Interface Builder is an unnecessary complication. So I’m going to tell you how to make an iPhone app that does not require any nibs at all. I am using Xcode 3.2.1 on Snow Leopard, but these instructions will likely work for older versions as well.

1) Start Xcode. From the File menu, pick “New Project.” In the window that opens, select the iPhone OS Application category on the left. In the group on the right, pick “Window-based Application.” This may work fine for the other project templates, but I haven’t tested that. Create the new project, name it whatever you want.

2) Remove MainWindow.xib from the project and put it into the trash. It is no longer needed. (Yay!)

3) Edit the Info.plist for your project by double-clicking on it. It will have a name similar to projectname-Info.plist. The plist file will have a key named “Main nib file base name,” with the value MainWindow. Remove this key completely: select it, and press the Delete key. Save the file.

4) Xcode will have created an app delegate class for your project, named something like niblessAppDelegate. Yuck, what a horrible name. Rename this class to AppController, by directly editing the header and source files for the class. You can of course pick a different name, or even skip this step altogether, but I’m going to assume from here on out that your app delegate class is called AppController.

5) At this point, you have destroyed the mechanism that iPhone OS normally uses to recognize the name of your app delegate class, which it needs to know to load your app. Fortunately, there is an easy way around this: you must pass your app delegate’s class name to UIApplicationMain(). Xcode created a file called main.m that contains your app’s main() function. Edit it now, and change main() so that it looks like this:

int main(int argc, char *argv[]) {     NSAutoreleasePool*  pool = [[NSAutoreleasePool alloc] init];         int retVal = UIApplicationMain(argc, argv, nil, @"AppController");         [pool release];     pool = nil;         return retVal; }

That’s it, you’re done. You may continue modifying this project until you have a real application.

At this point, you might be thinking: Whoa, scary change. Is this really safe? Well, I can offer myself up as an example. I’ve submitted two of my own apps to the App Store that use this technique, both of which have been downloaded thousands of times. I’ve gotten hundreds of emails from users over various issues, but my apps not having nibs has never been a problem.

UIProgressHUD replacement

February 27, 2010 11:22 pm

Dear internet: I have been searching for a UIProgressHUD replacement for many, many months. Why have you failed me? I don’t suppose it was because of that other, similarly named UIProgressHUD replacement? Which I am not going to link to or name, because it sucks. Sorry, yes I am a jerk, but it does. If I have to explain to you why it’s a bad idea to make a progress view that’s launching background tasks, then there is no helping you.

UIProgressHUD

So, anyway. In case you’re new to this. UIKit has a nice control called UIProgressHUD for displaying a heads-up, semi-transparent view with a spinny-control on it, and a single line of text. It looks good, it works well, it’s easy. The only down side is that it’s undocumented, so you can’t use it in apps destined for the App Store.

I used UIProgressHUD in a project I was writing for a client a few months ago. This particular app was not destined for the App Store, so it seemed like a nice shortcut. Maybe that’s not such a hot idea, but they weren’t paying much, so I couldn’t justify a detour for writing a brand-new view. But now here I am again, needing the exact same thing for my card game, and google searches still only turn up that progress view that thinks it’s an app launcher. Hrmph. Time to write my own, I guess.

WBProgressHUD

My goal was to write an exact, drop-in replacement for that view I can’t use. I’ve included screenshots of Apple’s original view, and my clone view. Pretty darn close, don’t you think? The only real differences are details that I don’t want to change. For example, I think they picked a font size that’s a bit excessive.

It would be great if I could just paste the source file and header file right into this post, for your amusement. My view is mercifully short, and would lend itself to that. Alas, it requires several support modules, so I had to make it into an example Xcode project. I’ve developed a huge library of iPhone support code by this point, so it doesn’t make sense to duplicate things in every single view and controller I write.

One clever feature of my demo project: you can change one line of code and it will use either UIProgressHUD or my own WBProgressHUD. No other code has to change, because the two views are that similar, dawg. It makes for a good test bed for developing a clone view such as this one.

Download wbprogresshud.zip by clicking here. If you like this project, how about hiring me to write more stuff like it? My contact details are on my About page.

iPhone and iPad SDK coexistence

February 14, 2010 7:14 am

Apple has released a new SDK specifically for iPad. It’s not yet ready for prime-time, so if you want to continue to develop for iPhone, you’ll need to use both the old and new SDKs for awhile. Curious about the logistics of this endeavor, I googled up this article. I dutifully followed the instructions and installed the beta 1 release in a separate dev directory. Yeah, I’m calling shenanigans. It did not work. I think it’s a bit irresponsible to post something like that without having tried it.

You can’t completely separate the two SDKs. According to the release notes, the one that is installed last is the one whose compilers will be used for both. I had other problems as well. Both the old and new device simulators were crashy. So I completely wiped both sets of dev tools and reinstalled the iPhone SDK.

Recently, Apple released beta 2 of the iPad SDK. They may have fixed the problems I experienced before, but I’m not taking any more chances. This time, I got myself an external Firewire hard drive. USB 2.0 would also work, but I prefer Firewire for a possibly frivolous reason: I have so many USB devices that it’s difficult to find a place to plug in a new one, but I have two Firewire ports on the back of my Cinema Display that are otherwise unused.

This next part is important. You’re going to need to boot off this drive, so its partition map scheme must be set to “GUID Partition Table.” If it isn’t, you should reinitialize it so that it is. On the Mac, you use the Disk Utility program for this. It doesn’t create GUID partition tables by default, so you have to press the button that says “Options…” and change it.

Next, use a program like Carbon Copy Cloner to make a copy of your primary hard drive onto the external drive. Finally, in System Preferences, pick “Startup Disk,” select your external hard drive, and reboot. Now install the new iPad SDK. It will be copied onto your external disk, leaving your primary disk alone. This way, the two SDKs won’t butt heads with each other.

It’s a shame that Apple is making us solve a software problem with hardware, but this is the sort of inconvenience you have to put up with if you want to live on the bleeding edge. I can’t even remember the last time I had to use the Startup Disk pref pane before this. It’s been years, surely. This reminds me of when I used to work at Be, and we had a new version of BeOS to install every couple of weeks.

iPhone cert FUD

February 12, 2010 9:24 am

I finally got around to installing Snow Leopard on my MacBook Pro. I don’t believe in putting a new operating system on top of an old one, so I started over from scratch, on a brand-new hard drive. Which explains why I waited so long. Installing everything from scratch is torture.

A couple of days ago, I could no longer postpone reinstalling all the dev certs and crud you need to make iPhone apps that you can put on real iPhone hardware. As I was installling all that stuff, I encountered this interesting text from Apple’s developer website, concerning development certificates:

It is critical that you save your private key somewhere safe in the event that you need to develop on multiple computers or decide to reinstall your system OS. Without your private key, you will be unable to sign binaries in Xcode and test your application on any Apple device. When a CSR is generated, the Keychain Access application creates a private key on your login keychain. This private key is tied to your user account and cannot be reproduced if lost due to an OS reinstall. If you plan to do development and testing on multiple systems, you will need to import your private key onto all of the systems you’ll be doing work on.

Well! From the sound of that, you would be forgiven for thinking that, if you don’t back up your private key, you’ll be completely dead in the water, never to write another iPhone app again. That’s certainly what I thought when I was setting this stuff up the first time, about a year ago. So I did just what they told me to, and I backed it all up. I was reading somebody else’s blog recently that claimed the text quoted above was literally true. Which is why I’m writing today.

Actually, no. If you don’t back up that stuff, you won’t be dead in the water. I found my cert backups from a year ago, and tried to use them, but it was just too painful. Nothing about Apple’s iPhone code signing process makes much sense. When you get stuck, it’s best to just blow everything away and start over.

Which is just what I did! I removed everything from my keychain that had anything to do with iPhone development, I threw away all my provisioning profiles, and I started over from scratch. It went a lot easier this time. I think I’ve finally been doing this long enough to understand a few of the concepts.

From the sound of it, I think the only time you’d really need to follow Apple’s backup device is if you plan to use two or more Macs for iPhone development simultaneously. I’ve never needed to do that, so that’s one less thing to worry about.

iPhone: UIImage rotation and scaling

January 31, 2010 8:03 pm

Hello to my three remaining blog subscribers! Long time no see!

For my first post back after my long hiatus, I’m going to revisit the single most popular entry I’ve written to date, which was about UIImage rotation. I’ve used that code a great deal since I first wrote it. I’ve modified it several times. It’s pretty near perfect at this point.

The original code I modified for that earlier post was for dealing with photos taken with the iPhone camera. I stripped out a lot of that stuff, because I was only interested in rotating images that were embedded in the program. And then, wouldn’t you know it, I got a contract job that required me to deal with iPhone camera images as well. So I had to revisit the subject.

The original code from blog.logichigh.com had the rotation and scaling all lumped together in one chaotic function. I split it out into separate rotation and scaling methods, so they can be used independently. It also makes for easier code maintenance.

The code contains [UIImage rotate:], which works the same as the last time around, but it has been streamlined a bit internally. It will rotate any UIImage to any orientation, with or without mirroring. Then we have two scaling methods, the simplest being [UIImage scaleWithMaxSize:]. You provide it with a float value, which is the largest width and/or height that you want the output image to have. If the input image is already smaller than that, it won’t be scaled.

Finally, the all-singing, all-dancing method for massaging photos from the iPhone camera: [UIImage rotateAndScaleFromCameraWithMaxSize:]. It examines the EXIF data in the image and, if needed, rotates it to the proper orientation. Then it scales the image to the maximum width and/or height supplied to the method.

This code has been thoroughly tested in several real-world iPhone projects. There are no known bugs. If you find one, I’d love to hear about it, so I can fix it.

Here’s the header file, WBImage.h:

// WBImage.h -- extra UIImage methods // by allen brunson  march 29 2009 #ifndef WBIMAGE_H #define WBIMAGE_H #import <UIKit/UIKit.h> @interface UIImage (WBImage) // rotate UIImage to any angle -(UIImage*)rotate:(UIImageOrientation)orient; // rotate and scale image from iphone camera -(UIImage*)rotateAndScaleFromCameraWithMaxSize:(CGFloat)maxSize; // scale this image to a given maximum width and height -(UIImage*)scaleWithMaxSize:(CGFloat)maxSize; -(UIImage*)scaleWithMaxSize:(CGFloat)maxSize  quality:(CGInterpolationQuality)quality; @end #endif  // WBIMAGE_H

And here’s the implementation file, WBImage.mm:

// WBImage.mm -- extra UIImage methods // by allen brunson  march 29 2009 #include "WBImage.h" static inline CGFloat degreesToRadians(CGFloat degrees) {     return M_PI * (degrees / 180.0); } static inline CGSize swapWidthAndHeight(CGSize size) {     CGFloat  swap = size.width;     size.width  = size.height;     size.height = swap;     return size; } @implementation UIImage (WBImage) // rotate an image to any 90-degree orientation, with or without mirroring. // original code by kevin lohman, heavily modified by yours truly. // http://blog.logichigh.com/2008/06/05/uiimage-fix/ -(UIImage*)rotate:(UIImageOrientation)orient {     CGRect             bnds = CGRectZero;     UIImage*           copy = nil;     CGContextRef       ctxt = nil;     CGRect             rect = CGRectZero;     CGAffineTransform  tran = CGAffineTransformIdentity;     bnds.size = self.size;     rect.size = self.size;     switch (orient)     {         case UIImageOrientationUp:         return self;         case UIImageOrientationUpMirrored:         tran = CGAffineTransformMakeTranslation(rect.size.width, 0.0);         tran = CGAffineTransformScale(tran, -1.0, 1.0);         break;         case UIImageOrientationDown:         tran = CGAffineTransformMakeTranslation(rect.size.width,          rect.size.height);         tran = CGAffineTransformRotate(tran, degreesToRadians(180.0));         break;         case UIImageOrientationDownMirrored:         tran = CGAffineTransformMakeTranslation(0.0, rect.size.height);         tran = CGAffineTransformScale(tran, 1.0, -1.0);         break;         case UIImageOrientationLeft:         bnds.size = swapWidthAndHeight(bnds.size);         tran = CGAffineTransformMakeTranslation(0.0, rect.size.width);         tran = CGAffineTransformRotate(tran, degreesToRadians(-90.0));         break;         case UIImageOrientationLeftMirrored:         bnds.size = swapWidthAndHeight(bnds.size);         tran = CGAffineTransformMakeTranslation(rect.size.height,          rect.size.width);         tran = CGAffineTransformScale(tran, -1.0, 1.0);         tran = CGAffineTransformRotate(tran, degreesToRadians(-90.0));         break;         case UIImageOrientationRight:         bnds.size = swapWidthAndHeight(bnds.size);         tran = CGAffineTransformMakeTranslation(rect.size.height, 0.0);         tran = CGAffineTransformRotate(tran, degreesToRadians(90.0));         break;         case UIImageOrientationRightMirrored:         bnds.size = swapWidthAndHeight(bnds.size);         tran = CGAffineTransformMakeScale(-1.0, 1.0);         tran = CGAffineTransformRotate(tran, degreesToRadians(90.0));         break;         default:         // orientation value supplied is invalid         assert(false);         return nil;     }     UIGraphicsBeginImageContext(bnds.size);     ctxt = UIGraphicsGetCurrentContext();     switch (orient)     {         case UIImageOrientationLeft:         case UIImageOrientationLeftMirrored:         case UIImageOrientationRight:         case UIImageOrientationRightMirrored:         CGContextScaleCTM(ctxt, -1.0, 1.0);         CGContextTranslateCTM(ctxt, -rect.size.height, 0.0);         break;         default:         CGContextScaleCTM(ctxt, 1.0, -1.0);         CGContextTranslateCTM(ctxt, 0.0, -rect.size.height);         break;     }     CGContextConcatCTM(ctxt, tran);     CGContextDrawImage(ctxt, rect, self.CGImage);     copy = UIGraphicsGetImageFromCurrentImageContext();     UIGraphicsEndImageContext();     return copy; } -(UIImage*)rotateAndScaleFromCameraWithMaxSize:(CGFloat)maxSize {     UIImage*  imag = self;     imag = [imag rotate:imag.imageOrientation];     imag = [imag scaleWithMaxSize:maxSize];     return imag; } -(UIImage*)scaleWithMaxSize:(CGFloat)maxSize {     return [self scaleWithMaxSize:maxSize quality:kCGInterpolationHigh]; } -(UIImage*)scaleWithMaxSize:(CGFloat)maxSize  quality:(CGInterpolationQuality)quality {     CGRect        bnds = CGRectZero;     UIImage*      copy = nil;     CGContextRef  ctxt = nil;     CGRect        orig = CGRectZero;     CGFloat       rtio = 0.0;     CGFloat       scal = 1.0;     bnds.size = self.size;     orig.size = self.size;     rtio = orig.size.width / orig.size.height;     if ((orig.size.width <= maxSize) && (orig.size.height <= maxSize))     {         return self;     }     if (rtio > 1.0)     {         bnds.size.width  = maxSize;         bnds.size.height = maxSize / rtio;     }     else     {         bnds.size.width  = maxSize * rtio;         bnds.size.height = maxSize;     }     UIGraphicsBeginImageContext(bnds.size);     ctxt = UIGraphicsGetCurrentContext();     scal = bnds.size.width / orig.size.width;     CGContextSetInterpolationQuality(ctxt, quality);     CGContextScaleCTM(ctxt, scal, -scal);     CGContextTranslateCTM(ctxt, 0.0, -orig.size.height);     CGContextDrawImage(ctxt, orig, self.CGImage);     copy = UIGraphicsGetImageFromCurrentImageContext();     UIGraphicsEndImageContext();     return copy; } @end

iPhone: keyboard trouble

August 28, 2009 3:07 am
It is possible to type into two or more of these fields at once without dismissing the keyboard

It is possible to type into two or more of these fields without dismissing the keyboard

A couple of months ago, a user of my card game reported a bug. He said that when he tried to change the names of all the robot players at once, only one of them actually changed. I tried to duplicate the problem, but couldn’t. For me, all three robot player names changed. I asked him for more details, but like many non-technical users, he wasn’t able to articulate the problem very well. He said he kept fiddling and fiddling and eventually got all three names changed. I had made a real effort to duplicate the bug and failed. So I wrote this one off as user error.

Over the next few weeks, the same bug got reported three more times. I was still not able to reproduce it. I asked for more specific instructions on how to tickle the bug, but none of this batch of people took the time to send a second email. I can’t blame them. Most people view it as a frivolous time-wasting diversion, useful for whiling away a few minutes while in line at the grocery or something.

Fortunately for me, the fifth report came from a fellow programmer. He provided very explicit instructions, and I was finally able to reproduce it. Now that I understand what was happening, it’s no small wonder that it evaded me for so long. The problem was that my mental model of how the iPhone accepts text input was wrong.

I had assumed the sequence of events was always like this:

• User touches an editable text field
• Keyboard rolls up into view
• User types some text
• User presses the return key on the keyboard
• Program forces the keyboard to disappear
• Program processes the new text

But guess what, here is another possible sequence of events:

• User touches an editable text input field
• Keyboard rolls up into view
• User types some text
User touches a second text input field
User edits the second input field
• User presses the return key on the keyboard
• Program forces the keyboard to disappear
• Program processes the new text

A third possible outcome is that the user navigates away from the current view to a different one, and therefore never presses the return key.

This sounds complicated, but it turns out that the fix is pretty easy. You simply have to treat “user pressed the return key” and “user finished editing” as two separate events, rather than conflating them as one.

The assumption here it that your users are typing into UITextField objects. The fix I’m proposing is for UITextField delegates, i.e., whatever object implements the UITextFieldDelegate protocol. Here’s an example implementation of one of those methods:

-(BOOL)textFieldShouldReturn:(UITextField*)textField {     [textField resignFirstResponder];     return TRUE; }

This is the delegate method that gets called when the return key is pressed. I used to process the newly-edited text field contents in here. That was a mistake which led to the bug I’m talking about. In a typical implementation of this method, the only thing you want to do is make the keyboard go away, which is what the [textField resignFirstResponder] line does.

Here’s the method you should implement to deal with new text field contents:

-(void)textFieldDidEndEditing:(UITextField*)textField {     // deal with new text field contents here }

I can’t show a typical implementation, because only you can decide what you should be doing with the new contents of the text field. But as far as I can tell, this method is always called when the user is finished editing the text field, regardless of why that happened. It could be because the return key was pressed, or because the user switched to a different text field, or because she switched away from the parent view altogether, or perhaps other reasons I’m not aware of.

app store progress

August 6, 2009 6:40 pm
Hearts Solo downloads

For the first month of its existence, my game was ticking along at about 200 downloads per day. I have no experience with this, but that seemed pretty good to me. Then I released 1.1.0, which is what caused that spike you see near the end there. On the first day: 192 new downloads, 949 upgrades to the new version. Second day: 576 new downloads, 1100 upgrades. It’s tapered off since then, but downloads are still significantly higher than they were before the new version came out.

According to the records I get from Apple, 6707 people downloaded version 1.0.0 of the game. So far, 3936 of those people have upgraded to the new version. Let’s hope you are all still so loyal when I get around to releasing the paid version. Heh!