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

Threading intro

June 15th, 2009

While my app grabs data from the net I want the user to see a loading animation. To do this I need to break off the main thread and start a new one for the net load, and use the main thread to display the animation. I’ve set up my load animation using a nib, though this is simple enough to where I’d probably prefer to just do it programmatically. But that’s not really what I care about right now. Anyway, my nib is called “LoadSpinner”, and the controller is called “LoadSpinnerController”. Simple first step is to load this view (it subclasses UIView) and add it to the application view. Easy enough, done this quite a few times now:

spinnerController = [[LoadSpinnerController alloc] initWithNibName:@”LoadSpinner” bundle:nil];
[window addSubview:spinnerController.view];

Next I want to start the loading process, so I detach a new thread from the main thread like so:

[NSThread detachNewThreadSelector:@selector(loadNetInfo) toTarget:self withObject:nil];

where

loadNetInfo

is the method used to grab whatever data I need. Outlined here:

-(void) loadNetInfo
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

doing the loading stuff here..

[self performSelectorOnMainThread:@selector(releaseSpinner) withObject:nil waitUntilDone:NO];
[pool release];
}

Notice the selector is looking for

releaseSpinner

. I don’t know if this is the best way to do it, but all I’m doing in this method is

[spinnerController release];

. This gets rid of the spinner when the info is returned, loaded and ready for use. I’ll have to see if there’s a better way to do this.