Presence 3 – Part 2 Custom Delegation

August 6th, 2009

Setting up a custom delegate seems pretty straightforward. In this case we want the calling class to understand what the child is saying to it.

I’ve set up my PersonListTableViewController with a toolbar item that allows me to update my twitter status. It does so by popping up a new modal window where I type in my status and hit done to send it over to twitter. Here’s where I do that. In the main view controller I add the method that will trigger my modal window. didTapEditStatusButton:

-(void)didTapEditStatusButton
{
StatusComposeViewController *statusComposeViewController = [[StatusComposeViewController alloc] init];
statusComposeViewController.delegate = self;

[self presentModalViewController:statusComposeViewController animated:YES];
[statusComposeViewController release];
}

The first thing that happens here is the modal window is instantiated.
Next I assign the delegate of the modal window to the main view controller. This is how we get the parent to listen to the child. Or, I guess this is where we tell the child who it’s daddy is.
When this is done all we need to do is present the controller, then release.

Because it is considered “Bad Form” for a modal window to dismiss itself, I’ll use delegation to let my parent view controller PersonListTableViewController know what the child view controller (my modal window) has done.

So the first thing I need to do is create my modal view. I’m going with the suggested StatusComposeViewController. Nothing new here, I’m subclassing UIViewController.

In the class header for my new view I need to add the delegation protocol. First thing I need to remember to do is declare the delegate protocol.

@protocol StatusComposeViewControllerDelegate;

Then I create the protocol and give it a couple of optional methods:

@protocol StatusComposeViewControllerDelegate
@optional
-(void)statusViewControllerDidSetStatus:(StatusComposeViewController *)controller didSetStatus:(NSString *)text;
-(void)statusViewControllerDidCancel:(StatusComposeViewController *)controller;
@end

This essentially declares these methods for whatever controller you specify as your delegate.

Moving back into my delegate (PersonListTableViewController) I’m going to need to implement the methods I’ve defined in my modal view. For this post I’ll just do one, the statusViewControllerDidSetStatus method.

-(void)statusViewControllerDidSetStatus:(StatusComposeViewController *)controller didSetStatus:(NSString *)text
{
[TwitterHelper updateStatus:text forUsername:username withPassword:password];
[self didFinishWithModalViewController];
}

A simple method for updating twitter status. The TwitterHelper class is provided in the course files. didFinishWithModalViewController is a method I’m using for cleanup and dismissal of the modal window. (Dismiss using [self didFinishWithModalViewController]). Any view can only have one modal view up at any given time, so we don’t need to tell this method which modal view to dismiss… it will always be the current modal view.

What did I miss…

Ok in the modal view controller StatusComposeViewController I need to implement the methods I’ll use to hit the delegate methods to perform the actions that the modal view is intended for.

- (IBAction)doneButtonPressed:(id)sender{
if([self.delegate respondsToSelector:@selector(statusViewControllerDidSetStatus:didSetStatus:)]){
[self.delegate statusViewControllerDidSetStatus:self didSetStatus:textField.text];
}
}

It is important to make sure you find out weather or not the delegate has implemented the method you’re going to try to call. This is done in the first line. Then we perform the operation.

Drag ‘n Drop reorder in an HBox

June 26th, 2009

It’s a bit off topic, but I was googling for an example of reordering images in an HBox using drag and drop. I came up empty, but I also found that there were a few other people asking for help doing the same thing, so I figured I’d post my solution here.

Assuming you’ve set up your image list (I’ve included an xml list of images in the source) the idea is pretty simple. Loop through the images, and add them to an array. With each image add the appropriate listeners so that they know how to handle being dragged, dragged over, and dropped on. The array is used to keep track of the image positions in the HBox, and gets reshuffled each time an image changes it’s position.

First things first, grab your list of image data. I’m calling a php page and grabbing the list from the database. My php assembles the xml for me, but I’ve included some sample xml in the source. Once you grab that you process the images, pushing each image to the array and making sure to add your listeners.

for(var i:int=0; i<ListData.image.length(); i++){         var ImageObject:Object = new Object();         var nextInLine:XML = ListData.image[i];                 ImageObject.source_path = nextInLine.elements("name");         ImageObject.image_id = nextInLine.elements("image_id");                 var nextImage:Image = new Image();                 nextImage.addEventListener(MouseEvent.MOUSE_DOWN, initDragImageProc);                 nextImage.addEventListener(DragEvent.DRAG_DROP,dragImageDropHandler);                 nextImage.addEventListener(DragEvent.DRAG_ENTER,dragImageEnterHandler);                 nextImage.buttonMode = true;                 nextImage.source = ImageObject.source_path;                 nextImage.id = ImageObject.image_id;                 nextImage.width = 100;                 nextImage.height = 100;                 ImageArray.push(nextImage);         h_image_list.addChild(nextImage);         }

