Some favorite site feeds aggregated locally: iPhone Development RSS   Adobe Labs RSS   Macrumors RSS

Tuesday, July 21, 2009

Tags in Objective-C are like instances in AS3. Not really.

Tuesday, July 21, 2009    3 Comments

I have come to love tag properties in Objective-C, tagging UI elements for identification later, etc. I suppose I do this because I have been a Flash designer and developer for so long it's just burned into a part of my brain that can't be healed. No problem.

Tags in Objective-C are integers however.

I have witnessed the big push for Obj-C to be very verbose in regards to property names, method arguments, argument names, etc. I mean, it borders ridiculous at times, but I get the whole legibility part of it. Autocomplete in Xcode is pretty sweet, so it can usually keep up without pissing me off too much.

Over time I'm sure to like it even more. In AS, we are told to keep the names understandable but as short as possible to speed up lookups. Cool. I digress.

In regards to overall code legibility, instance names in Flash beat the pants off an Obj-C element's integer tag property. What is more descriptive: Button_0 or a tag value of 0? I'd say the former. You could always set up hash tables, and ultimately perhaps that's the right way to go. I am a little curious why the tag - perhaps it's a speed issue ultimately to avoid a String lookup.

Anyway, they are indeed useful and I like using them. ...Back to tagging some stuff.

Labels: ,

 

iPhone: Question: Setting lots of UIButtons?

   1 Comments

I am playing around with UIScrollView and one of the things I am doing is loading it up with a large image that one can pan around and zoom in and out of. That part is done and was easy. Now, I'd like to add hot spots to that image and the easiest way I've initially thought to do that is by setting custom buttons on top of it.

What I really wanted to do was to create a really wide view that contains my image and would allow me to place my UIButtons by hand on top, but when using IB for the iPhone, you can't stretch a View out wider than the screen. Even if I can do that with code when it loads up, I still can't place those buttons by hand. At least I don't think that one can. I haven't seen how, and it would be a really nice enhancement feature somehow if one could with a special View XIB or something.

Anyway, I have a bunch of hotspots that I need to place. Currently it's awfully tedious and it's being done one at a time by hand. I am wondering if I should write a method that takes x, y, width, height, and just loops through an array and places these one at a time in the method.

I am not exactly sure how I would set that up in Obj-C yet at the moment, but it must be close to AS3 in doing so. My current pseudo-code:
UIImage *image = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"zooeyDeschanel" ofType:@"png"]];
imageView = [[UIImageView alloc] initWithImage:image];

myScrollView.contentSize = CGSizeMake(imageView.frame.size.width, imageView.frame.size.height);
myScrollView.maximumZoomScale = 6.0;
myScrollView.minimumZoomScale = 1.0;
myScrollView.clipsToBounds = YES;
myScrollView.delegate = self;

UIButton *but = [UIButton buttonWithType:UIButtonTypeCustom];
but.frame = CGRectMake( 0, 0, 50, 50 );
but.tag = 0;
[but setTitle:@"Test" forState:UIControlStateNormal];
[but addTarget:self action:@selector(displayValue:) forControlEvents:UIControlEventTouchUpInside];

UIButton *but2 = [UIButton buttonWithType:UIButtonTypeCustom];
but2.frame = CGRectMake( 200, 0, 50, 50 );
but2.tag = 1;
[but2 setTitle:@"Test 2" forState:UIControlStateNormal];
[but2 addTarget:self action:@selector(displayValue:) forControlEvents:UIControlEventTouchUpInside];

//... repeat this over and over

[myScrollView addSubview:imageView];
[myScrollView addSubview:but];
[myScrollView addSubview:but2];
I guess I am looking for a way to construct that loop. Probably shouldn't be too hard, but I wonder if there is a better way than floating the buttons above the image in the scroll view to begin with.

To be able to layout a really big view somehow would be a big plus instead of nailing down all the positions by hand.

Labels: ,

 

Friday, July 17, 2009

iPhone: External Accessory Framework snippets

Friday, July 17, 2009    17 Comments

