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

Recent Photos

 

RT : I just pushed 3.1.0 with full support for iOS 4.1 and GameKit/GameCenter.
 

Simon say’s Project-D UITableViewCells autosize based on text height in MonoTouch. Here’s how!

Project-DWhile building the main story view in Project-D which uses a UITableView I needed to have each cell dynamically snap in their height based on the height of the content, which in this case is text.

To do this you must implement UITableViewDelegate along with UITableViewDataSource.

When implementing UITableViewDelegate the key override is GetHeightForRow(). This method lets us return what the height is for a particular row.

I’m doing 2 key things for performance reasons in GetHeightForRow() which are:

  1. Caching a font instance which represents the font I am using on the UITableViewCell
  2. Caching the cell height in my model, so that I only ever do this calculation once for a particular UITableViewCell

Basically what I am doing here is giving the measuring API a constraint width wise, and it will give me back a size of how high the text with this particular font consumes:

public class StoryViewTableController : UITableViewDelegate
{
    private static UIFont font                  = null;
    private StoryViewController viewController  = null;

    public StoryViewTableController(StoryViewController viewController)
    {
        this.viewController = viewController;
        if (font == null)
            font = UIFont.FromName("Helvetica", 14.0f);
    }

    public override float GetHeightForRow(UITableView tableView, NSIndexPath indexPath)
    {
        Story story = viewController.DataSource.Stories.Stories[indexPath.Row];

        if (story.CellSize.Height == 0)
        {
            story.Title = story.Title.Trim();
            story.Description = story.Description.Trim();

            story.TitleSize = tableView.StringSize(story.Title, font, new SizeF(237.0f, 1000.0f), UILineBreakMode.WordWrap);
            story.DescriptionSize = tableView.StringSize(story.Description, font, new SizeF(237.0f, 1000.0f), UILineBreakMode.WordWrap);

            SizeF sizeTotal = new SizeF(237.0f, story.TitleSize.Height + story.DescriptionSize.Height + 20);
            story.CellSize = sizeTotal;

            if (story.CellSize.Height < 108)
                story.CellSize = new SizeF(story.CellSize.Width, 108);
        }

        return story.CellSize.Height;
    }
}

In my UITableViewDataSource I am doing the same technique I showed in the tutorial
Let’s Build A Custom UITableViewCell and applying the new sizes that I store in the model to the cell itself.

public override UITableViewCell GetCell(UITableView tableView, MonoTouch.Foundation.NSIndexPath indexPath)
{
UITableViewCell cell = null;
StoryCellController controller = null;

cell = tableView.DequeueReusableCell("StoryCell");
if (cell == null)
{
controller = new StoryCellController();
NSBundle.MainBundle.LoadNib("StoryCellController", controller, null);
cell = controller.Cell;

cell.Tag = Environment.TickCount;
cellControllers.Add(cell.Tag, controller);
}
else
{
controller = cellControllers[cell.Tag];
}

Story story = stories.Stories[indexPath.Row];
controller.Title = story.Title.Trim();
controller.Description = story.Description.Trim();

    // Sizing the cell.
    RectangleF rectCell = cell.Frame;
    rectCell.Height = story.CellSize.Height;
    cell.Frame = rectCell;

    // Sizing the background image.
    RectangleF rectImageBackground = controller.ImageBackground.Frame;
    rectImageBackground.Height = story.CellSize.Height;
    controller.ImageBackground.Frame = rectImageBackground;

    // Sizing the LabelTitle.
    controller.LabelTitle.Frame = new RectangleF(
        new PointF(
            controller.LabelTitle.Frame.X,
            controller.LabelTitle.Frame.Y),
        story.TitleSize);

    // Sizing the LabelDescription.
    controller.LabelDescription.Frame = new RectangleF(
        new PointF(
            controller.LabelDescription.Frame.X,
            story.TitleSize.Height + 10),
        story.DescriptionSize);

return cell;
}

Hopefully this helps people out. As always you can find me on Twitter via @simongui and I appreciate any retweets!

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.

