My phone is faster than my first PC, and faster than my first cable modem

Recent Photos

 

Holy crap wasn't kidding when he said Wendy's large drink was freaking huge.
 

Simon says, MonoTouch downloading images like AppStore app in less than 50 lines with CoreAnimation Transitions!

Project-D-02_thumb

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();
}

This website uses IntenseDebate comments, but they are not currently loaded because either your browser doesn't support JavaScript, or they didn't load fast enough.

4 Responses to “Simon says, MonoTouch downloading images like AppStore app in less than 50 lines with CoreAnimation Transitions!”

  1. Johnny 30 October 2009 at 12:55 am #

    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?

  2. Mike 31 October 2009 at 2:03 pm #

    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.

    • Gonzalo 1 November 2009 at 4:55 pm #

      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.

      • hemp 3 November 2009 at 2:15 am #

        When you say "next version" do you mean 1.2 (currently Beta 1) or some future version that is not yet available?


Leave a Reply

 
PHVsPjxsaT48c3Ryb25nPndvb19hYm91dDwvc3Ryb25nPiAtIFNvZnR3YXJlIERldmVsb3BlciwgZ2FkZ2V0IGdlZWsgYW5kIG1vYmlsaXR5IGp1bmt5LjwvbGk+PGxpPjxzdHJvbmc+d29vX2Fib3V0bGluazwvc3Ryb25nPiAtIDwvbGk+PGxpPjxzdHJvbmc+d29vX2Fib3V0X2J1dHRvbjwvc3Ryb25nPiAtIDwvbGk+PGxpPjxzdHJvbmc+d29vX2Fib3V0X2hlYWRlcjwvc3Ryb25nPiAtIDwvbGk+PGxpPjxzdHJvbmc+d29vX2Fib3V0X3Bob3RvPC9zdHJvbmc+IC0gPC9saT48bGk+PHN0cm9uZz53b29fYWJvdXRfdGV4dDwvc3Ryb25nPiAtIDwvbGk+PGxpPjxzdHJvbmc+d29vX2Fkczwvc3Ryb25nPiAtIGZhbHNlPC9saT48bGk+PHN0cm9uZz53b29fYWRfMzAwX2Fkc2Vuc2U8L3N0cm9uZz4gLSA8L2xpPjxsaT48c3Ryb25nPndvb19hZF8zMDBfaW1hZ2U8L3N0cm9uZz4gLSBodHRwOi8vd3d3Lndvb3RoZW1lcy5jb20vYWRzLzMwMHgyNTBhLmpwZzwvbGk+PGxpPjxzdHJvbmc+d29vX2FkXzMwMF91cmw8L3N0cm9uZz4gLSBodHRwOi8vd3d3Lndvb3RoZW1lcy5jb208L2xpPjxsaT48c3Ryb25nPndvb19hZF9jb250ZW50PC9zdHJvbmc+IC0gZmFsc2U8L2xpPjxsaT48c3Ryb25nPndvb19hZF9jb250ZW50X2Fkc2Vuc2U8L3N0cm9uZz4gLSA8L2xpPjxsaT48c3Ryb25nPndvb19hZF9jb250ZW50X2ltYWdlPC9zdHJvbmc+IC0gaHR0cDovL3d3dy53b290aGVtZXMuY29tL2Fkcy83Mjh4OTBhLmpwZzwvbGk+PGxpPjxzdHJvbmc+d29vX2FkX2NvbnRlbnRfdXJsPC9zdHJvbmc+IC0gaHR0cDovL3d3dy53b290aGVtZXMuY29tPC9saT48bGk+PHN0cm9uZz53b29fYWRfZm9vdGVyPC9zdHJvbmc+IC0gZmFsc2U8L2xpPjxsaT48c3Ryb25nPndvb19hZF9mb290ZXJfYWRzZW5zZTwvc3Ryb25nPiAtIDwvbGk+PGxpPjxzdHJvbmc+d29vX2FkX2Zvb3Rlcl9pbWFnZTwvc3Ryb25nPiAtIGh0dHA6Ly93d3cud29vdGhlbWVzLmNvbS9hZHMvd29vdGhlbWVzLTQ2OHg2MC0yLmdpZjwvbGk+PGxpPjxzdHJvbmc+d29vX2FkX2Zvb3Rlcl91cmw8L3N0cm9uZz4gLSBodHRwOi8vd3d3Lndvb3RoZW1lcy5jb208L2xpPjxsaT48c3Ryb25nPndvb19hZF9oZWFkZXI8L3N0cm9uZz4gLSBmYWxzZTwvbGk+PGxpPjxzdHJvbmc+d29vX2FkX2hlYWRlcl9jb2RlPC9zdHJvbmc+IC0gPC9saT48bGk+PHN0cm9uZz53b29fYWRfaGVhZGVyX2ltYWdlPC9zdHJvbmc+IC0gaHR0cDovL3d3dy53b290aGVtZXMuY29tL2Fkcy80Njh4NjBhLmpwZzwvbGk+PGxpPjxzdHJvbmc+d29vX2FkX2hlYWRlcl91cmw8L3N0cm9uZz4gLSBodHRwOi8vd3d3Lndvb3RoZW1lcy5jb208L2xpPjxsaT48c3Ryb25nPndvb19hZF9pbWFnZV8xPC9zdHJvbmc+IC0gaHR0cDovL3NpbW9uLm51cmVhbGl0eS5jYS93cC1jb250ZW50L2Fkcy9hZC0xMjV4MTI1LmpwZzwvbGk+PGxpPjxzdHJvbmc+d29vX2FkX2ltYWdlXzI8L3N0cm9uZz4gLSBodHRwOi8vc2ltb24ubnVyZWFsaXR5LmNhL3dwLWNvbnRlbnQvYWRzL2FkLTEyNXgxMjUuanBnPC9saT48bGk+PHN0cm9uZz53b29fYWRfaW1hZ2VfMzwvc3Ryb25nPiAtIGh0dHA6Ly9zaW1vbi5udXJlYWxpdHkuY2Evd3AtY29udGVudC9hZHMvYWQtMTI1eDEyNS5qcGc8L2xpPjxsaT48c3Ryb25nPndvb19hZF9pbWFnZV80PC9zdHJvbmc+IC0gaHR0cDovL3NpbW9uLm51cmVhbGl0eS5jYS93cC1jb250ZW50L2Fkcy9hZC0xMjV4MTI1LmpwZzwvbGk+PGxpPjxzdHJvbmc+d29vX2FkX3RvcDwvc3Ryb25nPiAtIGZhbHNlPC9saT48bGk+PHN0cm9uZz53b29fYWRfdG9wX2Fkc2Vuc2U8L3N0cm9uZz4gLSA8L2xpPjxsaT48c3Ryb25nPndvb19hZF90b3BfaW1hZ2U8L3N0cm9uZz4gLSBodHRwOi8vd3d3Lndvb3RoZW1lcy5jb20vYWRzLzQ2OHg2MGEuanBnPC9saT48bGk+PHN0cm9uZz53b29fYWRfdG9wX3VybDwvc3Ryb25nPiAtIGh0dHA6Ly93d3cud29vdGhlbWVzLmNvbTwvbGk+PGxpPjxzdHJvbmc+d29vX2FkX3VybF8xPC9zdHJvbmc+IC0gaHR0cDovL2V4YW1wbGUuY29tL2Fkcy9hZDFfZGVzdGluYXRpb24uaHRtbDwvbGk+PGxpPjxzdHJvbmc+d29vX2FkX3VybF8yPC9zdHJvbmc+IC0gaHR0cDovL2V4YW1wbGUuY29tL2Fkcy9hZDFfZGVzdGluYXRpb24uaHRtbDwvbGk+PGxpPjxzdHJvbmc+d29vX2FkX3VybF8zPC9zdHJvbmc+IC0gaHR0cDovL2V4YW1wbGUuY29tL2Fkcy9hZDFfZGVzdGluYXRpb24uaHRtbDwvbGk+PGxpPjxzdHJvbmc+d29vX2FkX3VybF80PC9zdHJvbmc+IC0gaHR0cDovL2V4YW1wbGUuY29tL2Fkcy9hZDFfZGVzdGluYXRpb24uaHRtbDwvbGk+PGxpPjxzdHJvbmc+d29vX2FsdF9jb2xvdXJzPC9zdHJvbmc+IC0gZGVmYXVsdC5jc3M8L2xpPjxsaT48c3Ryb25nPndvb19hbHRfc3R5bGVzaGVldDwvc3Ryb25nPiAtIGNsZWFuLWxpZ2h0LmNzczwvbGk+PGxpPjxzdHJvbmc+d29vX2FyY2hpdmVzPC9zdHJvbmc+IC0gPC9saT48bGk+PHN0cm9uZz53b29fYXNpZGVzX2NhdGVnb3J5PC9zdHJvbmc+IC0gU2VsZWN0IGEgY2F0ZWdvcnk6PC9saT48bGk+PHN0cm9uZz53b29fYXNpZGVzX2VudHJpZXM8L3N0cm9uZz4gLSBTZWxlY3QgYSBudW1iZXI6PC9saT48bGk+PHN0cm9uZz53b29fYXV0b19pbWc8L3N0cm9uZz4gLSBmYWxzZTwvbGk+PGxpPjxzdHJvbmc+d29vX2Jpbzwvc3Ryb25nPiAtIDwvbGk+PGxpPjxzdHJvbmc+d29vX2Jsb2dfY2F0PC9zdHJvbmc+IC0gPC9saT48bGk+PHN0cm9uZz53b29fYmxvZ19uYXZpZ2F0aW9uPC9zdHJvbmc+IC0gZmFsc2U8L2xpPjxsaT48c3Ryb25nPndvb19ibG9nX3Blcm1hbGluazwvc3Ryb25nPiAtIDwvbGk+PGxpPjxzdHJvbmc+d29vX2Jsb2dfc3VibmF2aWdhdGlvbjwvc3Ryb25nPiAtIGZhbHNlPC9saT48bGk+PHN0cm9uZz53b29fYnV0dG9uX2xpbms8L3N0cm9uZz4gLSA8L2xpPjxsaT48c3Ryb25nPndvb19jYXRfYm94XzE8L3N0cm9uZz4gLSBmYWxzZTwvbGk+PGxpPjxzdHJvbmc+d29vX2NhdF9ib3hfMV9pbWFnZTwvc3Ryb25nPiAtIDwvbGk+PGxpPjxzdHJvbmc+d29vX2NhdF9jb2xvcl8xPC9zdHJvbmc+IC0gPC9saT48bGk+PHN0cm9uZz53b29fY2F0X25hdl8xPC9zdHJvbmc+IC0gZmFsc2U8L2xpPjxsaT48c3Ryb25nPndvb19jYXRfbmF2XzEwPC9zdHJvbmc+IC0gdHJ1ZTwvbGk+PGxpPjxzdHJvbmc+d29vX2NhdF9uYXZfMTY8L3N0cm9uZz4gLSB0cnVlPC9saT48bGk+PHN0cm9uZz53b29fY2F0X25hdl8xNzwvc3Ryb25nPiAtIHRydWU8L2xpPjxsaT48c3Ryb25nPndvb19jYXRfbmF2XzE4PC9zdHJvbmc+IC0gZmFsc2U8L2xpPjxsaT48c3Ryb25nPndvb19jYXRfbmF2XzIxPC9zdHJvbmc+IC0gdHJ1ZTwvbGk+PGxpPjxzdHJvbmc+d29vX2NhdF9uYXZfMzwvc3Ryb25nPiAtIHRydWU8L2xpPjxsaT48c3Ryb25nPndvb19jb250YWN0bWU8L3N0cm9uZz4gLSBTZWxlY3QgYSBwYWdlOjwvbGk+PGxpPjxzdHJvbmc+d29vX2N1c3RvbV9jc3M8L3N0cm9uZz4gLSA8L2xpPjxsaT48c3Ryb25nPndvb19jdXN0b21fZmF2aWNvbjwvc3Ryb25nPiAtIDwvbGk+PGxpPjxzdHJvbmc+d29vX2RlbGljaW91czwvc3Ryb25nPiAtIDwvbGk+PGxpPjxzdHJvbmc+d29vX2RpZ2c8L3N0cm9uZz4gLSA8L2xpPjxsaT48c3Ryb25nPndvb19mYWNlYm9vazwvc3Ryb25nPiAtIDwvbGk+PGxpPjxzdHJvbmc+d29vX2ZlYXR1cmVkX3Bvc3RzPC9zdHJvbmc+IC0gU2VsZWN0IGEgbnVtYmVyOjwvbGk+PGxpPjxzdHJvbmc+d29vX2ZlZWRidXJuZXJfaWQ8L3N0cm9uZz4gLSA8L2xpPjxsaT48c3Ryb25nPndvb19mZWVkYnVybmVyX3VybDwvc3Ryb25nPiAtIDwvbGk+PGxpPjxzdHJvbmc+d29vX2ZsaWNrcjwvc3Ryb25nPiAtIDQxMjY1ODU3QE4wNDwvbGk+PGxpPjxzdHJvbmc+d29vX2ZsaWNrcl9pZDwvc3Ryb25nPiAtIDQxMjY1ODU3QE4wNDwvbGk+PGxpPjxzdHJvbmc+d29vX2dvb2dsZV9hbmFseXRpY3M8L3N0cm9uZz4gLSA8c2NyaXB0IHR5cGU9XCJ0ZXh0L2phdmFzY3JpcHRcIj4NCnZhciBnYUpzSG9zdCA9ICgoXCJodHRwczpcIiA9PSBkb2N1bWVudC5sb2NhdGlvbi5wcm90b2NvbCkgPyBcImh0dHBzOi8vc3NsLlwiIDogXCJodHRwOi8vd3d3LlwiKTsNCmRvY3VtZW50LndyaXRlKHVuZXNjYXBlKFwiJTNDc2NyaXB0IHNyYz1cJ1wiICsgZ2FKc0hvc3QgKyBcImdvb2dsZS1hbmFseXRpY3MuY29tL2dhLmpzXCcgdHlwZT1cJ3RleHQvamF2YXNjcmlwdFwnJTNFJTNDL3NjcmlwdCUzRVwiKSk7DQo8L3NjcmlwdD4NCjxzY3JpcHQgdHlwZT1cInRleHQvamF2YXNjcmlwdFwiPg0KdHJ5IHsNCnZhciBwYWdlVHJhY2tlciA9IF9nYXQuX2dldFRyYWNrZXIoXCJVQS0xMDE0MzgzMi0xXCIpOw0KcGFnZVRyYWNrZXIuX3RyYWNrUGFnZXZpZXcoKTsNCn0gY2F0Y2goZXJyKSB7fTwvc2NyaXB0PjwvbGk+PGxpPjxzdHJvbmc+d29vX2dyYXZhdGFyPC9zdHJvbmc+IC0gdHJ1ZTwvbGk+PGxpPjxzdHJvbmc+d29vX2xhc3RmbTwvc3Ryb25nPiAtIDwvbGk+PGxpPjxzdHJvbmc+d29vX2xheW91dDwvc3Ryb25nPiAtIGhlYWRlci1hbHQucGhwPC9saT48bGk+PHN0cm9uZz53b29fbGlua2VkaW48L3N0cm9uZz4gLSA8L2xpPjxsaT48c3Ryb25nPndvb19sb2dvPC9zdHJvbmc+IC0gaHR0cDovL3NpbW9uLm51cmVhbGl0eS5jYS93cC1jb250ZW50L3dvb191cGxvYWRzLzQtbG9nby5wbmc8L2xpPjxsaT48c3Ryb25nPndvb19tYW51YWw8L3N0cm9uZz4gLSBodHRwOi8vd3d3Lndvb3RoZW1lcy5jb20vc3VwcG9ydC90aGVtZS1kb2N1bWVudGF0aW9uL3RoaWNrLzwvbGk+PGxpPjxzdHJvbmc+d29vX25hdl9leGNsdWRlPC9zdHJvbmc+IC0gPC9saT48bGk+PHN0cm9uZz53b29fb3RoZXJfZW50cmllczwvc3Ryb25nPiAtIDEwPC9saT48bGk+PHN0cm9uZz53b29fcG9zdF9zaXplPC9zdHJvbmc+IC0gZmFsc2U8L2xpPjxsaT48c3Ryb25nPndvb19yZXNpemU8L3N0cm9uZz4gLSBmYWxzZTwvbGk+PGxpPjxzdHJvbmc+d29vX3Njcm9sbGVyX3Bvc3RzPC9zdHJvbmc+IC0gU2VsZWN0IGEgbnVtYmVyOjwvbGk+PGxpPjxzdHJvbmc+d29vX3Nob3J0bmFtZTwvc3Ryb25nPiAtIHdvbzwvbGk+PGxpPjxzdHJvbmc+d29vX3NpZGViYXJfYWRfaHJlZl8xPC9zdHJvbmc+IC0gaHR0cDovL3d3dy53b290aGVtZXMuY29tPC9saT48bGk+PHN0cm9uZz53b29fc2lkZWJhcl9hZF9ocmVmXzI8L3N0cm9uZz4gLSBodHRwOi8vd3d3Lndvb3RoZW1lcy5jb208L2xpPjxsaT48c3Ryb25nPndvb19zaWRlYmFyX2FkX2hyZWZfMzwvc3Ryb25nPiAtIGh0dHA6Ly93d3cud29vdGhlbWVzLmNvbTwvbGk+PGxpPjxzdHJvbmc+d29vX3NpZGViYXJfYWRfaHJlZl80PC9zdHJvbmc+IC0gaHR0cDovL3d3dy53b290aGVtZXMuY29tPC9saT48bGk+PHN0cm9uZz53b29fc2lkZWJhcl9hZF9pbWdfMTwvc3Ryb25nPiAtIGh0dHA6Ly93d3cud29vdGhlbWVzLmNvbS9hZHMvd29vdGhlbWVzLTEyNXgxMjUtMS5naWY8L2xpPjxsaT48c3Ryb25nPndvb19zaWRlYmFyX2FkX2ltZ18yPC9zdHJvbmc+IC0gaHR0cDovL3d3dy53b290aGVtZXMuY29tL2Fkcy93b290aGVtZXMtMTI1eDEyNS0yLmdpZjwvbGk+PGxpPjxzdHJvbmc+d29vX3NpZGViYXJfYWRfaW1nXzM8L3N0cm9uZz4gLSBodHRwOi8vd3d3Lndvb3RoZW1lcy5jb20vYWRzL3dvb3RoZW1lcy0xMjV4MTI1LTMuZ2lmPC9saT48bGk+PHN0cm9uZz53b29fc2lkZWJhcl9hZF9pbWdfNDwvc3Ryb25nPiAtIGh0dHA6Ly93d3cud29vdGhlbWVzLmNvbS9hZHMvd29vdGhlbWVzLTEyNXgxMjUtNC5naWY8L2xpPjxsaT48c3Ryb25nPndvb19zaWRlX2ltYWdlPC9zdHJvbmc+IC0gaHR0cDovL3NpbW9uLm51cmVhbGl0eS5jYS93cC1jb250ZW50L2Fkcy9hZC0xMjB4MjQwLmpwZzwvbGk+PGxpPjxzdHJvbmc+d29vX3NpZGVfdXJsPC9zdHJvbmc+IC0gaHR0cDovL3d3dy53b290aGVtZXMuY29tPC9saT48bGk+PHN0cm9uZz53b29fc2luZ2xlX3RodW1iPC9zdHJvbmc+IC0gZmFsc2U8L2xpPjxsaT48c3Ryb25nPndvb19zdHVtYmxlPC9zdHJvbmc+IC0gPC9saT48bGk+PHN0cm9uZz53b29fdGV4dGxvZ288L3N0cm9uZz4gLSBmYWxzZTwvbGk+PGxpPjxzdHJvbmc+d29vX3RoZW1lbmFtZTwvc3Ryb25nPiAtIFRIaUNLPC9saT48bGk+PHN0cm9uZz53b29fdHdpdHRlcjwvc3Ryb25nPiAtIHNpbW9uZ3VpPC9saT48bGk+PHN0cm9uZz53b29fdXBsb2Fkczwvc3Ryb25nPiAtIGE6Mjp7aTowO3M6NTk6Imh0dHA6Ly9zaW1vbi5udXJlYWxpdHkuY2Evd3AtY29udGVudC93b29fdXBsb2Fkcy80LWxvZ28ucG5nIjtpOjE7czo1OToiaHR0cDovL3NpbW9uLm51cmVhbGl0eS5jYS93cC1jb250ZW50L3dvb191cGxvYWRzLzMtbG9nby5wbmciO308L2xpPjxsaT48c3Ryb25nPndvb195b3V0dWJlPC9zdHJvbmc+IC0gPC9saT48L3VsPg==