I do not have any immediate plans to play around with this and I don't have any hardware or the protocol knowledge to do so with anything, but I was reading up on the External Accessory Framework that comes with the OS3.0 SDK.

Some information, but I didn't see any sample projects (for obvious reasons) or code snippets anywhere until just now. I'm not sure if this stuff is up to date or not, but here you go.

Add the ExternalAccessory.framework to your project. Make sure to add #import <ExternalAccessory/ExternalAccessory.h> to your .m file, and this is some example code:
- (EASession *)openSessionForProtocol:(NSString *)protocolString {
NSArray *accessories = [[EAAccessoryManager sharedAccessoryManager]
connectedAccessories];
EAAccessory *accessory = nil;
EASession *session = nil;

for (EAAccessory *obj in accessories) {
if ([[obj protocolStrings] containsObject:protocolString]){
accessory = obj;
break;
}
}

if (accessory){
session = [[EASession alloc] initWithAccessory:accessory
forProtocol:protocolString];
if (session) {
[[session inputStream] setDelegate:self];
[[session inputStream] scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[[session inputStream] open];
[[session outputStream] setDelegate:self];
[[session outputStream] scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[[session outputStream] open];
[session autorelease];
}
}
return session;
}

// Handle communications from the streams.
- (void)stream:(NSStream*)theStream handleEvent:(NSStreamEvent)streamEvent
{
switch (streamEvent)
{
// case NSStreamHasBytesAvailable: this was incorrect in Apple documentation...
case NSStreamEventHasBytesAvailable:
// Process the incoming stream data.
break;
case NSStreamEventHasSpaceAvailable:
// Send the next queued command.
break;
default:
break;
}
}
Enjoy. This is here merely as a kind of reference.

Labels: , ,

 

Wednesday, July 15, 2009

iPhone: Previous example with touches

Wednesday, July 15, 2009    0 Comments

I just wanted to update my previous post in regards to an iPhone application that allows one to step through images as a presentation to a potential/current client, etc. So all I've really done here is added swiping left and right with a small allowance for vertical changes.

#import "ClickThroughViewController.h"
#import <QuartzCore/QuartzCore.h>

@implementation ClickThroughViewController
@synthesize imageView, imageViewTop, aboutView, aboutButton,
advanceButton, backButton, label, gestureStartPoint, dirString;

- (void)updateLabel {
NSString *c = [[NSString alloc] initWithFormat:@"%i", count];
label.text = c;
[c release];
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
gestureStartPoint = [touch locationInView:self.view];
dirString = NULL;
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint currentPosition = [touch locationInView:self.view];
if(fabsf(gestureStartPoint.x - currentPosition.x ) >= kMinimumGestureLength && fabsf(gestureStartPoint.y - currentPosition.y) <= kMaximumVariance){
//Horizontal Swipe
if( gestureStartPoint.x < currentPosition.x ){
dirString = @"fromLeft";
} else {
dirString = @"fromRight";
}
}
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if( [dirString isEqualToString:@"fromLeft"] ){
[self prevImage];
} else if( [dirString isEqualToString:@"fromRight"]){
[self nextImage];
}
}

/*
You need to make sure the prototype images that you use are JPGs
and use the "image_" convention, otherwise the total count will
be off and your application will bork.
*/
- (void)viewDidLoad {

UIImage *img = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"image_1.jpg" ofType:nil]];
UIImage *img2 = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"image_1.jpg" ofType:nil]];
imageView.image = img2;
imageViewTop.image = img;
imageView.hidden = YES;
totalCount = 0;
aboutShowing = NO;

NSArray *d = [[NSBundle mainBundle] pathsForResourcesOfType:@"jpg" inDirectory:nil];
for(NSString *s in d){
if([[s lastPathComponent] hasPrefix:@"image_"]){
totalCount++;
}
}
count = 1;
prev = totalCount;
label.alpha = 0;

[self updateLabel];
[super viewDidLoad];
}

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

- (void)dealloc {
[imageView release];
[backButton release];
[advanceButton release];
[super dealloc];
}

