Creating an animated spinner in a Xamarin.iOS (MonoTouch) UIImageView

Background

I’m well into my first week of building the Sierra Trading Post first iOS app using Xamarin.iOS and it has been a fun ride so far. One of the first things needed was a system for showing a loading image while asynchronously retrieving the final image with a web request.

Attempt 1

Xamarin has a recipe for using a UIImageView‘s AnimationImages to make a spinner.

UIImageView someImageView = new UIImageView();
someImageView.AnimationImages = new UIImage[] {
    UIImage.FromBundle("Spinning Circle_1.png"),
    UIImage.FromBundle("Spinning Circle_2.png"),
    UIImage.FromBundle("Spinning Circle_3.png"),
    UIImage.FromBundle("Spinning Circle_4.png"),
};
someImageView.AnimationRepeatCount = 0; // Repeat forever.
someImageView.AnimationDuration = 1.0; // Every 1s.
someImageView.StartAnimating();

It may be possible to make this work, but it wasn’t quite what I needed. This seems to be more of an image rotation than an animation. As a result, it creates a jerky animation between the various images equally distributed over the AnimationDuration you set.

After this, attempts to find some ideas for a better solution lead me to about a hundred lines of code that proved a difficult to consume, involving a CGBitmapContext and CGAffineTransform.MakRotation. (To be fair, this code isn’t doing something as simple as what I want to do.) Hoping to avoid that, I simply added four more rotation positions into the list. It would probably take many more to make it appear smooth. Any more than that and I really didn’t want to bloat my project with minute rotations of the same PNG. Back to Google I went.

Solution

After enough poking around some slightly related Google results, I began to understand enough of CABasicAnimation to see how it could work for the job. You create the desired animation and add an instance of UIImageView.Layer.

    // Image to be rotated (in this case, found in the project as "/Assets/Images/loading_icon.png").
    UIImageView someImageView = new UIImageView(UIImage.FromBundle("Assets/Images/loading_icon"));
    CABasicAnimation rotationAnimation = CABasicAnimation.FromKeyPath("transform.rotation");
    rotationAnimation.To = NSNumber.FromDouble(Math.PI * 2); // full rotation (in radians)
    rotationAnimation.RepeatCount = int.MaxValue; // repeat forever
    rotationAnimation.Duration = 1;
    // Give the added animation a key for referencing it later (to remove, in this case).
    someImageView.Layer.AddAnimation(rotationAnimation, "rotationAnimation");

The main part of this is the simple rotation CABasicAnimation that is applied to a Layer of a UIImageView. In this case, it is set to do a full rotation (accepted in radians) every one second through a very large number of repetitions. The repetitions is actually one oddity in the switch to this new method. When you set UIImageView.AnimationRepeatCount, you can set it to zero to make it loop forever. Oddly, a CABasicAnimation.RepeatCount set to zero is the same as one, and it loops a single time before stopping.

Code (Example doing for a bunch of UITableView cells)

static NSString key = new NSString("somecellkey");
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath) {
    UITableViewCell cell = tableView.DequeueReusableCell(key);
    if (cell == null) {
        cell = new UITableViewCell(UITableViewCellStyle.Default, key);
    }

    // Image to be rotated (in this case, found in the project as "/Assets/Images/loading_icon.png").
    cell.ImageView.Image = UIImage.FromBundle("Assets/Images/loading_icon");
    CABasicAnimation rotationAnimation = CABasicAnimation.FromKeyPath("transform.rotation");
    rotationAnimation.To = NSNumber.FromDouble(Math.PI * 2); // full rotation (in radians)
    rotationAnimation.RepeatCount = int.MaxValue; // repeat forever
    rotationAnimation.Duration = 1;
    // Give the added animation a key for referencing it later (to remove, in this case).
    cell.ImageView.Layer.AddAnimation(rotationAnimation, "rotationAnimation");

    // Do your lazy-loading of the image (blog post coming soon...maybe).
    ...

    // For a good time, you can keep rotating your final, lazy-loaded image by not calling this line.
    cell.ImageView.Layer.RemoveAnimation("rotationAnimation");

    // Do the rest of your visual stuff to the cell.
    ...

    return cell;
}

