Archive for the ‘iPhone’ Category.
May 15, 2013, 10:34 pm
My current iOS app gets images that I need to process. Basically, in my app, I get a UIImage with a white background and a CGRect, and I need to make the background of the image clear, automatically crop the image, scale image into the given CGRect preserving the aspect ratio, and then pad the scaled CGRect so that it appears centered inside of the original CGRect.
So in order to do this, I went through and found some UIImage categories to perform these tasks. Here is the first category that makes the white background into a clear background:
- (UIImage *)makeWhiteBackgroundTransparent
{
return [UIImage replaceColor:[UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0] inImage:self withTolerance:0.0];
}
+ (UIImage *)replaceColor:(UIColor *)color inImage:(UIImage *)image withTolerance:(float)tolerance
{
CGImageRef imageRef = [image CGImage];
NSUInteger width = CGImageGetWidth(imageRef);
NSUInteger height = CGImageGetHeight(imageRef);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
NSUInteger bytesPerPixel = 4;
NSUInteger bytesPerRow = bytesPerPixel * width;
NSUInteger bitsPerComponent = 8;
NSUInteger bitmapByteCount = bytesPerRow * height;
unsigned char *rawData = (unsigned char*) calloc(bitmapByteCount, sizeof(unsigned char));
CGContextRef context = CGBitmapContextCreate(rawData, width, height,
bitsPerComponent, bytesPerRow, colorSpace,
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
CGColorRef cgColor = [color CGColor];
const CGFloat *components = CGColorGetComponents(cgColor);
float r = components[0];
float g = components[1];
float b = components[2];
//float a = components[3]; // not needed
r = r * 255.0;
g = g * 255.0;
b = b * 255.0;
const float redRange[2] = {
MAX(r - (tolerance / 2.0), 0.0),
MIN(r + (tolerance / 2.0), 255.0)
};
const float greenRange[2] = {
MAX(g - (tolerance / 2.0), 0.0),
MIN(g + (tolerance / 2.0), 255.0)
};
const float blueRange[2] = {
MAX(b - (tolerance / 2.0), 0.0),
MIN(b + (tolerance / 2.0), 255.0)
};
int byteIndex = 0;
while (byteIndex < bitmapByteCount)
{
unsigned char red = rawData[byteIndex];
unsigned char green = rawData[byteIndex + 1];
unsigned char blue = rawData[byteIndex + 2];
if (((red >= redRange[0]) && (red <= redRange[1])) &&
((green >= greenRange[0]) && (green <= greenRange[1])) &&
((blue >= blueRange[0]) && (blue <= blueRange[1])))
{
// make the pixel transparent
//
rawData[byteIndex] = 0;
rawData[byteIndex + 1] = 0;
rawData[byteIndex + 2] = 0;
rawData[byteIndex + 3] = 0;
}
byteIndex += 4;
}
CGImageRef imgRef = CGBitmapContextCreateImage(context);
UIImage *result = [UIImage imageWithCGImage:imgRef];
CGImageRelease(imgRef);
CGContextRelease(context);
free(rawData);
return result;
} |
I would attribute this code, but I can’t find where I found this particular replaceColor method. If anyone knows where this code originally came from, please let me know and I will make the necessary attribution.
BTW, Happy Anniversary to McDonalds, who opened their first restaurant in San Bernardino, California on this date back in 1940.
April 18, 2013, 1:20 pm
When you are looking at unfamiliar iOS Objective-C code (either someone else’s or your own), it can be tricky to figure out where code can be hiding out within a project.
Well no more. I found this interesting bit of method swizzling from Michael Armstrong that will show you which UIViewController subclass in on the screen at any time.
MADebugTools
I went into this code and added a #define that I am setting in my .pch file, so that I can turn off the view controller labels if I do not need to see them.
As an iOS aside, if you are an iOS developer, I would recommend following Romain Briche either on Twitter or on his web site. I have found quite a few useful controls, code samples, and other hints and tutorials as a result of Romain’s postings.
BTW, Happy Birthday to Melody Thomas Scott, who I thought was fantastic in The Car, one of my all time favorite movies. (Oh, and she is on The Young and The Restless, too.)
April 15, 2013, 6:20 pm
I found this interesting category of NSObject from Simon Strandgaard that walks through your instance’s class variables and builds up a description string, which you can then output to the console. Here is the link to the category on Github:
https://github.com/neoneye/autodescribe
BTW, Happy Birthday to Lois Chiles, the who played Dr. Goodhead in the James Bond movie Moonraker.
March 27, 2013, 11:15 pm
Well I had a nice little issue with Xcode. Whenever I would try to do something in Organizer that had to sign into my Apple developer account (refresh provisioning profiles, add a device to a provisioning profile, etc.), it would ask for my credentials, chug for a moment, and then slip out the back door like it was late for a date with a supermodel.
Luckily, today I got frustrated with this issue and did some digging. I found a post on Apple’s forums about getting rid of some Library files created and used by Xcode, and after I removed the two files in question, lo and behold it started to be non-crashy again. Bonus.
The files were in the folder “~/Library/Developer/Xcode/” and begin with “connect1.apple.com”. Apple’s post says to move the files, but I just blew them away and it seemed to work fine. Here is the post on the Apple web site: (you may need to log in with your Apple developer account to see this posting)
Xcode 4.6.1 crashing while interacting with the Developer Portal
BTW, Happy Birthday to Routzy! We had our one year birthday party for Routzy tonight at Vito’s Wine Bar in Delaware, Ohio. Thanks to all who turned out, and wait until you see what we have in store for Routzy in year 2.
Post script: Happy Belated 40th Birthday to The Young and The Restless.
March 14, 2013, 2:22 pm
In keeping with my attempt to use more regular expressions in .NET, I figured it would be a good exercise to try and use NSRegularExpression to do some checking in my iOS app.
I have a situation where an array of strings come in, and I need to know which of them are UUIDs and which are not. Here is the code that I wrote to accomplish the UUID checking, it is implemented as a category of NSString:
#define UUID_PATTERN @"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
- (BOOL)isUUID
{
NSRegularExpression *regex;
regex = [NSRegularExpression regularExpressionWithPattern:UUID_PATTERN
options:NSRegularExpressionCaseInsensitive
error:nil];
int matches = [regex numberOfMatchesInString:self options:0
range:NSMakeRange(0, [self length])];
return (matches == 1);
} |
To use this NSString category, you would just do something like this:
NSLog(@"Is %@ a UUID? %@", theTestString, ([theTestString isUUID] ? @"YES" : @"NO")); |
BTW, as far as I am concerned, Google laid a couple of eggs the last two days. Firstly, yesterday (Wednesday, March 13) I tried for almost an hour to get Google I/O tickets, to no avail. I was hoping to attend as my trip to I/O last year was interrupted by my father passing away. Then, this morning, news breaks that Google is killing off Google Reader. My opinion of Google has gone down a couple of notches.
Double Bonus BTW: Happy Pi Day everyone! See you at Stir Trek!!! (Yes, I got tickets for that one. Whew…)
February 14, 2013, 8:45 pm
For my third and final AFNetworking migration series of posts, let us consider calling a web service URL with POST variables.
Here was the ASIHTTPRequest code:
NSMutableString *fileURL = [NSMutableString stringWithString:THE_URL];
NSURL *url = [NSURL URLWithString:fileURL];
__block ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setDelegate:self];
[request addRequestHeader:@"Content-Type" value:@"application/json; charset=utf-8"];
[request setRequestMethod:@"POST"];
[request setPostValue:theID forKey:@"id"];
[request setCompletionBlock:^{
NSLog(@"success with response string %@", request.responseString);
}];
[request setFailedBlock:^{
NSLog(@"error: %@", request.error.localizedDescription);
}];
[request startAsynchronous]; |
And here is the corresponding AFNetworking code:
NSMutableString *fileURL = [NSMutableString stringWithString:THE_URL];
AFHTTPClient *client = [[[AFHTTPClient alloc] initWithBaseURL:url] autorelease];
NSDictionary *params = @ { @"id" : theID };
NSMutableURLRequest *request = [client requestWithMethod:@"POST" path:fileURL parameters:params];
AFHTTPRequestOperation *op = [[[AFHTTPRequestOperation alloc] initWithRequest:request] autorelease];
[op setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(@"success with response string %@", operation.responseString);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"error: %@", error.localizedDescription);
}];
[op start]; |
BTW, Happy Valentine’s Day to one and all.
February 12, 2013, 9:01 am
For part 2 of my AFNetworking migration experiences, let’s cover uploading a file to a web service.
Here is the original code:
NSString *ws = [NSString stringWithFormat:@"http://myurl.com?id=%@", theID];
NSURL *url = [NSURL URLWithString:ws];
NSData *postData = [[[NSData alloc] initWithContentsOfFile:filePath] autorelease];
ASIFormDataRequest *request;
request = [[[ASIFormDataRequest alloc] initWithURL:url] autorelease];
[request setPostValue:pictureFileName forKey:@"file"];
[request setData:postData withFileName:pictureFileName andContentType:@"image/png" forKey:@"file"];
[request setRequestMethod:@"POST"];
[request setShouldAttemptPersistentConnection:YES];
[request setUploadProgressDelegate:progressView];
[request setCompletionBlock:^{
NSLog(@"success with response string %@", request.responseString);
}];
[request setFailedBlock:^{
NSLog(@"error: %@", request.error.localizedDescription);
}];
[request startAsynchronous]; |
And here is the AFNetworking code:
NSString *ws = [NSString stringWithFormat:@"http://myurl.com?id=%@", theID];
NSURL *url = [NSURL URLWithString:ws];
NSData *postData = [[[NSData alloc] initWithContentsOfFile:filePath] autorelease];
NSDictionary *sendDictionary = [NSDictionary dictionaryWithObject:postData forKey:@"file"];
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
NSMutableURLRequest *afRequest = [httpClient multipartFormRequestWithMethod:@"POST"
path:@""
parameters:sendDictionary
constructingBodyWithBlock:^(id < AFMultipartFormData > formData)
{
[formData appendPartWithFileData:postData
name:pictureFileName
fileName:pictureFileName
mimeType:@"image/png"];
}
];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:afRequest];
[operation setUploadProgressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) {
if (totalBytesExpectedToWrite == 0)
{
progressView.progress = 0.0;
}
else
{
progressView.progress = totalBytesWritten * 1.0 / totalBytesExpectedToWrite;
}
}];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSLog(@"success with response string %@", operation.responseString);
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
NSLog(@"error: %@", error.localizedDescription);
}];
[operation start]; |
The AFNetworking code is somewhat longer, so that is a bit of a problem. If anyone has any suggestions on how to make the AFNetworking code a bit more concise, please let me know.
BTW, Happy Birthday to Joe Don Baker, a distinguished actor with such outstanding films to his credit such as Mitchell, Final Justice, Fletch, and of course the all-time classic, Joysticks.
February 9, 2013, 8:26 pm
After using the ASIHTTPRequest library for a long time (so sad that Ben Copsey decided to abandon it), I finally decided to give in and switch to AFNetworking by Mattt Thompson and Scott Raymond. It has taken some trial and error to get it working correctly, so here is what I have found so far.
Conversion #1: Simple call to a URL
For this conversion, I am just making a call to a web service URL. I don’t really care about the return value, I just care if the call went through or whether there was an error in communications with the web service.
The ASI code:
NSString *urlString = [NSString stringWithFormat:@"http://myurl.com?id=%@", theID];
NSURL *url = [NSURL URLWithString:urlString];
__block ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request setCompletionBlock:^{
NSLog(@"success");
}];
[request setFailedBlock:^{
NSError *error = [request error];
NSLog(@"error: %@", error.localizedDescription);
}];
[request startAsynchronous]; |
The AFNetworking code:
NSString *urlString = [NSString stringWithFormat:@"http://myurl.com?id=%@", theID];
NSURL *url = [NSURL URLWithString:urlString];
AFHTTPRequestOperation *op = [[AFHTTPRequestOperation alloc] initWithRequest:[NSURLRequest requestWithURL:url]];
[op setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSLog(@"success");
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
NSLog(@"error: %@", error.localizedDescription);
}];
[op start]; |
Yeah, admittedly this one was not very hard to convert. Stay tuned, other more complicated conversions are coming.
BTW, Happy Birthday to Jim J. Bullock, who excellently portrayed Prince Valium in Spaceballs.
January 16, 2013, 3:41 pm
Back in the days when Google actually ruled Maps on the iOS platform (5.x and older), you had to push a URL through UIApplication’s sharedApplication object, and the system would see the Google maps URL and intercept it, sending the data to Maps instead of Safari.
Well this kind of works in iOS 6 as well, substituting maps.apple.com for maps.google.com. But alas, there is a better way.
Here is the code that I am now using to launch Maps, and it works in both iOS 6 and iOS 5.
- (void)launchMaps:(NSString *)serviceAddress
{
NSString *currentLocation = @"Current+Location";
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"6.0"))
{
// use MapKit to handle launching Maps app instead of pushing a URL
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressString:serviceAddress completionHandler:^(NSArray *placemarks, NSError *error) {
if (error)
{
NSLog(@"Geocode failed with error: %@", error.localizedDescription);
return;
}
if (placemarks && placemarks.count > 0)
{
CLPlacemark *pl = placemarks[0];
CLLocation *loc = pl.location;
NSLog(@"Geocoded latitude: %f; longitude: %f", loc.coordinate.latitude, loc.coordinate.longitude);
MKPlacemark *placemark = [[MKPlacemark alloc] initWithCoordinate:loc.coordinate addressDictionary:nil];
MKMapItem *mapItem = [[MKMapItem alloc] initWithPlacemark:placemark];
mapItem.name = @"Whatever name you want goes here";
mapItem.phoneNumber = @"And if you have a phone number, it would go here";
NSArray *mapItemArray = @[ mapItem ];
NSDictionary *launchOpt = @{ MKLaunchOptionsDirectionsModeKey: MKLaunchOptionsDirectionsModeDriving };
[MKMapItem openMapsWithItems:mapItemArray launchOptions:launchOpt];
[mapItem release];
[placemark release];
}
}];
[geocoder release];
return;
}
NSString *urlString = [NSString stringWithFormat:@"http://maps.google.com/maps?saddr=%@&daddr=%@", currentLocation, serviceAddress];
NSString *escaped = [urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:escaped]];
} |
And here are the defines for the OS checking macros that I use. (Yes, I know you should check for specific features instead of checking OS version numbers.)
#define SYSTEM_VERSION_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedSame)
#define SYSTEM_VERSION_GREATER_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedDescending)
#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
#define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
#define SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedDescending) |
The address string passed in would just be the components of your address separated by spaces (such as “1060 W Addison St Chicago IL”), and the Maps app should open up and give you the opportunity to start navigation to the destination.
BTW, Happy Birthday to Frank Zamboni, who is now forever immortalized with a Google Doodle.
December 31, 2012, 11:06 pm
Just a quick year end post for my loyal readers…
When you are looking to make performance improvements on your iOS app, no matter how well you think you know the code, fire up Instruments and run your code on a real device. Before the holiday break, I was not going to do this because I was certain where the code was slowing down, but on a lark I decided to profile it and needless to say it was a real eye opener. I saved myself a bundle of time by not chasing down phantom slow downs, made the code run super super fast, and got the girl in the end. (OK, maybe not that last one.)
Happy and prosperous New Year to one and all.