The interesting stuff happens when you perform the drag.

/**  * reorder the images  **/ private function dragImageDropHandler(event:DragEvent):void {                 // this is the image you are dragging                 var theImage:Image = event.dragInitiator as Image;         // this is the image you dropped the dragged image onto                 var theTarget:Image = event.target as Image;         // find the index of the target image in the image array                 var toImageIndex:int = ImageArray.indexOf(theTarget);         // set the index of the dragged child equal to the index of the target image                    this.h_image_list.setChildIndex(theImage,toImageIndex);         //refresh the array so that the indices represent the new order in the HBox                 reorderImageArray(); }

Take a look at it here. View source to enjoy a download =)

Presence 3 (Part 1, Threading)

June 22nd, 2009

Mastering the art of threading using NSOperation is the first part of this assignment. I unfortunately got a bit stuck in the lecture examples. I was trying to implement the example code that I had seen without a solid understanding of what it was doing. This led to a few nights of frustration. I finally decided to scrap my currant process, go back to the code and try to really understand what it was doing. (I know, this is something one should always do, but in my zest to have a working prototype I lost sight of this).

My biggest problem here was that I needed to understand the order of operations a little better. The first thing I need my app to do is get the list of usernames. So, in my table view controllers initWithStyle method I read the names from the TwitterUsers.plist and initialize my names NSArray with the results.

//get the names
usersBundle = [[NSBundle mainBundle] pathForResource:@”TwitterUsers” ofType:@”plist”];
names = [[NSArray alloc] initWithContentsOfFile:usersBundle];

I’ll also use this method to init my NSOperationQueue. I’ll be needing this very soon.

In my viewWillAppear method I’ll call the method to get the list of details for each person. This is going to include the URLs for their respective images. Except, I don’t really call the method that does this. I call the method that adds the method that does this to an NSOperationQueue (getListOfPeople).

-(void)getListOfPeople
{
NSLog(@”(void)getListOfPeople”);
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(syncLoadingPeopleList) object:nil];
[operationQueue addOperation:operation];
[operation release];
}

So, syncLoadingPeopleList is the method that loads the people data into the people array. When this is finished I call the didFinishLoadingPeopleList back in the main thread.

[self performSelectorOnMainThread:@selector(didFinishLoadingPeopleList) withObject:nil waitUntilDone:NO];

The didFinishLoadingPeopleList iterates through the people and adds the image URL to my imageURLList NSMutableArray. Upon writing this, it occurs to me that I could probably just as well populate my imageURLList array in the syncLoadingPeopleList method at the same time I am populating the people array…

Here’s something that gave me some trouble initially. Chalk it up to a dumb mistake. At this point I still had my numberOfRowsInSection method looking at the size of my names array (perfectly ok in Presence 2). The problem here was that I was looking for information in my ImageURLList, which was populated by my people array, which at that point may not have had a single person loaded yet. I was getting all sorts of invalid index errors. Once I switched that out with the people array I was good to go.

So now I’ve got my image URLs, but still no images. Images need to be downloaded from the net and loaded into the table cell. Obviously this calls for some more threading. In my cellForRowAtIndexPath method where I would normally set my image to equal an already existing image, I’m setting it to equal the result of a cached image array lookup. Here’s how this works:

  1. cellForRowAtIndexPath is called nd wants an image
  2. image is set to the cached image
    cell.image = [self cachedImageForURL:[imageURLList objectAtIndex:indexPath.row]];
    • the cachedImageForURL method takes a NSURL as a param and looks to see if it’s NSMutableArray counterpart (cachedImages) already has this image
      id cachedObject = [cachedImages objectForKey:url];
      if(cachedObject == nil){
    • If so, then it checks to make sure it is an NSURL, and sets it to nil if not (remember it’s ok to send a nil message)
      else if(![cachedObject isKindOfClass:[UIImage class]]){
      cachedObject = nil;
      }
    • If not, then it starts the process of downloading the image
      ImageLoadingOperation *operation = [[ImageLoadingOperation alloc] initWithImageURL:url target:self action:@selector(didFinishLoadingImage:)];
      [operationQueue addOperation:operation];
      [operation release];
  3. This executes as many times as there are people in the list, and calls didFinishLoadingImage as soon as the image is loaded and ready for use. This adds the image to the cashedImages array and reloads the table data.
    -(void)didFinishLoadingImage:(NSDictionary *)result
    {
    NSURL *url = [result objectForKey:@"url"];
    UIImage *image = [result objectForKey:@"image"];

    [cachedImages setObject:image forKey:url];
    [self.tableView reloadData];
    }

…and we have our image