More Code

If you want a quick demo application of the differences, check out the GitHub repo I put together [and finally got around to sharing]. It is a simple demo of two UIImageViews that implement the two methods here. Clicking anywhere will toggle between the two.

Here’s a quick video snippet of the demo code running.

Mono for Android: “aapt.exe” exited with code 1

TL;DR

Also available in TL;SO (too long; Stack Overflow) flavor.

Getting this error: "aapt.exe" exited with code 1?

Do you have any files in your Mono for Android solution that are being packaged together with the app (e.g., “AndroidResource” build action)?

If so, make sure they don’t have anything but letters, numbers, periods, and underscores ([a-z0-9_.]) in their names.

Details

I am still getting my feet wet with Mono for Android (MfA) development. One of my first projects was a flashlight app for my Galaxy Nexus that introduced me to a couple Android/MfA development quirks. One concept in app development that I would like to explore is long-running tasks. How do I keep something going after the user has switched off to something else?

Greg Shackles has a new Visual Studio Magazine article on Background Services in MfA on just that topic. He creates a background service for playing an MP3 file using a standard Android service.

In his sample project, he sets up the service to play some Creative Commons Nine Inch Nails music.

I happen to have a good collection of my own Nine Inch Nails music. As I put the code together in my own project, I grabbed one such MP3 file, named “02 1,000,000.mp3”, and added it with the “AndroidResource” Build Action. Unfortunately, hitting Ctrl+Shift+B resulted in a lovely error: "aapt.exe" exited with code 1.

If you go to the Visual Studio output window, you don’t find much else of value.

Fortunately, you can get more verbose output from MSBuild if you ask for it. In the Visual Studio Options, go to “Projects and Solutions” then “Build and Run”. Switch the “MSBuild project build output verbosity” from “Minimal” to “Normal”. After I built the project again, I got a little clearer message: res\raw\02 1,000,000.mp3: Invalid file name: must contain only [a-z0-9_.].

A quick name change to something within the [a-z0-9_.] range and the project built beautifully. While this new sample project is just a horrible version of the baked-in music application, that definitely isn’t the point. It is definitely nice to have some code written about a concept before you find yourself needing it the first time.

Where did that JSON field go? Serializing IHtmlString to JSON.

TL;DR

If your brain consumes Stack Overflow questions better than blog posts, go see “How do I serialize IHtmlString to JSON with Json.NET?” over there.

IHtmlString doesn’t play nicely with JSON serialization

If you have an IHtmlString in your JSON (regardless of arguments against putting raw HTML in JSON), you will probably need to customize the serialization to get the HTML out of that variable. In fact, the serialization will probably be invalid compared to what you expect; it does make sense if you think about how the serialization process works.

Fortunately, putting together a quick Json.NET JsonConverter will make the issue go away.

What you might expect from some object containing an IHtmlString variable named x:

{ x = "some <span>text</span>" }

What default .NET JavaScript serialization and default Json.NET serialization will give you for said object:

{ x = { } }

How to fix it with Json.NET:

public class IHtmlStringConverter : Newtonsoft.Json.JsonConverter {
    public override bool CanConvert(Type objectType) {
        return typeof(IHtmlString).IsAssignableFrom(objectType);
    }
    ...
    public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) {
        IHtmlString source = value as IHtmlString;
        if (source == null) {
            return;
        }
        writer.WriteValue(source.ToString());
    }
}

Background

While working on some random API, we noticed one of our JSON fields went from being a long string containing raw HTML (legacy baggage) to an empty object: SomeFieldWithHtml: {}.

While the API JSON-generating code hadn’t changed, that field was pulled from code shared by our MVC website. It seems that field was converted directly to an IHtmlString to avoid doubly-encoding it in an MVC view. If you ever have the same issue, you can avoid this issue entirely by leaving the source as a string and doing a quick MvcHtmlString.Create(x) on it before sending it to your view/view-model.

Code

For the full source used in this post, including an example MVC project where a controller has action methods to test all the variations here, head over to this post’s GitHub repository.