Partilhar via


Gráficos principais no Xamarin.iOS

Este artigo discute as estruturas principais do iOS de gráficos. Ele mostra como usar o Core Graphics para desenhar geometria, imagens e PDFs.

O iOS inclui a estrutura Core Graphics para fornecer suporte a desenhos de baixo nível. Essas estruturas são o que permitem os recursos gráficos avançados do UIKit.

Core Graphics é uma estrutura gráfica 2D de baixo nível que permite desenhar gráficos independentes do dispositivo. Todos os desenhos 2D no UIKit usam Core Graphics internamente.

Os gráficos principais suportam o desenho em vários cenários, incluindo:

Espaço geométrico

Independentemente do cenário, todos os desenhos feitos com gráficos principais são feitos no espaço geométrico, o que significa que funcionam em pontos abstratos em vez de pixels. Você descreve o que deseja desenhar em termos de geometria e estado do desenho, como cores, estilos de linha, etc. e o Core Graphics lida com a tradução de tudo em pixels. Esse estado é adicionado a um contexto gráfico, que você pode pensar como a tela de um pintor.

Existem alguns benefícios nessa abordagem:

  • O código de desenho se torna dinâmico e pode modificar gráficos em tempo de execução.
  • Reduzir a necessidade de imagens estáticas no pacote de aplicativos pode reduzir o tamanho do aplicativo.
  • Os gráficos se tornam mais resistentes a alterações de resolução entre dispositivos.

Desenhando em uma subclasse UIView

Cada UIView um tem um Draw método que é chamado pelo sistema quando precisa ser desenhado. Para adicionar código de desenho a uma vista, subclasse UIView e substituir Draw:

public class TriangleView : UIView
{
    public override void Draw (CGRect rect)
    {
        base.Draw (rect);
    }
}

O empate nunca deve ser chamado diretamente. Ele é chamado pelo sistema durante o processamento do loop de execução. Na primeira vez que o loop de execução depois que uma exibição é adicionada à hierarquia de exibição, seu Draw método é chamado. As chamadas subsequentes ocorrem Draw quando a exibição é marcada como precisando ser desenhada chamando ou SetNeedsDisplay SetNeedsDisplayInRect na exibição.

Padrão para código gráfico

O código na Draw implementação deve descrever o que deseja desenhar. O código de desenho segue um padrão no qual ele define algum estado de desenho e chama um método para solicitar que ele seja desenhado. Esse padrão pode ser generalizado da seguinte forma:

  1. Obtenha um contexto gráfico.

  2. Configure atributos de desenho.

  3. Crie alguma geometria a partir de primitivas de desenho.

  4. Chame um método Draw ou Stroke.

Exemplo de desenho básico

Por exemplo, considere o snippet de código a seguir:

//get graphics context
using (CGContext g = UIGraphics.GetCurrentContext ()) {

    //set up drawing attributes
    g.SetLineWidth (10);
    UIColor.Blue.SetFill ();
    UIColor.Red.SetStroke ();

    //create geometry
    var path = new CGPath ();

    path.AddLines (new CGPoint[]{
    new CGPoint (100, 200),
    new CGPoint (160, 100),
    new CGPoint (220, 200)});

    path.CloseSubpath ();

    //add geometry to graphics context and draw it
    g.AddPath (path);
    g.DrawPath (CGPathDrawingMode.FillStroke);
}

Vamos detalhar este código:

using (CGContext g = UIGraphics.GetCurrentContext ()) {
...
}

Com essa linha, ele primeiro obtém o contexto gráfico atual a ser usado para desenhar. Você pode pensar em um contexto gráfico como a tela em que o desenho acontece, contendo todo o estado do desenho, como cores de traçado e preenchimento, bem como a geometria a ser desenhada.

g.SetLineWidth (10);
UIColor.Blue.SetFill ();
UIColor.Red.SetStroke ();

Depois de obter um contexto gráfico, o código configura alguns atributos para usar ao desenhar, mostrados acima. Nesse caso, a largura da linha, as cores do traço e do preenchimento são definidas. Qualquer desenho subsequente usará esses atributos porque eles são mantidos no estado do contexto gráfico.

