Xamarin.Controls – Creating Your Own iOS Markdown UILabel

In a previous post, I talked about a Xamarin.Forms control I put on GitHub to allow you to display Markdown strings properly. It worked by parsing the markdown into html, then using the custom renderer to display the html string in a TextView for Android and a UILabel for iOS.

However, we are not always using Xamarin.Forms, so let’s take a look at achieving the same functionality with just Xamarin.iOS.

If you’re looking for the same type of solution, check out my previous post: Xamarin.Controls – Creating Your Own Android Markdown TextView

We’ll break it down into a few parts:

  1. Parse a markdown string into an html string
  2. Parse the html string into an NSAttributedString
  3. Set the AttributedText of the UILabel

Parsing Markdown

This is traditionally the most difficult part. However, our community is awesome and open sourced a Markdown processor with an MIT license (so use it freely!).

I won’t put the actual code in here because it is overwhelmingly long, but here is a link to it:

https://github.com/SuavePirate/MarkdownTextView/blob/master/src/Forms/SPControls.MarkdownTextView/SPControls.MarkdownTextView/Markdown.cs

Note that this is portable, so you can use it in a PCL without a problem and share it between your platforms.

Now that we have our means of processing the Markdown, let’s create some extension methods to make it easier to parse and do some extra processing like cleaning up our tags, line breaks, etc.

#region MARKDOWN STYLES
private const string ORIGINAL_PATTERN_BEGIN = "<code>";
private const string ORIGINAL_PATTERN_END = "</code>";
private const string PARSED_PATTERN_BEGIN = "<font color=\"#888888\" face=\"monospace\"><tt>";
private const string PARSED_PATTERN_END = "</tt></font>";
 
#endregion
 
public static string ToHtml(this string markdownText)
{
    var markdownOptions = new MarkdownOptions
    {
        AutoHyperlink = true,
        AutoNewlines = false,
        EncodeProblemUrlCharacters = false,
        LinkEmails = true,
        StrictBoldItalic = true
    };
    var markdown = new Markdown(markdownOptions);
    var htmlContent = markdown.Transform(markdownText);
    var regex = new Regex("\n");
    htmlContent = regex.Replace(htmlContent, "<br/>");
 
    var html = htmlContent.HtmlWrapped();
    var regex2 = new Regex("\r");
    html = regex.Replace(html, string.Empty);
    html = regex2.Replace(html, string.Empty);
    return html;
}
 
///
<summary>
/// Wrap html with a full html tag
/// </summary>
/// <param name="html"></param>
/// <returns></returns>
public static string HtmlWrapped(this string html)
{
    if (!html.StartsWith("<html>") || !html.EndsWith("</html>"))
    {
        html = $"<html><body>{html}</body></html>";
    }
    return html;
}
 
///<summary>
/// Parses html with code or pre tags and gives them proper
/// styled spans so that Android can parse it properly
/// </summary>
/// <param name="htmlText">The html string</param>
/// <returns>The html string with parsed code tags</returns>
public static string ParseCodeTags(this string htmlText)
{
    if (htmlText.IndexOf(ORIGINAL_PATTERN_BEGIN) < 0) return htmlText;
    var regex = new Regex(ORIGINAL_PATTERN_BEGIN);
    var regex2 = new Regex(ORIGINAL_PATTERN_END);
 
    htmlText = regex.Replace(htmlText, PARSED_PATTERN_BEGIN);
    htmlText = regex2.Replace(htmlText, PARSED_PATTERN_END);
    htmlText = htmlText.TrimLines();
    return htmlText;
}
 
public static bool EqualsIgnoreCase(this string text, string text2)
{
    return text.Equals(text2, StringComparison.CurrentCultureIgnoreCase);
}
 
public static string ReplaceBreaks(this string html)
{
    var regex = new Regex("<br/>");
    html = regex.Replace(html, "\n");
    return html;
}
 
public static string ReplaceBreaksWithSpace(this string html)
{
    var regex = new Regex("<br/>");
    html = regex.Replace(html, " ");
    return html;
}
 
public static string TrimLines(this string originalString)
{
    originalString = originalString.Trim('\n');
    return originalString;
}

Now we can properly parse markdown to html:

var markdown = "# Hello *World*";
var html = markdown.ToHtml();
// html = "<h1>Hello <strong>World</strong></h1>"

Parsing Html to NSAttributedString

The next step is to take our processed html string and turn it into something that an iOS UILabel can use.

How about another extension method?

public static NSAttributedString ToAttributedText(this string html)
{
    NSError error = new NSError();
     try
     {
         var htmlData = NSData.FromString(html);
         if (htmlData != null && htmlData.Length > 0)
         {
             NSAttributedString attributedString = null;

             attributedString = new NSAttributedString(htmlData, new DocumentType = NSDocumentType.HTML, StringEncoding = NSStringEncoding.UTF8    }, ref error);
             return attributedString;
         }
         return null;
     }
     catch (Exception ex)
     {
         Console.WriteLine(ex);
         return null;
     }
}

We’ll add one more extension method just to make the full conversion from markdown to html to formatted html:

public static NSAttributedString MarkdownToHtml(this string markdown) 
{
    return markdown.ToHtml().ToAttributedString();
}

Now for the very last bit: Show it!

Assiging To the UILabel

Pretty simple now that we have our useful extension methods!

var markdown = "# Hello *World*";
myLabel.AttributedText = markdown.MarkdownToHtml();

That’s it! Now you can see some cool stylized text in your labels.

2 thoughts on “Xamarin.Controls – Creating Your Own iOS Markdown UILabel”

Leave a comment