SuperJMN
SuperJMN

Reputation: 13972

How to draw text in Xamarin iOS?

I would like to draw text at a given point (x, y) in the Draw method of a custom View.

I have followed this sample from the Xamarin site.

This is the View I created:

public class MyView : UIView
{
    public override void Draw(CGRect rect)
    {
        using (var context = UIGraphics.GetCurrentContext())
        {
            DrawText(context, "hello", 20, new CGPoint(0, 0));
            DrawText(context, "how are you", 20, new CGPoint(0, 40));
        }
    }

    private void DrawText(CGContext context, string text, int textHeight, CGPoint point)
    {
        var x = point.X;
        var y = point.Y + textHeight;

        context.TranslateCTM(x, y);

        context.ScaleCTM(1, -1);
        context.SetFillColor(UIColor.Red.CGColor);

        var attributedString = new NSAttributedString(text,
            new CTStringAttributes
            {
                ForegroundColorFromContext = true,
                Font = new CTFont("Arial", 16)
            });

        using (var textLine = new CTLine(attributedString))
        {
            textLine.Draw(context);
        }
    }
}

The problem is that the DrawText method only works OK once. The first time you call it the text is drawn, but it doesn't work on successive calls (it draws nothing, or what it draws isn't visible).

What am I doing wrong?

Upvotes: 2

Views: 2126

Answers (2)

SuperJMN
SuperJMN

Reputation: 13972

I took @SushiHangover's code and modified it to work for me, since their version (1st in his post) had some problems (read notice below):

public void DrawText(FormattedText formattedText, Point point)
{
    context.SaveState();
    context.ScaleCTM(1, -1); 
    context.SetFillColor(formattedText.Brush.Color.ToiOS());

    var sizeOfText = formattedText.DesiredSize;

    var ctFont = new CTFont(formattedText.FontName, formattedText.FontSize);
    var attributedString = new NSAttributedString(formattedText.Text,
        new CTStringAttributes
        {
            ForegroundColor = formattedText.Brush.Color.ToiOS(),
            Font = ctFont
        });

    context.TextPosition = new CGPoint(point.X, -(point.Y + sizeOfText.Height - ctFont.DescentMetric));

    using (var textLine = new CTLine(attributedString))
    {               
        textLine.Draw(context);
    }

    context.RestoreState();
}

Keep into account that FormattedText and Point are custom classes I made to encapsulate the text to be drawn and the point where to draw it. Their properties are as simple as you see :)

IMPORTANT: The modifications to @SushiHangover's version are there to

Avoid side effects (if I drew a Rectangle after calling the DrawText method, the coordinates were messed up, for instance). It also had a problem with the x coordinate:

context.TranslateCTM(-x, -(y + textHeight)) 

should be

context.TranslateCTM(x, -(y + textHeight)) 

Upvotes: 1

SushiHangover
SushiHangover

Reputation: 74094

So there are two basic things that are wrong with your code.

  • You are performing a ScaleCTM and a TranslateCTM each time you call DrawText
  • You do not take into account that when you CTLine.Draw, the "cursor" is moved to the end of that text.

So, call ScaleCTM to flipped the whole thing so the text gets draw left2right, then call DrawText and translate to where you want to paint the text and then translate back to where you started so the next time you are at the same point.

Example Draw override:

public override void Draw(CGRect rect)
{
    var context = UIGraphics.GetCurrentContext();
    context.ScaleCTM(1, -1); // you flipped the context, now you must use negative Y values to draw "into" the view

    var textHeight = new CTFont("Arial", 16).CapHeightMetric; // lets use the actaul height of the font captials.

    DrawText(context, "Hello", textHeight, 0, 0);
    DrawText(context, "How are you?", textHeight, 0, 20);
    DrawText(context, "Sincerely,", textHeight, 0, 40);
    DrawText(context, "StackOverflow,", textHeight, 0, 60);
}

void DrawText(CGContext context, string text, nfloat textHeight, nfloat x, nfloat y)
{
    context.TranslateCTM(-x, -(y + textHeight));
    context.SetFillColor(UIColor.Red.CGColor);

    var attributedString = new NSAttributedString(text,
        new CTStringAttributes
        {
            ForegroundColorFromContext = true,
            Font = new CTFont("Arial", 16)
        });

    CGRect sizeOfText;
    using (var textLine = new CTLine(attributedString))
    {
        textLine.Draw(context);
        sizeOfText = textLine.GetBounds(CTLineBoundsOptions.UseOpticalBounds);
    }
    // Reset the origin back to where is was
    context.TranslateCTM(x - sizeOfText.Width, y + sizeOfText.Height); 
}

Results in:

enter image description here

Using NSMutableParagraphStyle and NSString.DrawString

var context = UIGraphics.GetCurrentContext();
CGRect textRect = new CGRect(0.0f, 0.0f, 200.0f, 100.0f);
{
    var textContent = "Hello\nHow are you?\nSincerely,\nStackOverflow";
    UIColor.Red.SetFill();
    var textStyle = new NSMutableParagraphStyle ();
    textStyle.Alignment = UITextAlignment.Left;

    var textFontAttributes = new UIStringAttributes () {Font = UIFont.FromName("ArialMT", 16.0f), ForegroundColor = UIColor.Red, ParagraphStyle = textStyle};
    var textTextHeight = new NSString(textContent).GetBoundingRect(new CGSize(textRect.Width, nfloat.MaxValue), NSStringDrawingOptions.UsesLineFragmentOrigin, textFontAttributes, null).Height;
    context.SaveState();
    context.ClipToRect(textRect);
    new NSString(textContent).DrawString(new CGRect(textRect.GetMinX(), textRect.GetMinY() + (textRect.Height - textTextHeight) / 2.0f, textRect.Width, textTextHeight), UIFont.FromName("ArialMT", 16.0f), UILineBreakMode.WordWrap, UITextAlignment.Left);
    context.RestoreState();
}

Upvotes: 2

Related Questions