Presence 3

As I said in an earlier post I’ve been working on learning Objective-C, more specifically, programming for the iPhone. Well, I just finished off my latest Stanford iPhone assignment, Presence

  1. As always you can find it up on github, there’s a branch for assignment 1, assignment 2 and now assignment 3.

A couple of very key concepts were introduced for this assignment including threading, modal views and text input.

Threading

Threading is one of the most important things to learn when doing iPhone programming. For this assignment the main idea was to have any network requests (loading any of the Twitter data) happen in another thread with a spinner showing that something is happening. There are a number of ways to move tasks off the main thread in Objective-C that abstract things away from using threads directly.

So far I have found using NSOperations to be the easiest way to deal with threads. One way to create an NSOperation is by creating a subclass and using an instance of that object in your code. My image loading operation for Presence 3 is below.

@interface ImageLoadingOperation : NSOperation {
	NSURL *imageURL;
	id target;
	SEL selector;
}
- (id) initWithImageURL:(NSURL *)url target:(id)theTarget 
                                   selector:(SEL)theSelector;
@end

@implementation ImageLoadingOperation

- (void) main {
  NSData *imageData = [[NSData alloc] initWithContentsOfURL:imageURL];
  UIImage *image = [[UIImage alloc] initWithData:imageData];
  
  if(image == nil){
    image = [[UIImage alloc] init];
  }
  NSDictionary *result = [NSDictionary dictionaryWithObjectsAndKeys:image, 
                          @"image", imageURL, @"url", nil];
	
  [target performSelectorOnMainThread:selector 
                           withObject:result 
                        waitUntilDone:NO];
	
  [imageData release];
  [image release];
}
@end

To create an NSOperation subclass the first thing you need to do is override the main method, that’s where the bulk of your code for the background thread will go. Once the work you need to do is finished you need to send the results back to the main thread with the method ‘performSelectorOnMainThread:withObject:waitUntilDone:’ passing the result and a boolean to say whether this thread should block until the selector is performed.

Now that we have an operation defined we need to create an instance and use it. It’s pretty much the same as using any other object, the only difference is that you need to add the operation to an operation queue and it deals with scheduling and all that.

operationsQueue = [[NSOperationQueue alloc] init];

ImageLoadingOperation *operation = [[ImageLoadingOperation alloc] 
  initWithImageURL: url
  target:self
  selector:@selector(didFinishLoadingImageWithResult:)];
[operationsQueue addOperation:operation];
[operation release];

Everything here is pretty straight forward, you create an operations queue (which can be used for many different types of operations) and then add the operation instance to the queue to be executed.

Another way to use operations for threading is by using the NSInvocationOperation class. It’s a implementation of NSOperation that you can use instead of having to subclass it yourself. All you have to do is remember to call ‘performSelectorOnMainThread:withObject:waitUntilDone:’ in the method given as the selector parameter.

NSInvocationOperation *operation = [[NSInvocationOperation alloc]
  initWithTarget:self
  selector:@selector(sendMessage:)
  object:text];

[operationsQueue addOperation:operation];
[operation release];

Modal Views are fairly common on the iPhone, it’s a view that can be popped up in the stack and has to be dismissed before getting back to any previous interactions. Think of when you’re composing an email or adding a contact; the view that shows when doing those actions is presented modally.

Any view controller (or navigation controller since it’s a subclass) can be presented modally with one simple method call.

[self presentModalViewController:composeView animated:YES];

This will show the ‘composeView’ which has to be dismissed before the user can continue whatever they were previously doing. Dismissing the view is best done by delegating back to the view or controller that presented it.

@implementation ComposeViewController

-(IBAction) cancelButtonPressed:(id)sender {
  if([self.delegate respondsToSelector:@selector(composeViewDidFinish)]) {
    [self.delegate composeViewDidFinish];
  }
}

@end

When the composeView was created I set the creator as the delegate, then when the cancel button is pressed I can tell the delegate that the composeView is finished and it can decide to dismiss the view or do something else. One simple method call in the delegate dismisses the modal view.

[self dismissModalViewControllerAnimated:YES];

Text Input

Handling text input is incredibly easy since the iPhone handles most of the annoying work for you. You don’t have to worry about calling up the keyboard or anything like that, when a keyboard is needed the framework knows and displays it for you. There’s a lot of customization that you can do through Interface Builder or code, all of which is pretty straight forward.

If you do need to interact with what’s happening with the keyboard there are some optional delegate methods that you can implement. Just set a class as the inputs’ delegate and implement the methods you want.

- (BOOL)textField:(UITextField *)textField
    shouldChangeCharactersInRange:(NSRange)range 
    replacementString:(NSString *)string {
    
  NSString *testString = [textField.text 
                          stringByReplacingCharactersInRange:range withString:string];
  charactersRemaining.text = [NSString stringWithFormat:@"%d", (140-[testString length])];

  if( [testString length]>0 && ([testString length]) <= 140 ) {
    sendButton.enabled = YES;
  } else {
    sendButton.enabled = NO;
  }

  return YES;  
}

This first method gives you a bunch of information about the current keyboard interaction, like what string is going to be added and where in the range of the string it’s going to be placed. This lets you filter out characters or modify what is happening with the input. A boolean is returned to say when the input should take place or not. In my case I wanted to enable or disable another button depending on how much text was going to be in the input. By using the ‘stringByReplacingCharactersInRange’ method on the current input text and the string that was being passed in the resulting text length can be found. Thanks to Ben Lachman who helped me out with this.

- (BOOL)textFieldShouldReturn:(UITextField *)textField{
  if ([textField.text length]<=140) {
  	[textField resignFirstResponder];
	
  	if([self.delegate respondsToSelector:@selector(composeViewDidFinish:withText:)]) {
  		[self.delegate composeViewDidFinish:self withText:textField.text];
  	}
  	return YES;		
  } else {
  	return NO;
  }
}

This next method lets you decide what should happen when return (or send, or go) is pressed. You can call other methods here and return a boolean saying whether the keyboard should return or not. Notice that I am letting the delegate decide what to do with the result.

Conclusion

Well, there was a lot going on in this version of Presence but hopefully I didn’t cut things down too much in this blog post. The most interesting bit is the threading, it’s also the most useful. If you want some more details just let me know or take a look at my version of Presence 3 yourself.

Posted October 14, 2009

Comments

comments powered by Disqus