5 Responses to “Simon say’s Project-D UITableViewCells autosize based on text height in MonoTouch. Here’s how!”

  1. xusan 30 December 2009 at 9:07 am #

    thnk great job.
    would you share source code

  2. filip 30 December 2009 at 3:26 pm #

    hi, why are you applying a height for the cell in the GetCell method? the height is already set by the overridden delegate, is it?

    thanks for sharing information!

    filip

  3. Rob Gibbens 6 January 2010 at 4:25 am #

    Just wanted to say thank you for sharing what you've learned. It's been invaluable as I plod along behind your path.

  4. Steve 1 March 2010 at 9:54 pm #

    Great tip. Trying to follow this but wondering where you get these from:

    - story.CellSize
    - story.TitleSize
    - story.CellSize.Height
    - story.TitleSize.Height
    - story.DescriptionSize
    - story.DescriptionSize.Height

    Would be more helpful to post a working sln for download.

  5. Steve 3 March 2010 at 11:12 am #

    Managed t work this out, supurb bit of work!

    I realised that you store the objects as SizeF types in your data class. Once i did this it all slotted into place. The only other thing I had to do was set the labels as Public in my XIB designer file.


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+d29vX2NhdF9uYXZfMjI8L3N0cm9uZz4gLSBmYWxzZTwvbGk+PGxpPjxzdHJvbmc+d29vX2NhdF9uYXZfMzwvc3Ryb25nPiAtIHRydWU8L2xpPjxsaT48c3Ryb25nPndvb19jb250YWN0bWU8L3N0cm9uZz4gLSBTZWxlY3QgYSBwYWdlOjwvbGk+PGxpPjxzdHJvbmc+d29vX2N1c3RvbV9jc3M8L3N0cm9uZz4gLSA8L2xpPjxsaT48c3Ryb25nPndvb19jdXN0b21fZmF2aWNvbjwvc3Ryb25nPiAtIDwvbGk+PGxpPjxzdHJvbmc+d29vX2RlbGljaW91czwvc3Ryb25nPiAtIDwvbGk+PGxpPjxzdHJvbmc+d29vX2RpZ2c8L3N0cm9uZz4gLSA8L2xpPjxsaT48c3Ryb25nPndvb19mYWNlYm9vazwvc3Ryb25nPiAtIDwvbGk+PGxpPjxzdHJvbmc+d29vX2ZlYXR1cmVkX3Bvc3RzPC9zdHJvbmc+IC0gU2VsZWN0IGEgbnVtYmVyOjwvbGk+PGxpPjxzdHJvbmc+d29vX2ZlZWRidXJuZXJfaWQ8L3N0cm9uZz4gLSA8L2xpPjxsaT48c3Ryb25nPndvb19mZWVkYnVybmVyX3VybDwvc3Ryb25nPiAtIDwvbGk+PGxpPjxzdHJvbmc+d29vX2ZsaWNrcjwvc3Ryb25nPiAtIDQxMjY1ODU3QE4wNDwvbGk+PGxpPjxzdHJvbmc+d29vX2ZsaWNrcl9pZDwvc3Ryb25nPiAtIDQxMjY1ODU3QE4wNDwvbGk+PGxpPjxzdHJvbmc+d29vX2dvb2dsZV9hbmFseXRpY3M8L3N0cm9uZz4gLSA8c2NyaXB0IHR5cGU9XCJ0ZXh0L2phdmFzY3JpcHRcIj4NCnZhciBnYUpzSG9zdCA9ICgoXCJodHRwczpcIiA9PSBkb2N1bWVudC5sb2NhdGlvbi5wcm90b2NvbCkgPyBcImh0dHBzOi8vc3NsLlwiIDogXCJodHRwOi8vd3d3LlwiKTsNCmRvY3VtZW50LndyaXRlKHVuZXNjYXBlKFwiJTNDc2NyaXB0IHNyYz1cJ1wiICsgZ2FKc0hvc3QgKyBcImdvb2dsZS1hbmFseXRpY3MuY29tL2dhLmpzXCcgdHlwZT1cJ3RleHQvamF2YXNjcmlwdFwnJTNFJTNDL3NjcmlwdCUzRVwiKSk7DQo8L3NjcmlwdD4NCjxzY3JpcHQgdHlwZT1cInRleHQvamF2YXNjcmlwdFwiPg0KdHJ5ew0KdmFyIHBhZ2VUcmFja2VyID0gX2dhdC5fZ2V0VHJhY2tlcihcIlVBLTczNDg5NDMtMlwiKTsNCnBhZ2VUcmFja2VyLl90cmFja1BhZ2V2aWV3KCk7DQp9IGNhdGNoKGVycikge308L3NjcmlwdD48L2xpPjxsaT48c3Ryb25nPndvb19ncmF2YXRhcjwvc3Ryb25nPiAtIHRydWU8L2xpPjxsaT48c3Ryb25nPndvb19sYXN0Zm08L3N0cm9uZz4gLSA8L2xpPjxsaT48c3Ryb25nPndvb19sYXlvdXQ8L3N0cm9uZz4gLSBoZWFkZXItYWx0LnBocDwvbGk+PGxpPjxzdHJvbmc+d29vX2xpbmtlZGluPC9zdHJvbmc+IC0gPC9saT48bGk+PHN0cm9uZz53b29fbG9nbzwvc3Ryb25nPiAtIGh0dHA6Ly9zaW1vbi5udXJlYWxpdHkuY2Evd3AtY29udGVudC93b29fdXBsb2Fkcy80LWxvZ28ucG5nPC9saT48bGk+PHN0cm9uZz53b29fbWFudWFsPC9zdHJvbmc+IC0gaHR0cDovL3d3dy53b290aGVtZXMuY29tL3N1cHBvcnQvdGhlbWUtZG9jdW1lbnRhdGlvbi90aGljay88L2xpPjxsaT48c3Ryb25nPndvb19uYXZfZXhjbHVkZTwvc3Ryb25nPiAtIDwvbGk+PGxpPjxzdHJvbmc+d29vX290aGVyX2VudHJpZXM8L3N0cm9uZz4gLSAxMDwvbGk+PGxpPjxzdHJvbmc+d29vX3Bvc3Rfc2l6ZTwvc3Ryb25nPiAtIGZhbHNlPC9saT48bGk+PHN0cm9uZz53b29fcmVzaXplPC9zdHJvbmc+IC0gZmFsc2U8L2xpPjxsaT48c3Ryb25nPndvb19zY3JvbGxlcl9wb3N0czwvc3Ryb25nPiAtIFNlbGVjdCBhIG51bWJlcjo8L2xpPjxsaT48c3Ryb25nPndvb19zaG9ydG5hbWU8L3N0cm9uZz4gLSB3b288L2xpPjxsaT48c3Ryb25nPndvb19zaWRlYmFyX2FkX2hyZWZfMTwvc3Ryb25nPiAtIGh0dHA6Ly93d3cud29vdGhlbWVzLmNvbTwvbGk+PGxpPjxzdHJvbmc+d29vX3NpZGViYXJfYWRfaHJlZl8yPC9zdHJvbmc+IC0gaHR0cDovL3d3dy53b290aGVtZXMuY29tPC9saT48bGk+PHN0cm9uZz53b29fc2lkZWJhcl9hZF9ocmVmXzM8L3N0cm9uZz4gLSBodHRwOi8vd3d3Lndvb3RoZW1lcy5jb208L2xpPjxsaT48c3Ryb25nPndvb19zaWRlYmFyX2FkX2hyZWZfNDwvc3Ryb25nPiAtIGh0dHA6Ly93d3cud29vdGhlbWVzLmNvbTwvbGk+PGxpPjxzdHJvbmc+d29vX3NpZGViYXJfYWRfaW1nXzE8L3N0cm9uZz4gLSBodHRwOi8vd3d3Lndvb3RoZW1lcy5jb20vYWRzL3dvb3RoZW1lcy0xMjV4MTI1LTEuZ2lmPC9saT48bGk+PHN0cm9uZz53b29fc2lkZWJhcl9hZF9pbWdfMjwvc3Ryb25nPiAtIGh0dHA6Ly93d3cud29vdGhlbWVzLmNvbS9hZHMvd29vdGhlbWVzLTEyNXgxMjUtMi5naWY8L2xpPjxsaT48c3Ryb25nPndvb19zaWRlYmFyX2FkX2ltZ18zPC9zdHJvbmc+IC0gaHR0cDovL3d3dy53b290aGVtZXMuY29tL2Fkcy93b290aGVtZXMtMTI1eDEyNS0zLmdpZjwvbGk+PGxpPjxzdHJvbmc+d29vX3NpZGViYXJfYWRfaW1nXzQ8L3N0cm9uZz4gLSBodHRwOi8vd3d3Lndvb3RoZW1lcy5jb20vYWRzL3dvb3RoZW1lcy0xMjV4MTI1LTQuZ2lmPC9saT48bGk+PHN0cm9uZz53b29fc2lkZV9pbWFnZTwvc3Ryb25nPiAtIGh0dHA6Ly9zaW1vbi5udXJlYWxpdHkuY2Evd3AtY29udGVudC9hZHMvYWQtMTIweDI0MC5qcGc8L2xpPjxsaT48c3Ryb25nPndvb19zaWRlX3VybDwvc3Ryb25nPiAtIGh0dHA6Ly93d3cud29vdGhlbWVzLmNvbTwvbGk+PGxpPjxzdHJvbmc+d29vX3NpbmdsZV90aHVtYjwvc3Ryb25nPiAtIGZhbHNlPC9saT48bGk+PHN0cm9uZz53b29fc3R1bWJsZTwvc3Ryb25nPiAtIDwvbGk+PGxpPjxzdHJvbmc+d29vX3RleHRsb2dvPC9zdHJvbmc+IC0gZmFsc2U8L2xpPjxsaT48c3Ryb25nPndvb190aGVtZW5hbWU8L3N0cm9uZz4gLSBUSGlDSzwvbGk+PGxpPjxzdHJvbmc+d29vX3R3aXR0ZXI8L3N0cm9uZz4gLSBzaW1vbmd1aTwvbGk+PGxpPjxzdHJvbmc+d29vX3VwbG9hZHM8L3N0cm9uZz4gLSBhOjI6e2k6MDtzOjU5OiJodHRwOi8vc2ltb24ubnVyZWFsaXR5LmNhL3dwLWNvbnRlbnQvd29vX3VwbG9hZHMvNC1sb2dvLnBuZyI7aToxO3M6NTk6Imh0dHA6Ly9zaW1vbi5udXJlYWxpdHkuY2Evd3AtY29udGVudC93b29fdXBsb2Fkcy8zLWxvZ28ucG5nIjt9PC9saT48bGk+PHN0cm9uZz53b29feW91dHViZTwvc3Ryb25nPiAtIDwvbGk+PC91bD4=