So I heard that blocks were one of the new features of 4.1 and I decided to give it a try. And it’s awesome! Within half an hour I’d solved a problem that I’d had to make much more complicated implementations to solve previously, and I’m so excited I decided I’d share the code.
Problem: NSURLConnection asynchronous connections are ugly. If you want to have one controller make multiple different calls you have to make unique delegates for each call or have some convoluted way for the connection manager to tell one delegate from another. It’s a real mess.
Solution: BLOCKS!
Step 1: Create NSURLConnection block extension that allows for calling async methods from class API like you would sync method.
//
// NSURLConnection-block.h
//
// Created by Kevin Lohman on 9/12/10.
// Copyright 2010 Logic High Software. All rights reserved.
// Free to use in your code commercial or otherwise, as long as you leave this comment block in
// http://blog.logichigh.com/2010/09/12/cocoa-blocks/
#import
@interface NSURLConnection (block)
#pragma mark Class API Extensions
+ (void)asyncRequest:(NSURLRequest *)request success:(void(^)(NSData *,NSURLResponse *))successBlock_ failure:(void(^)(NSData *,NSError *))failureBlock_;
@end
and
#import "NSURLConnection-block.h"
@implementation NSURLConnection (block)
#pragma mark API
+ (void)asyncRequest:(NSURLRequest *)request success:(void(^)(NSData *,NSURLResponse *))successBlock_ failure:(void(^)(NSData *,NSError *))failureBlock_
{
[NSThread detachNewThreadSelector:@selector(backgroundSync:) toTarget:[NSURLConnection class]
withObject:[NSDictionary dictionaryWithObjectsAndKeys:
request,@"request",
successBlock_,@"success",
failureBlock_,@"failure",
nil]];
}
#pragma mark Private
+ (void)backgroundSync:(NSDictionary *)dictionary
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
void(^success)(NSData *,NSURLResponse *) = [dictionary objectForKey:@"success"];
void(^failure)(NSData *,NSError *) = [dictionary objectForKey:@"failure"];
NSURLRequest *request = [dictionary objectForKey:@"request"];
NSURLResponse *response = nil;
NSError *error = nil;
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if(error)
{
failure(data,error);
}
else
{
success(data,response);
}
[pool release];
}
@end
Now just import your new class and make your async call! AWESOME!
[NSURLConnection asyncRequest:request
success:^(NSData *data, NSURLResponse *response) {
NSLog(@"Success!");
}
failure:^(NSData *data, NSError *error) {
NSLog(@"Error! %@",[error localizedDescription]);
}];
If you aren’t interested in Objective C Cocoa development for the iPhone… read on.
The problems:
- UIImage provides no easy way to scale, rotate or transform the underlying CGImage. While it is easy to transform it by placing the image in a view, if you need the actual image changed then good luck.
- UIImageJPEGRepresentation(), the easiest (only?) way to convert a UIImage to a JPEG uses the underlying CGImage, and ignores your UIImage imageOrientation, so regardless of camera position when the picture was taken the exported JPEG will always be oriented in landscape or right mode, ending up with pictures that need rotation.
The solution: Thanks to some help in the Apple Support forums, and the #iphonedev IRC chat groups, I was able to clean up a function that takes a UIImage, and fixes it’s underlying core image.
Update: The “Forever” setting on Pastebin doesn’t seem to be so forever… but still lots of interest in this fix, so enjoy below:
UIImage *scaleAndRotateImage(UIImage *image)
{
int kMaxResolution = 320; // Or whatever
CGImageRef imgRef = image.CGImage;
CGFloat width = CGImageGetWidth(imgRef);
CGFloat height = CGImageGetHeight(imgRef);
CGAffineTransform transform = CGAffineTransformIdentity;
CGRect bounds = CGRectMake(0, 0, width, height);
if (width > kMaxResolution || height > kMaxResolution) {
CGFloat ratio = width/height;
if (ratio > 1) {
bounds.size.width = kMaxResolution;
bounds.size.height = bounds.size.width / ratio;
}
else {
bounds.size.height = kMaxResolution;
bounds.size.width = bounds.size.height * ratio;
}
}
CGFloat scaleRatio = bounds.size.width / width;
CGSize imageSize = CGSizeMake(CGImageGetWidth(imgRef), CGImageGetHeight(imgRef));
CGFloat boundHeight;
UIImageOrientation orient = image.imageOrientation;
switch(orient) {
case UIImageOrientationUp: //EXIF = 1
transform = CGAffineTransformIdentity;
break;
case UIImageOrientationUpMirrored: //EXIF = 2
transform = CGAffineTransformMakeTranslation(imageSize.width, 0.0);
transform = CGAffineTransformScale(transform, -1.0, 1.0);
break;
case UIImageOrientationDown: //EXIF = 3
transform = CGAffineTransformMakeTranslation(imageSize.width, imageSize.height);
transform = CGAffineTransformRotate(transform, M_PI);
break;
case UIImageOrientationDownMirrored: //EXIF = 4
transform = CGAffineTransformMakeTranslation(0.0, imageSize.height);
transform = CGAffineTransformScale(transform, 1.0, -1.0);
break;
case UIImageOrientationLeftMirrored: //EXIF = 5
boundHeight = bounds.size.height;
bounds.size.height = bounds.size.width;
bounds.size.width = boundHeight;
transform = CGAffineTransformMakeTranslation(imageSize.height, imageSize.width);
transform = CGAffineTransformScale(transform, -1.0, 1.0);
transform = CGAffineTransformRotate(transform, 3.0 * M_PI / 2.0);
break;
case UIImageOrientationLeft: //EXIF = 6
boundHeight = bounds.size.height;
bounds.size.height = bounds.size.width;
bounds.size.width = boundHeight;
transform = CGAffineTransformMakeTranslation(0.0, imageSize.width);
transform = CGAffineTransformRotate(transform, 3.0 * M_PI / 2.0);
break;
case UIImageOrientationRightMirrored: //EXIF = 7
boundHeight = bounds.size.height;
bounds.size.height = bounds.size.width;
bounds.size.width = boundHeight;
transform = CGAffineTransformMakeScale(-1.0, 1.0);
transform = CGAffineTransformRotate(transform, M_PI / 2.0);
break;
case UIImageOrientationRight: //EXIF = 8
boundHeight = bounds.size.height;
bounds.size.height = bounds.size.width;
bounds.size.width = boundHeight;
transform = CGAffineTransformMakeTranslation(imageSize.height, 0.0);
transform = CGAffineTransformRotate(transform, M_PI / 2.0);
break;
default:
[NSException raise:NSInternalInconsistencyException format:@"Invalid image orientation"];
}
UIGraphicsBeginImageContext(bounds.size);
CGContextRef context = UIGraphicsGetCurrentContext();
if (orient == UIImageOrientationRight || orient == UIImageOrientationLeft) {
CGContextScaleCTM(context, -scaleRatio, scaleRatio);
CGContextTranslateCTM(context, -height, 0);
}
else {
CGContextScaleCTM(context, scaleRatio, -scaleRatio);
CGContextTranslateCTM(context, 0, -height);
}
CGContextConcatCTM(context, transform);
CGContextDrawImage(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, width, height), imgRef);
UIImage *imageCopy = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return imageCopy;
}