// #pragma isn't very cool in my opinion, so...
//MARK: Custom Code

- (IBAction)advanceImage:(id)sender {
[self nextImage];
}

- (void)nextImage {
count++;
if( count > totalCount ){
count = 1;
}
if( count == 1 ){
prev = totalCount;
} else {
prev = count - 1;
}
[self updateLabel];

NSString *tmp = [[NSString alloc] initWithFormat:@"image_%i", count];
NSString *old = [[NSString alloc] initWithFormat:@"image_%i", prev];
UIImage *img = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:tmp ofType:@"jpg"]];
UIImage *img2 = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:old ofType:@"jpg"]];

imageView.image = img2;
imageViewTop.image = img;
label.alpha = 1;

CGContextRef context = UIGraphicsGetCurrentContext();
[UIView beginAnimations:nil context:context];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:[self imageViewTop] cache:YES];
[UIView setAnimationDuration:0.5];
[UIView commitAnimations];

CGContextRef context2 = UIGraphicsGetCurrentContext();
[UIView beginAnimations:nil context:context2];
[UIView setAnimationDuration:1.0];
label.alpha = 0;
[UIView commitAnimations];
}

- (IBAction)aboutChange:(id)sender {
if( !aboutShowing ){
aboutShowing = YES;
aboutView.hidden = NO;
aboutView.frame = CGRectMake(0, -50, 320, 480);
aboutView.alpha = 0.0;
CGContextRef context = UIGraphicsGetCurrentContext();
[UIView beginAnimations:nil context:context];
[UIView setAnimationDuration:1.0];
aboutView.frame = CGRectMake(0, 0, 320, 480);
aboutView.alpha = 1.0;
[UIView commitAnimations];

} else {
aboutShowing = NO;
CGContextRef context2 = UIGraphicsGetCurrentContext();
[UIView beginAnimations:nil context:context2];
[UIView setAnimationDuration:1.0];
aboutView.frame = CGRectMake(0, -50, 320, 480);
aboutView.alpha = 0.0;
[UIView commitAnimations];
}
}

- (IBAction)backImage:(id)sender {
[self prevImage];
}

- (void)prevImage {
count--;
if( count < 1 ){
count = totalCount;
}
if( count == 1 ){
prev = totalCount;
} else {
prev = count + 1;
}
[self updateLabel];
NSString *tmp = [[NSString alloc] initWithFormat:@"image_%i", count];
NSString *old = [[NSString alloc] initWithFormat:@"image_%i", prev];
UIImage *img = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:tmp ofType:@"jpg"]];
UIImage *img2 = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:old ofType:@"jpg"]];
imageView.image = img2;
imageViewTop.image = img;
label.alpha = 1;

CGContextRef context = UIGraphicsGetCurrentContext();
[UIView beginAnimations:nil context:context];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:[self imageViewTop] cache:YES];
[UIView setAnimationDuration:0.5];
[UIView commitAnimations];

CGContextRef context2 = UIGraphicsGetCurrentContext();
[UIView beginAnimations:nil context:context2];
[UIView setAnimationDuration:1.0];
label.alpha = 0;
[UIView commitAnimations];
}

@end

Labels: ,

 

Friday, July 10, 2009

iPhone: Easy step through presentation application

Friday, July 10, 2009    2 Comments

This does not account for left and right swiping, but it could be added easily. Have a bunch of photos that you'd like to show someone in a presentation? Perhaps mock-ups of layouts, etc. This bit can really help you along. I wrote this quickly but it works pretty well, even with some subtle and nice effects. You'll need to create two custom buttons that you'll hook up to backImage and advanceImage.

The code reads JPG images that use "image_" in their filename. That allows you just load up your project with the files you want and they end up in the bundle, available to you. You could tweak this to use online assets too if you really wanted.

Enjoy on this fine Friday.
ClickViewController.m
#import "ClickThroughViewController.h"
#import <quartzcore/quartzcore.h>