Para criar geometria, o código usa um CGPath, que permite que um caminho gráfico seja descrito a partir de linhas e curvas. Nesse caso, o caminho adiciona linhas conectando uma matriz de pontos para formar um triângulo. Conforme exibido abaixo, o Core Graphics usa um sistema de coordenadas para desenho de vista, onde a origem está no canto superior esquerdo, com x-direto positivo para a direita e a direção y-positivo para baixo:

var path = new CGPath ();

path.AddLines (new CGPoint[]{
new CGPoint (100, 200),
new CGPoint (160, 100),
new CGPoint (220, 200)});

path.CloseSubpath ();

Depois que o caminho é criado, ele é adicionado ao contexto gráfico para que chamá-lo AddPath e DrawPath , respectivamente, possam desenhá-lo.

A exibição resultante é mostrada abaixo:

O triângulo de saída de exemplo

Criando preenchimentos de gradiente

Formas mais ricas de desenho também estão disponíveis. Por exemplo, os gráficos principais permitem criar preenchimentos de gradiente e aplicar caminhos de recorte. Para desenhar um preenchimento de gradiente dentro do caminho do exemplo anterior, primeiro o caminho precisa ser definido como o caminho de recorte:

// add the path back to the graphics context so that it is the current path
g.AddPath (path);
// set the current path to be the clipping path
g.Clip ();

Definir o caminho atual como o traçado de recorte restringe todos os desenhos subseqüentes dentro da geometria do caminho, como o código a seguir, que desenha um gradiente linear:

// the color space determines how Core Graphics interprets color information
    using (CGColorSpace rgb = CGColorSpace.CreateDeviceRGB()) {
        CGGradient gradient = new CGGradient (rgb, new CGColor[] {
        UIColor.Blue.CGColor,
        UIColor.Yellow.CGColor
    });

// draw a linear gradient
    g.DrawLinearGradient (
        gradient,
        new CGPoint (path.BoundingBox.Left, path.BoundingBox.Top),
        new CGPoint (path.BoundingBox.Right, path.BoundingBox.Bottom),
        CGGradientDrawingOptions.DrawsBeforeStartLocation);
    }

Essas alterações produzem um preenchimento de gradiente, conforme mostrado abaixo:

O exemplo com um preenchimento de gradiente

Modificando padrões de linha

Os atributos de desenho das linhas também podem ser modificados com os gráficos principais. Isso inclui alterar a largura da linha e a cor do traço, bem como o próprio padrão de linha, conforme visto no código a seguir:

//use a dashed line
g.SetLineDash (0, new nfloat[] { 10, 4 * (nfloat)Math.PI });

Adicionar esse código antes de qualquer operação de desenho resulta em traços tracejados de 10 unidades de comprimento, com 4 unidades de espaçamento entre traços, conforme mostrado abaixo:

Adicionar esse código antes de qualquer operação de desenho resulta em traços tracejados

Observe que, ao usar a API Unificada no Xamarin.iOS, o tipo de matriz precisa ser um nfloate também precisa ser convertido explicitamente em Math.PI.

Desenho de imagens e texto

Além de desenhar caminhos no contexto gráfico de uma exibição, os gráficos principais também oferecem suporte ao desenho de imagens e texto. Para desenhar uma imagem, basta criar uma CGImage e passá-la para uma DrawImage chamada:

public override void Draw (CGRect rect)
{
    base.Draw (rect);

    using(CGContext g = UIGraphics.GetCurrentContext ()){
        g.DrawImage (rect, UIImage.FromFile ("MyImage.png").CGImage);
    }
}

No entanto, isso produz uma imagem desenhada de cabeça para baixo, conforme mostrado abaixo:

Uma imagem desenhada de cabeça para baixo

A razão para isso é que a origem do Core Graphics para desenho de imagem está no canto inferior esquerdo, enquanto a exibição tem sua origem no canto superior esquerdo. Portanto, para exibir a imagem corretamente, a origem precisa ser modificada, o que pode ser feito modificando a Matriz de Transformação Atual (CTM). O CTM define onde os pontos residem, também conhecido como espaço do usuário. Inverter o CTM na direção y e deslocá-lo pela altura dos limites na direção y negativa pode inverter a imagem.

O contexto gráfico tem métodos auxiliares para transformar o CTM. Nesse caso, ScaleCTM "inverte" o desenho e TranslateCTM o desloca para o canto superior esquerdo, conforme mostrado abaixo:

