Simon says, MonoTouch downloading images like AppStore app in less than 50 lines with CoreAnimation Transitions!
In Project-D I needed to dynamically load images in the same manner the Apple AppStore loads the application icons over the web and have them nicely render into place when the download is completed. Obviously this can’t occur in the UITableView methods because scrolling performance would be degraded significantly waiting for even 1 image to download.
I was amazingly surprised how simple this is in MonoTouch using ThreadPool.QueueUserWorkItem() and some Objective-C classes MonoTouch has wrapped bindings for. I did this all in 50 lines of code. I could have optimized it more to shrink it, but it’s 50 lines with broken out methods and Core Animation transitions. Very small and easy to do!
This is based on my blog about how to build custom UITableViewCell’s in Interface Builder which you can find here.
Inside the GetCell() method of my UITableViewDelegate I call GetImage() passing the UITableViewCell controller and the story (which contains the image uri). GetImage() queues a task with the ThreadPool to go fetch us the image so that the UITableView scrolling is unaffected.
The great thing about GetCell() on the iPhone is, it only gets called for cells which are being rendered. So we do not have to handle all the logic about calculating what is visible, what do we fetch etc. We already know what we need to download! I fade the images alpha so that they appear to fade in nicely once the download is complete.
public override UITableViewCell GetCell(UITableView tableView, MonoTouch.Foundation.NSIndexPath indexPath)
{
// ... Do your usual stuff...
GetImage(controller, story);
}
private void GetImage(StoryCellController controller, Story story)
{
controller.ImageThumbnail.Alpha = 0.0f;
if (story.Thumbnail != null && story.Thumbnail.Src != string.Empty)
{
if (images.ContainsKey(story.Thumbnail.Src) == true)
{
UIImage imageThumbnail = images[story.Thumbnail.Src];
controller.ImageThumbnail.Image = imageThumbnail;
controller.ImageThumbnail.Alpha = 1.0f;
}
else
{
controller.ImageUri = story.Thumbnail.Src;
ThreadPool.QueueUserWorkItem(RequestImage, controller);
}
}
}
private void RequestImage(object state)
{
StoryCellController controller = state as StoryCellController;
if (controller != null)
{
NSUrl imageUrl = NSUrl.FromString(controller.ImageUri);
NSData imageData = NSData.FromUrl(imageUrl);
controller.ImageThumbnail.Image = UIImage.LoadFromData(imageData);
images.Add(controller.ImageUri, controller.ImageThumbnail.Image);
InvokeOnMainThread(delegate { RefreshImage(controller); });
}
}
private void RefreshImage(StoryCellController controller)
{
UIView.BeginAnimations("imageThumbnailTransitionIn");
UIView.SetAnimationDuration(0.5f);
controller.ImageThumbnail.Alpha = 1.0f;
UIView.CommitAnimations();
}


Thanks, this is outside the scope of threading, but can you please elaborate on the RefreshImage method and why you need to deal with the animation methods?
Nice post!
Speculation -> does the NS* code in RequestImage leak in your example? I think you need to wrap it in an NSAutoReleasePool unless you're on the main thread, where mt handles it for you.
I don't know if NsAutoReleasePool is required in Simon's case, but it won't be required in the next versin of MonoTouch since NSAutoReleasePoll will be dealt with in ThreadPool threads too.
When you say "next version" do you mean 1.2 (currently Beta 1) or some future version that is not yet available?
We just implemented image requests using the Simon's approach and both simulator and iPhone show "…class UIImage autoreleased with no pool in place – just leaking". I have tried to use NsAutoReleasePool to wrap the call but no success. Any suggestions?
Thanks
Figured it out after digging into different posts. The answer is to wrap RequestImage() with using (NSAutoreleasePool pool = new NSAutoreleasePool())
The code looks like this now,
private void RequestImage(object state)
{
using (NSAutoreleasePool pool = new NSAutoreleasePool())
{
…
}
}
Is there a way to manually dispose the necessary objects instead of using NSAutoreleasePool?
Also how are you finding the memory leak? I have a leak which i believe to be related to UIImage or it's inner CGImage but I'm not sure how to go about tracking it?
Johnny