@implementation ClickThroughViewController
@synthesize imageView, imageViewTop, aboutView, aboutButton, advanceButton, backButton, label;

- (void)updateLabel {
NSString *c = [[NSString alloc] initWithFormat:@"%i", count];
label.text = c;
[c release];
}

/*
You need to make sure the prototype images that you use are JPGs
and use the "image_" convention, otherwise the total count will
be off and your application will bork.
*/
- (void)viewDidLoad {
UIImage *img = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"image_1.jpg" ofType:nil]];
UIImage *img2 = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"image_6.jpg" ofType:nil]];
imageView.image = img2;
imageViewTop.image = img;
imageView.hidden = YES;
totalCount = 0;
aboutShowing = NO;

NSArray *d = [[NSBundle mainBundle] pathsForResourcesOfType:@"jpg" inDirectory:nil];
for(NSString *s in d){
if([[s lastPathComponent] hasPrefix:@"image_"]){
totalCount++;
}
}
count = 1;
prev = totalCount;
label.alpha = 0;
[self updateLabel];
[super viewDidLoad];
}

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

- (void)dealloc {
[imageView release];
[backButton release];
[advanceButton release];
[super dealloc];
}

// #pragma isn't very cool in my opinion, so...
//MARK: Custom Code

- (IBAction)advanceImage:(id)sender {
count++;
if( count > totalCount ){
count = 1;
}
if( count == 1 ){
prev = totalCount;
} else {
prev = count - 1;
}
[self updateLabel];

NSString *tmp = [[NSString alloc] initWithFormat:@"image_%i", count];
NSString *old = [[NSString alloc] initWithFormat:@"image_%i", prev];
UIImage *img = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:tmp ofType:@"jpg"]];
UIImage *img2 = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:old ofType:@"jpg"]];

imageView.image = img2;
imageViewTop.image = img;
label.alpha = 1;

CGContextRef context = UIGraphicsGetCurrentContext();
[UIView beginAnimations:nil context:context];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:[self imageViewTop] cache:YES];
[UIView setAnimationDuration:0.5];
[UIView commitAnimations];

CGContextRef context2 = UIGraphicsGetCurrentContext();
[UIView beginAnimations:nil context:context2];
[UIView setAnimationDuration:1.0];
label.alpha = 0;
[UIView commitAnimations];
}

- (IBAction)aboutChange:(id)sender {
if( !aboutShowing ){
aboutShowing = YES;
aboutView.hidden = NO;
aboutView.frame = CGRectMake(0, -50, 320, 480);
aboutView.alpha = 0.0;
CGContextRef context = UIGraphicsGetCurrentContext();
[UIView beginAnimations:nil context:context];
[UIView setAnimationDuration:1.0];
aboutView.frame = CGRectMake(0, 0, 320, 480);
aboutView.alpha = 1.0;
[UIView commitAnimations];

} else {
aboutShowing = NO;
CGContextRef context2 = UIGraphicsGetCurrentContext();
[UIView beginAnimations:nil context:context2];
[UIView setAnimationDuration:1.0];
aboutView.frame = CGRectMake(0, -50, 320, 480);
aboutView.alpha = 0.0;
[UIView commitAnimations];
}
}

- (IBAction)backImage:(id)sender {
count--;
if( count < 1 ){
count = totalCount;
}
if( count == 1 ){
prev = totalCount;
} else {
prev = count + 1;
}
[self updateLabel];
NSString *tmp = [[NSString alloc] initWithFormat:@"image_%i", count];
NSString *old = [[NSString alloc] initWithFormat:@"image_%i", prev];
UIImage *img = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:tmp ofType:@"jpg"]];
UIImage *img2 = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:old ofType:@"jpg"]];
imageView.image = img2;
imageViewTop.image = img;
label.alpha = 1;

CGContextRef context = UIGraphicsGetCurrentContext();
[UIView beginAnimations:nil context:context];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:[self imageViewTop] cache:YES];
[UIView setAnimationDuration:0.5];
[UIView commitAnimations];

