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:
- Desenhando para a tela por meio de um
UIView
. - Desenho de imagens na memória ou na tela.
- Criando e desenhando em um PDF.
- Ler e desenhar um PDF existente.
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:
Obtenha um contexto gráfico.
Configure atributos de desenho.
Crie alguma geometria a partir de primitivas de desenho.
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:
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:
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:
Observe que, ao usar a API Unificada no Xamarin.iOS, o tipo de matriz precisa ser um nfloat
e 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:
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:
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:
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:
Chamando
UIGraphics.BeginImageContext
(ouBeginImageContextWithOptions
)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 UIView
arquivo .
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.