Cheap Gas! 3.01 Released

December 1, 2009

Cheap Gas! Version 3.01 has now been approved for sale in the App Store.  You can get it here.

This is a very minor update to correct a timestamp reporting issue.

Underline Text on the iPhone

November 26, 2009

One of the least talked about feature deficiencies of Apple iPhone SDK Font Handling is the inability to underline text.

You know – like you see in every web link on every web page on the internet.

Yeah.  That.

For me, where I need this the most is when I’m trying to create a link on one of my text sections or display elements, that will invoke a web URI and go off to do some work.

There isn’t a way to style underline fonts included with the iPhone, and Apple doesn’t make it easy at all to include custom fonts without a great deal of pain and gnashing of teeth.

What is one to do?

Well, I don’t know what the rest of you guys do, but this is what I did.

I created a button class (UnderlineButton) that subclasses UIButton and implemented my own drawRect function to draw the underline myself.

This isn’t rocket science, but works quite nicely.  The button should probably have a number of init functions to do things like set underline stroke width and color, but I needed a quick and dirty solution and had about 30 minutes to write, test, and deliver.

The code below is what I wound up with (UnderlineButton.m follows; UnderlineButton.h is simply a stub class that contains only @interface UnderlineButton : UIButton {}).


//
//  UnderlineButton.m
//
//  Created by David Hinson on 11/24/09.
//  Copyright 2009 Sumner Systems Management, Inc.. All rights reserved.
//

#import "UnderlineButton.h"
@implementation UnderlineButton

- (id)initWithFrame:(CGRect)frame {
  if (self = [super initWithFrame:frame]) {
    // Initialization code
    }
  return self;
}
- (void)drawRect:(CGRect)rect {
  [super drawRect:rect];
  CGContextRef context = UIGraphicsGetCurrentContext();

  CGContextSetRGBStrokeColor(context, 62.0/255.0, 62.0/255.0, 62.0/255.0, 1.0);

  // Draw them with a 1.0 stroke width.
  CGContextSetLineWidth(context, 1.0);

  // Draw a single line from left to right
  CGContextMoveToPoint(context, 0, rect.size.height);
  CGContextAddLineToPoint(context, rect.size.width, rect.size.height);
  CGContextStrokePath(context);
}

- (void)dealloc {
  [super dealloc];
}

@end

To use the button in an actual application, you would do something like the following:

  CGSize constraintSize, offset1;
  constraintSize.width  = 300.0f;
  constraintSize.height = MAXFLOAT;

  NSString * btnText = @"My Button Text";

  UnderlineButton * myButton = [[UnderlineButton buttonWithType:UIButtonTypeCustom] retain];
  offset1                    = [btnText sizeWithFont:[UIFont systemFontOfSize:16]
                                constrainedToSize:constraintSize
                                lineBreakMode:UILineBreakModeTailTruncation];
  myButton.frame = CGRectMake(20, 164, offset1.width, offset1.height);
  [myButton setTitle:btnText forState:UIControlStateNormal];

  [myButton setTitleColor:[UIColor darkTextColor] forState:UIControlStateNormal];
  [myButton setFont:[UIFont systemFontOfSize:16]];
  [myButton addTarget:self action:@selector(doButtonTouch:)
            forControlEvents:UIControlEventTouchUpInside];

  [cell myButton];
  [myButton release];

The doButtonTouch method will perform whatever it is you want to do; in my case this invoking another method to slide in a UIWebView to show drill down content.

Summary: Underline Text. Nothing there to make it natively easy, but workable solutions can be cobbled together.

But I will be the very first to say “it shouldn’t be this hard.

Well.

I finally got “bit” by the current Apple mania for ferreting out private API calls in App Store iPhone Applications.

You’d think with all my video game experience that I’d be more prepared for this (props to Toy Story 2).

Anywho, these are the two lines of code that got my submission smacked out of the park:

[[UIApplication sharedApplication] terminate];

and

[NSHost currentHost];

Doesn’t look like much, does it?

Still, verboten all the same.

In the case of the terminate function call, all I’m trying to do gracefully end the application in the event that network connectivity is not available.  There is a simple workaround here, and that is to instead use the C Language exit(0) call.  Easy-peazy, lemon-sqeazy.

The second “offending” call, [NSHost currentHost], is simply a call to get the iPhone’s current IP address.  The workaround here is to do something else, like this.

In any event, both of these changes were minor.

But frustrating.

Was I in the “wrong” for using them?  In the view of Apple, absolutely.  And in view of what I agreed to as an iPhone Developer publishing apps on the App Store, again, absolutely.

But, believe it or not, I don’t memorize every API call and know right off the top of my head whether it’s official or not.  Shocking, I know.