CGContextRef context2 = UIGraphicsGetCurrentContext();
[UIView beginAnimations:nil context:context2];
[UIView setAnimationDuration:1.0];
label.alpha = 0;
[UIView commitAnimations];
}

@end

Labels: , ,

 

Monday, July 6, 2009

iPhoneSimulatorExchange

Monday, July 6, 2009    0 Comments

There is a new program out for iPhone developers that allows you to create a one-click installer of your iPhone application that allows you to package up your app so it can be run on someone else's Simulator. It's said to help with testing and also for allowance of screencasts for review purposes.

I can see the value for the screencasts certainly, and use for other developers is neat without them having to worry about your frameworks, etc. I do think that it might almost be easy enough to zip up a project file and have another developer just compile the thing you want tested, but I could certainly be wrong about that. Check it out.

Labels: , ,

 

Thursday, June 25, 2009

[iPhone] Getting images from your bundle without hardcoding

Thursday, June 25, 2009    0 Comments

Update 3. Better yet:
totalCount = 0;
NSArray *d = [[NSBundle mainBundle] pathsForResourcesOfType:@"jpg" inDirectory:nil];
for(NSString *s in d){
if([[s lastPathComponent] hasPrefix:@"image_"]){
totalCount++;
}
}
Update 2. Since the file structure normally gets flattened out within your iPhone application (I guess there are some compiler tricks you can do), you'll always be targeting the root of the app directory. With that in mind, you'll want to probably use some kind of naming convention to separate any special files you'd like to collect from the bundle. You can also use extension. Say collect up and get the count of all images in your bundle that are JPGs and contain "image_" in the title:
totalCount = 0;
NSArray *d = [[NSBundle mainBundle] pathsForResourcesOfType:@"jpg" inDirectory:nil];
for( int i=0;i<[d count];i++){
NSString *searchForMe = @"image_";
NSString *s = [[NSString alloc] initWithString:[d objectAtIndex:i]];
NSRange range = [s rangeOfString:searchForMe];
if( range.location != NSNotFound ){
totalCount++;
}
}
Boom.

Update. I'm a moron. I should have just looked into NSBundle.h.
NSUInteger jpegCount = [[[NSBundle mainBundle] pathsForResourcesOfType:@"jpg" inDirectory:subDirName] count];

You can get the number of a type of item in your bundle on the iPhone, and this can come in very handy. No magic numbers. This code example doesn't look in a resource directory, but you could supply the inDirectory with something like @"backgrounds" and thus build up arrays of images of differing types, etc. There may be a better way of doing this (ie. if you only want to get the count of the items), but I don't know about it yet.
NSMutableArray * imageArray = [[NSMutableArray alloc] init];
NSEnumerator * imageBundlePathEnumerator = [[[NSBundle mainBundle] pathsForResourcesOfType: @"jpg" inDirectory: nil] objectEnumerator];
NSString * imageBundlePath = nil;

while (imageBundlePath = [imageBundlePathEnumerator nextObject])
{
[imageArray addObject: [[[NSImage alloc] initWithContentsOfFile: imageBundlePath] autorelease]];
}

NSLog(@"Number of images loaded : %d", [imageArray count]);

NSImage * anImage = nil;
NSEnumerator * imageEnumerator = [imageArray objectEnumerator];

while (anImage = [imageEnumerator nextObject])
{
NSLog(@"image = %@", anImage);
}
Tada. I hope to find some more examples.

Labels: ,

 

Wednesday, May 13, 2009

iPhone: Detect online network status

Wednesday, May 13, 2009    6 Comments

For my previous post I showed you how you can cache in memory images from URLs... however, what if the device is not able to connect to a network? The previous example will hang and eventually bomb back to the springboard because it's not coded to handle that condition.

For me this network detection thing was a bit of a Holy Grail item. I'd seen a few examples around from Apple that seemed like they used tons of code to get the job done (or maybe I just didn't understand their code well enough yet). Then I read online about a seismic XML tutorial from Apple that did network detection.