public override void Draw (CGRect rect)
{
    base.Draw (rect);

    using (CGContext g = UIGraphics.GetCurrentContext ()) {

        // scale and translate the CTM so the image appears upright
        g.ScaleCTM (1, -1);
        g.TranslateCTM (0, -Bounds.Height);
        g.DrawImage (rect, UIImage.FromFile ("MyImage.png").CGImage);
}

A imagem resultante é então exibida na vertical:

A imagem de amostra exibida na vertical

Importante

As alterações no contexto gráfico se aplicam a todas as operações de desenho subsequentes. Portanto, quando o CTM for transformado, ele afetará qualquer desenho adicional. Por exemplo, se você desenhou o triângulo após a transformação CTM, ele aparecerá de cabeça para baixo.

Adicionando texto à imagem

Assim como acontece com caminhos e imagens, desenhar texto com gráficos principais envolve o mesmo padrão básico de definir algum estado gráfico e chamar um método para desenhar. No caso de texto, o método para exibir texto é ShowText. Quando adicionado ao exemplo de desenho de imagem, o código a seguir desenha algum texto usando Core Graphics:

public override void Draw (RectangleF rect)
{
    base.Draw (rect);

    // image drawing code omitted for brevity ...

    // translate the CTM by the font size so it displays on screen
    float fontSize = 35f;
    g.TranslateCTM (0, fontSize);

    // set general-purpose graphics state
    g.SetLineWidth (1.0f);
    g.SetStrokeColor (UIColor.Yellow.CGColor);
    g.SetFillColor (UIColor.Red.CGColor);
    g.SetShadow (new CGSize (5, 5), 0, UIColor.Blue.CGColor);

    // set text specific graphics state
    g.SetTextDrawingMode (CGTextDrawingMode.FillStroke);
    g.SelectFont ("Helvetica", fontSize, CGTextEncoding.MacRoman);

    // show the text
    g.ShowText ("Hello Core Graphics");
}

Como você pode ver, definir o estado gráfico para desenho de texto é semelhante à geometria do desenho. Para desenho de texto, no entanto, o modo de desenho de texto e a fonte também são aplicados. Nesse caso, uma sombra também é aplicada, embora a aplicação de sombras funcione da mesma forma para o desenho do caminho.

O texto resultante é exibido com a imagem conforme mostrado abaixo:

O texto resultante é exibido com a imagem

Imagens com suporte de memória

Além de desenhar no contexto gráfico de uma exibição, o Core Graphics oferece suporte ao desenho de imagens com suporte de memória, também conhecido como desenho fora da tela. Para tal, é necessário:

  • Criando um contexto gráfico que é apoiado por um bitmap na memória
  • Definir o estado do desenho e emitir comandos de desenho
  • Obtendo a imagem do contexto
  • Removendo o contexto

Ao contrário do Draw método, em que o contexto é fornecido pela exibição, nesse caso, você cria o contexto de duas maneiras:

  1. Chamando UIGraphics.BeginImageContext (ou BeginImageContextWithOptions)

  2. Ao criar um novo CGBitmapContextInstance

CGBitmapContextInstance é útil quando você está trabalhando diretamente com os bits de imagem, como nos casos em que você está usando um algoritmo de manipulação de imagem personalizado. Em todos os outros casos, você deve usar BeginImageContext ou BeginImageContextWithOptions.

Depois de ter um contexto de imagem, adicionar código de desenho é exatamente como em uma UIView subclasse. Por exemplo, o exemplo de código usado anteriormente para desenhar um triângulo pode ser usado para desenhar em uma imagem na memória em vez de em um UIView, como mostrado abaixo:

UIImage DrawTriangle ()
{
    UIImage triangleImage;

    //push a memory backed bitmap context on the context stack
    UIGraphics.BeginImageContext (new CGSize (200.0f, 200.0f));

    //get graphics context
    using(CGContext g = UIGraphics.GetCurrentContext ()){

        //set up drawing attributes
        g.SetLineWidth(4);
        UIColor.Purple.SetFill ();
        UIColor.Black.SetStroke ();

        //create geometry
        path = new CGPath ();

        path.AddLines(new CGPoint[]{
            new CGPoint(100,200),
            new CGPoint(160,100),
            new CGPoint(220,200)});

        path.CloseSubpath();

        //add geometry to graphics context and draw it
        g.AddPath(path);
        g.DrawPath(CGPathDrawingMode.FillStroke);

        //get a UIImage from the context
        triangleImage = UIGraphics.GetImageFromCurrentImageContext ();
    }

    return triangleImage;
}

Um uso comum do desenho em um bitmap com suporte de memória é capturar uma imagem de qualquer UIView. Por exemplo, o código a seguir renderiza a camada de uma exibição em um contexto de bitmap e cria um UIImage a partir dele:

UIGraphics.BeginImageContext (cellView.Frame.Size);

//render the view's layer in the current context
anyView.Layer.RenderInContext (UIGraphics.GetCurrentContext ());

//get a UIImage from the context
UIImage anyViewImage = UIGraphics.GetImageFromCurrentImageContext ();
UIGraphics.EndImageContext ();

Desenhando PDFs

Além das imagens, o Core Graphics suporta desenho em PDF. Como as imagens, você pode renderizar um PDF na memória, bem como ler um PDF para renderizar em um UIViewarquivo .

PDF em uma interface do usuárioVer

Os gráficos principais também suportam a leitura de um PDF de um arquivo e a renderização em uma exibição usando a CGPDFDocument classe. A CGPDFDocument classe representa um PDF em código e pode ser usada para ler e desenhar páginas.

Por exemplo, o código a seguir em uma UIView subclasse lê um PDF de um arquivo em um CGPDFDocument:

public class PDFView : UIView
{
    CGPDFDocument pdfDoc;

    public PDFView ()
    {
        //create a CGPDFDocument from file.pdf included in the main bundle
        pdfDoc = CGPDFDocument.FromFile ("file.pdf");
    }

     public override void Draw (Rectangle rect)
    {
        ...
    }
}

O Draw método pode então usar o CGPDFDocument para ler uma página CGPDFPage e renderizá-la chamando DrawPDFPage, conforme mostrado abaixo:

public override void Draw (CGRect rect)
{
    base.Draw (rect);

    //flip the CTM so the PDF will be drawn upright
    using (CGContext g = UIGraphics.GetCurrentContext ()) {
        g.TranslateCTM (0, Bounds.Height);
        g.ScaleCTM (1, -1);

        // render the first page of the PDF
        using (CGPDFPage pdfPage = pdfDoc.GetPage (1)) {

        //get the affine transform that defines where the PDF is drawn
        CGAffineTransform t = pdfPage.GetDrawingTransform (CGPDFBox.Crop, rect, 0, true);

        //concatenate the pdf transform with the CTM for display in the view
        g.ConcatCTM (t);

        //draw the pdf page
        g.DrawPDFPage (pdfPage);
        }
    }
}

PDF com suporte de memória

Para um PDF na memória, você precisa criar um contexto PDF chamando BeginPDFContext. O desenho em PDF é granular em páginas. Cada página é iniciada chamando BeginPDFPage e concluída chamando EndPDFContent, com o código gráfico no meio. Além disso, como no desenho de imagem, o desenho em PDF com suporte de memória usa uma origem no canto inferior esquerdo, que pode ser contabilizada modificando o CTM da mesma forma que nas imagens.

O código a seguir mostra como desenhar texto em um PDF:

//data buffer to hold the PDF
NSMutableData data = new NSMutableData ();

//create a PDF with empty rectangle, which will configure it for 8.5x11 inches
UIGraphics.BeginPDFContext (data, CGRect.Empty, null);

//start a PDF page
UIGraphics.BeginPDFPage ();

using (CGContext g = UIGraphics.GetCurrentContext ()) {
    g.ScaleCTM (1, -1);
    g.TranslateCTM (0, -25);
    g.SelectFont ("Helvetica", 25, CGTextEncoding.MacRoman);
    g.ShowText ("Hello Core Graphics");
    }

//complete a PDF page
UIGraphics.EndPDFContent ();

O texto resultante é desenhado para o PDF, que é então contido em um NSData que pode ser salvo, carregado, enviado por e-mail, etc.

Resumo

Neste artigo, examinamos os recursos gráficos fornecidos por meio da estrutura Core Graphics . Vimos como usar o Core Graphics para desenhar geometria, imagens e PDFs dentro do contexto de um UIView, e também de contextos gráficos com suporte de memória.