And, since the app that was being submitted has been in the App Store for over a year (as have these forbidden calls), it wasn’t like I was trying to sneak in some neato feature available only to Apple by using private calls.

Long story longer, I can easily correct these transgressions – and have done so – in about 15 minutes time.

The bad news is that the app sat in the approval queue for ten (10) days just to be rejected.  An app that has passed numerous times before.  And now must be resubmitted and waited upon.  Again.

Another seven-to-fourteen visit to the purgatory of App Store Approval.

If you think you’re gonna sneak some hidden feature in, reconsider.

Unless, of course, you have all the time in the world to resubmit offending apps.

Or more specifically, posting photos to tweetPhoto.com… but the method works for posting any image from an iPhone to any site that can accept POSTed images.

You can find the API documentation for tweetPhoto here.

Since using NSURLConnection and NSMutableRequest are covered numerous places elsewhere, I’m not going to describe much about how to make asynchronous HTTP calls.  The code below pretty much speaks for itself.

The interesting parts of this function (sendPhoto:tweet:photo:tags:longitude:latitude) are the portions involved in composing the HTTP form data to go over the wire.  Again, the code is fairly self explanatory.

The inbound (NSData*)photo should be the binary image of a picture that you will be sending to tweetPhoto (or whatever service you’re posting images to).  Please note that you will need to Change the Content-Type to match whatever images you’ll be sending (in my case, I always send .PNG images).

So, without further ado, here’s the deal-ee-oh:

-(void)sendPhoto:(NSString*)message tweet:(BOOL)tweet photo:(NSData*)photo tags:(NSString*)tags longitude:(float)longitude latitude:(float)latitude {
TweetPhotoAppDelegate* myApp = (TweetPhotoAppDelegate*)[[UIApplication sharedApplication] delegate];

NSString *url;
if (tweet) {
url = [NSString stringWithFormat:@"http://www.tweetphoto.com/uploadandpostapiwithkey.php"];
} else {
url = [NSString stringWithFormat:@"http://www.tweetphoto.com/uploadapiwithkey.php"];
}

NSString * boundary = @"tweetPhotoBoundaryParm";
NSMutableData *postData = [NSMutableData dataWithCapacity:[photo length] + 1024];

NSString * userNameString = [NSString stringWithFormat:@"Content-Disposition: form-data; name=\"username\"\r\n\r\n%@", myApp.loginString];
NSString * passwordString = [NSString stringWithFormat:@"Content-Disposition: form-data; name=\"password\"\r\n\r\n%@", myApp.passwordString];
NSString * apiString = [NSString stringWithFormat:@"Content-Disposition: form-data; name=\"api_key\"\r\n\r\n%@", apiKey];
NSString * messageString = [NSString stringWithFormat:@"Content-Disposition: form-data; name=\"message\"\r\n\r\n%@", message];
NSString * tagsString = [NSString stringWithFormat:@"Content-Disposition: form-data; name=\"tags\"\r\n\r\n%@", tags];
NSString * latString = [NSString stringWithFormat:@"Content-Disposition: form-data; name=\"latitude\"\r\n\r\n%f", latitude];
NSString * longString = [NSString stringWithFormat:@"Content-Disposition: form-data; name=\"longitude\"\r\n\r\n%f", longitude];
NSString * boundaryString = [NSString stringWithFormat:@"\r\n--%@\r\n", boundary];
NSString * boundaryStringFinal = [NSString stringWithFormat:@"\r\n--%@--\r\n", boundary];

[postData appendData:[boundaryString dataUsingEncoding:NSUTF8StringEncoding]];
[postData appendData:[userNameString dataUsingEncoding:NSUTF8StringEncoding]];
[postData appendData:[boundaryString dataUsingEncoding:NSUTF8StringEncoding]];
[postData appendData:[passwordString dataUsingEncoding:NSUTF8StringEncoding]];
[postData appendData:[boundaryString dataUsingEncoding:NSUTF8StringEncoding]];
[postData appendData:[apiString dataUsingEncoding:NSUTF8StringEncoding]];
[postData appendData:[boundaryString dataUsingEncoding:NSUTF8StringEncoding]];

if (message != nil && ![message isEqualToString:@""]) {
[postData appendData:[messageString dataUsingEncoding:NSUTF8StringEncoding]];
[postData appendData:[boundaryString dataUsingEncoding:NSUTF8StringEncoding]];
}

if (tags != nil && ![tags isEqualToString:@""]) {
[postData appendData:[tagsString dataUsingEncoding:NSUTF8StringEncoding]];
[postData appendData:[boundaryString dataUsingEncoding:NSUTF8StringEncoding]];
}

if (longitude && latitude) {
[postData appendData:[latString dataUsingEncoding:NSUTF8StringEncoding]];
[postData appendData:[boundaryString dataUsingEncoding:NSUTF8StringEncoding]];
[postData appendData:[longString dataUsingEncoding:NSUTF8StringEncoding]];
[postData appendData:[boundaryString dataUsingEncoding:NSUTF8StringEncoding]];
}

[postData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"media\";\r\nfilename=\"media.png\"\r\nContent-Type: image/png\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
[postData appendData:photo];
[postData appendData:[boundaryStringFinal dataUsingEncoding:NSUTF8StringEncoding]];

NSMutableRequest * theRequest=(NSMutableURLRequest*)[NSMutableURLRequest requestWithURL:[NSURL URLWithString:url] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];

[theRequest setHTTPMethod:@"POST"];

[theRequest addValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary] forHTTPHeaderField:@"Content-Type"];
[theRequest addValue:@"www.tweetphoto.com" forHTTPHeaderField:@"Host"];
NSString * dataLength = [NSString stringWithFormat:@"%d", [postData length]];
[theRequest addValue:dataLength forHTTPHeaderField:@"Content-Length"];
[theRequest setHTTPBody:(NSData*)postData];

NSURLConnection * theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self];