I popped open the project in the Xcode Documentation window and started checking out the .h and .m files. I saw the implementation they used, and it didn't involve tons of code. And after testing it, it works just fine for the moment.

I've since been told that I could use a separate thread and populate the initial UIImageViews with a stock image, and the threaded process would fetch and place the artwork as needed (to allow for initial smooth scrolling). I don't know enough about that yet though, so this will have to do.

Make sure you Add the SystemConfiguration.framework to your project, and include it in your .m (#import ).

In your .h file, define a BOOL, I called mine "availableNetwork" - you don't need to assign it as a property, just up in the interface block. Make sure you also define the method in the .h... -(BOOL)isDataSourceAvailable;

Here are the two methods I'm using to report back network status (in my .m):
- (BOOL)isDataSourceAvailable {

static BOOL checkNetwork = YES;
static BOOL _isDataSourceAvailable = NO;
if (checkNetwork) { // Since checking the reachability of a host can be expensive, cache the result and perform the reachability check once.
checkNetwork = NO;
Boolean success;
const char *host_name = "google.com"; //pretty reliable :)
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(NULL, host_name);
SCNetworkReachabilityFlags flags;
success = SCNetworkReachabilityGetFlags(reachability, &flags);
_isDataSourceAvailable = success && (flags & kSCNetworkFlagsReachable) && !(flags & kSCNetworkFlagsConnectionRequired);
CFRelease(reachability);
}
return _isDataSourceAvailable;
}
That's the method we'll call when we load our view, and set our "availableNetwork" value to. loadView is triggered before viewDidLoad.
- (void)loadView {

availableNetwork = NO;
BOOL returnVal = [self isDataSourceAvailable];
availableNetwork = returnVal;
...
And there you have it. It's a one shot deal here, if connectivity returns, the flag won't be reset. You could always run a NSTimer that checks every now and then, or I am sure there is some kind of notification of that kind of change one could listen for... I'm not far enough along with all of this to know yet.

Labels: , ,

 

Tuesday, May 12, 2009

iPhone: UITableView customization

Tuesday, May 12, 2009    1 Comments

I have a UITableView where I am providing my own graphics for the normal and selected states. Because my graphic for the selected state is dark, the dark text in my UILabels in each cell needs to turn to white for legibility. Then of course it needs to be reset afterwards to black on the normal graphic.

After spending a lot of time on forums, Google, and email lists, I have a solution that should have been easier to get to. I received suggestions that I keep a pointer around for the previously selected item, so when a selection is detected I could introspect the current cell, change the text color there, and then change the previous cell's UILabels back to normal. A lot of work, and it's keeping state which is a bad idea. There were mentions of reloadData for the table, etc.

However one thing I overlooked initially was that I was thinking of the table cell as retaining it's selected state. Normally you'd just call up another view from a selection in a table. So I didn't really need to maintain the selected visual state in the table, but I needed to show it to the user.

A fine chap from Apple emailed me that most iPhone applications implement a nice animation for selected cells in a table and that I should follow that paradigm. So that got me digging.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
//NSLog(@"%d:%d", [indexPath row], [indexPath section] );
// Need to get to the cell
UITableViewCell *newCell = [tableView cellForRowAtIndexPath:indexPath];
UILabel *cellLabel = (UILabel *)[newCell.contentView viewWithTag:1];
NSLog( @"%@", [cellLabel text] );
}
That bit of animation basically tells the table to deselect the selected item with animation. This works great but it doesn't reset the text color in the cell itself. That's when I stumbled onto the holy grail solution for me being able to reset the text without having to dig into the cell itself or keep a pointer around for the previous selected cell. It was simple using a property I hadn't run into yet before so I didn't know of it's existence... highlightedTextColor. DOH! That was it!

So pairing the above method with this detail in my cellForRowAtIndexPath, I get exactly what I needed:
...
labelThree = [[[UILabel alloc] initWithFrame:CGRectMake( 65, 34, 200, 25)] autorelease];
labelThree.tag = 3;
labelThree.font = [UIFont systemFontOfSize:10.0];
labelThree.backgroundColor = [UIColor clearColor];
labelThree.textColor = UIColorFromRGB(0x555555);
labelThree.highlightedTextColor = [UIColor whiteColor]; // Why didn't I know about this!!!
labelThree.text = [NSString stringWithFormat:@"%@", [[photoArray objectAtIndex:row] objectForKey:@"rating"]];
...
I set the text color (using a macro) and also the highlighted text color. So what happens is that the background transitions to my dark selected graphic while the text turns white, and upon deselection the text returns back to that 0x555555 color while the dark selected graphic fades away leaving the normal graphic for the cell.

This works perfectly without me keeping state myself anywhere... let the APIs handle all of that crap for me. I burned a whole day on this simple thing, but it takes moments like these to really understand and learn (in my opinion).

Labels: , ,

 

Thursday, May 7, 2009

iPhone: Want to use Hex for UIColor?

Thursday, May 7, 2009    1 Comments

When you need to set basic colors to things in Objective-C, it's really easy:
//This works, but come on...
cell.textColor = [UIColor grayColor];
However, if you want to be more specific normally you need to get the RGBA for the color and set it that way. That's time consuming especially if the color may be tweaked several times before the final is settled upon. In flash we can just use Hex. Now, with this little macro, you can too & it's a HUGE time saver:
#define UIColorFromRGB(rgbValue) [UIColor \
colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 \
green:((float)((rgbValue & 0xFF00) >> 8))/255.0 \
blue:((float)(rgbValue & 0xFF))/255.0 alpha:1.0]

- (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
if (nil == cell) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:@"cell"] autorelease];
}
cell.textColor = UIColorFromRGB(0x333333);
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.text = @"Testing 1 2 3";
}
Shazaam!!!

Labels: ,

 

Wednesday, May 6, 2009

Getting columned data in a UITableView

Wednesday, May 6, 2009    0 Comments

Since I'm still fairly green to this whole Objective-C thing and I am still learning the APIs that are available (there are a billion of them), I don't always know how to do exactly what I want to do. A case in point... I wanted to present a high scores screen in a little game I am coding up (a learning exercise... I highly recommend coding a game to expose yourself to tons of little challenges).

I have an NSMutableArray that I have stuffed with NSDictionary items. Each of those has a key for name and score. Easy. However I thought about using the "\t" character like we can do in ActionScript 3 and implement a tabStop somehow... thus negating the need to use dictionaries at all and just munge the name with the score.

Obviously this makes sorting an extremely stupid activity (many hoops to needlessly jump through). So it's not a solution by any means (an array of dictionaries is), but I wanted to see how I might replicate the functionality of tabStops in Objective-C (non-paragraph).

I have yet to find the solution. However in beating myself up in trying to figure out how to properly implement NSTextTab for a non UITextField I came across a true solution. Adding subviews!

This is exactly what I was after, and it has the benefit of allowing tons of flexibility in how to best present your textual data in the UITableView.

Without dressing the main method up too much, I think you'll be able to see what's going on here. I am only using a straight NSArray for this example, but you can see how this works.
// Set up the table view cells
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {

static NSString *SimpleTableIdentifier = @"SimpleTableIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SimpleTableIdentifier];

UILabel *label; // for scores
UILabel *nameLabel;

if( cell == nil ){

CGRect frame = CGRectMake(0,0,300,44);
cell = [[[UITableViewCell alloc] initWithFrame:frame reuseIdentifier:SimpleTableIdentifier] autorelease];
cell.selectionStyle = UITableViewCellSelectionStyleNone;

//Create the score label in the cell
label = [[[UILabel alloc] initWithFrame:CGRectMake(0.0, 10.0, 70.0, 25.0)] autorelease];
label.tag = 1;
label.font = [UIFont boldSystemFontOfSize:18.0];//systemFontOfSize
label.textAlignment = UITextAlignmentRight;
label.backgroundColor = [UIColor clearColor];
label.textColor = [UIColor whiteColor];
label.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleHeight;
[cell.contentView addSubview:label];
label.text = @"1000";

// Create the name label in the cell
nameLabel = [[[UILabel alloc] initWithFrame:CGRectMake(80.0, 10.0, 195.0, 25.0)] autorelease];
nameLabel.tag = 2;
nameLabel.font = [UIFont boldSystemFontOfSize:18.0];
nameLabel.textColor = [UIColor whiteColor];
nameLabel.backgroundColor = [UIColor clearColor];
nameLabel.lineBreakMode = UILineBreakModeWordWrap;
nameLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[cell.contentView addSubview:nameLabel];

} else {
// Out of view items
label = (UILabel *)[cell.contentView viewWithTag:1];
nameLabel = (UILabel *)[cell.contentView viewWithTag:2];
}

NSInteger row = [indexPath row];
nameLabel.text = [listData objectAtIndex:row];
cell.selected = NO;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;
}