[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
if (theConnection) {
receivedData=[[NSMutableData data] retain];
}
else {
[myApp addTextToLog:@"Could not connect to the network" withCaption:@"tweetPhoto"];
}
}

One of the more commonly asked questions about iPhone SDK development is “how do you send attachments in Email using the iPhone SDK?”

The answer is: you can’t.  At least, not yet.

Well, then how are developers seemingly able to do this?  Many of you have seen apps where this looks like this is being done.  The Apple Photos app, for example, seems to be able to do this.

What’s the secret?

Come closer.  You Ready?

Embedded Images.

Embedded images are most commonly seen in your junk email and are a favorite trick used by spammers to circumvent your email filters to slip content in that can’t be scanned textually.

Here, we will use them for good and not evil.  Promise.

Let’s look at some screen grabs and code from my upcoming iPhone App Interpolate.  Interpolate is a numerical analysis app that finds missing range points given a table of data points representing some function, like y=x squared.  In addition to calculating interpolants, it also produces cartesian graphs of each function set, and allows you to send the chart to your friends using email.

img_0012

The code below is from a function call to create my email message.  In my app, I am taking a UIView (or at least, a class derived from UIView) and converting it into a PNG image.  Once I convert the view to PNG, I then convert the image data to a Base64 string, and use that Base64 string as the source for my embedded image in the email.


ChartView *cv = (ChartView *)cvc.view;


UIGraphicsBeginImageContext(cv.bounds.size);
[cv.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();


NSData *imageData = UIImagePNGRepresentation(viewImage);
char encodeArray[64 * 1024];
memset(encodeArray,'', sizeof(encodeArray));

// Exercise for the reader - encode takes the image data buffer and encodes it to base64.

// I can't do everything for you - that spoils the fun.
encode([imageData length], (char *)[imageData bytes], sizeof(encodeArray), encodeArray);


NSString *dataStr = [NSString stringWithCString:encodeArray length:strlen(encodeArray)];


// Save to photo library... maybe another time!
// UIImageWriteToSavedPhotosAlbum(viewImage, self, nil, nil);


NSString *body       = [@"" stringByAppendingFormat:@"<b><img src='data:image/png;base64,%@' alt='Interpolate Chart'></b>", dataStr];
NSString *encoded    = [body stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSString *title      = [[@"" stringByAppendingFormat:@"Interpolate: Function %@", cvc.title] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSString * urlString = [@"" stringByAppendingFormat:@"mailto:me@you.com?subject=%@&body=%@", title, encoded];
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlString]];

The really interesting bits are:

  • Converting the UIView to a UIImage using UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
  • Converting the UIImage to NSData using NSData *imageData = UIImagePNGRepresentation(viewImage);
  • Convert the NSData to Base64
  • Converting the Base64 encoded image data into an NSString using NSString *dataStr = [NSString stringWithCString:encodeArray length:strlen(encodeArray)];
  • Embedding the image into email using NSString *body       = [@"" stringByAppendingFormat:@"<b><img src='data:image/png;base64,%@' alt='Interpolate Chart'></b>", dataStr];

IMPORTANT: the <b> and </b> in the embedded image code above must be present, or mail will strip your image.  Don’t ask me why – it just does.

Wait a minute… where is the Base64 encoding mumbo-jumbo done?

Hey – like my old math teacher used to say – the rest is left as an exercise for the reader.

But if you’ve followed along this far, finding a Base64 C routine to use in your own code is the easy part.

Happy Coding.

Thank you, Etan Horowitz, for the kinds words and exposure on his Orlando Sentinel Tech Blog this morning for my upcoming “Cheap Gas!” iPhone app.

One can never have too many friends or enough link love.