Labels: , , , ,

 

Thursday, April 16, 2009

Sending an email from an iPhone application

Thursday, April 16, 2009    2 Comments

This certainly is not the most secure way of sending an email by any means, but it does work. So you would like your iPhone application to be able to send an email - perhaps a feedback form of some kind, etc. The example supplied here contains a UITextField and a UIButton. A UIAlertView and a UIActionSheet are also used. Oh, and there is a little snippet of PHP to put on your server someplace (that's the unsecure part here).

Your .h file:
#import <UIKit/UIKit.h>

@interface SendEmailViewController : UIViewController {
IBOutlet UIButton *button;
IBOutlet UITextField *messageText;
IBOutlet UIButton *backgroundButton;
}

@property (nonatomic, retain) UIButton *button;
@property (nonatomic, retain) UITextField *messageText;
@property (nonatomic, retain) UIButton *backgroundButton;

-(IBAction)sendMail:(id)sender;
-(IBAction)textFieldDoneEditing:(id)sender;
-(IBAction)backgroundClick:(id)sender;

@end
And now your .m file:
#import "SendEmailViewController.h"

@implementation SendEmailViewController

@synthesize button, messageText, backgroundButton;

-(IBAction)textFieldDoneEditing:(id)sender {
[sender resignFirstResponder];
}

-(IBAction)backgroundClick:(id)sender {
[messageText resignFirstResponder];
}

-(void)displayAlert {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Email Sent" message:@"Thank you for contacting me" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
[messageText setText:@""];
[alert show];
[alert release];
}

-(void)displaySheet {
NSString *msg = nil;
msg = [[NSString alloc] initWithFormat:@"Send Email? Your message:\"%@\"", messageText.text];
UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:msg delegate:self cancelButtonTitle:@"Not yet" destructiveButtonTitle:@"Yes" otherButtonTitles:nil];
[actionSheet showInView:self.view];
[actionSheet release];
[msg release];
}

-(void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex {
if( !(buttonIndex == [actionSheet cancelButtonIndex]) ){
NSString *post = nil;
post = [[NSString alloc] initWithFormat:@"message=%@",messageText.text];
NSData *postData = [post dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
NSString *postLength = [NSString stringWithFormat:@"%d", [postData length]];
NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] init] autorelease];
[request setURL:[NSURL URLWithString:@"http://www.yourserveraddy/yourScript.php"]];
[request setHTTPMethod:@"POST"];
[request setValue:postLength forHTTPHeaderField:@"Content-Length"];
[request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
[request setHTTPBody:postData];
[ NSURLConnection connectionWithRequest:request delegate:self ];

[post release];
[self displayAlert];
}
}

-(IBAction)sendMail:(id)sender {
[self displaySheet];
}

- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];

// Release any cached data, images, etc that aren't in use.
}

- (void)viewDidUnload {
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[super dealloc];
[button release];
[messageText release];
[backgroundButton release];
}
And lastly your PHP file:
<?php
$message = $_REQUEST['message'] ;
mail( "recipient@server.com", "Test Message", $message );
?>
You just need to set your outlets and set you should be all set.

Labels: , ,