Core Animation-2: Boarding diagram, coreanimation-2

Source: Internet
Author: User

Core Animation-2: Boarding diagram, coreanimation-2

# Boarding chart

> Images are better than a thousand words, and the interface is worth thousands of pictures-Ben Shneiderman

 

In chapter 1 "layer Tree", we introduced the CALayer class and created a simple layer with a blue background. The background color is fine, but it is boring if it only shows a monotonous color. In fact, the CALayer class can contain a picture you like. In this chapter, we will explore the boarding map of CALayer in the future (that is, the picture contained in the layer ).

 

# Contents attributes

CALayer has an attribute called 'contents'. The type of this attribute is defined as id, meaning it can be an object of any type. In this case, you can assign any value to the 'contents' attribute, and your app can be compiled. However, in practice, if you do not assign CGImage to 'contents', the layer you get will be blank.

 

The strange performance of 'contents' is caused by the historical causes of Mac OS. It is defined as the id type because on Mac OS, this attribute applies to values of the CGImage and NSImage types. If you try to assign the value of UIImage to it on iOS platform, you can only get a blank layer. Some iOS developers who first know Core Animation may be confused about this.

 

The headache is not just the problem we mentioned just now. In fact, the type you really want to assign values should be CGImageRef, which is a pointer to the CGImage structure. UIImage has a CGImage attribute, which returns a "CGImageRef". If you want to assign this value directly to the 'contents' of CALayer, you will get a compilation error. CGImageRef is not a real Cocoa object, but a Core Foundation type.

 

Although the Core Foundation type is similar to the Cocoa object at runtime (called toll-free bridging), they are not compatible with the type, but you can use the bridged keyword conversion. If you want to assign a value to the boarding chart of the layer, you can follow the following method:

 

'''Objective-c

Layer. contents = (_ bridge id) image. CGImage;

'''

 

If you do not use ARC (automatic reference count), you do not need _ bridge. But why don't you use ARC ?!

 

Let's continue to modify the project we created in Chapter 1 so that we can display an image, not just a background color. We have already created a layer using code, so we don't need any additional layers. Then we directly set the 'contents' attribute of the host layer of layerView to an image.

 

The updated code of listing 2.1.

 

'''Objective-c

@ Implementation ViewController

 

-(Void) viewDidLoad

{

[Super viewDidLoad]; // load an image

UIImage * image = [UIImage imageNamed: @ "Snowman.png"];

 

// Add it directly to our view's layer

Self. layerView. layer. contents = (_ bridge id) image. CGImage;

}

@ End

'''

 

Chart 2.1 displays an image in the host layer of UIView

 

 

 

We use these simple code to do a very interesting thing: we use CALayer to display an image in a common UIView. This is not a UIImageView. It is not a method we usually use to display images. By directly operating the layers, we use some new functions to make the UIView more interesting.

 

** ContentGravity **

 

You may have noticed that our Snowman looks a little bit... Fat =! The image we load is not just a square image. To adapt to this view, it is stretched a little bit. You have encountered the same problem when using UIImageView. The solution is to set the 'contentmode' attribute to a more appropriate value, as shown in the following code:

 

'''Objective-c

View. contentMode = UIViewContentModeScaleAspectFit;

'''

This method is basically close to the solution we encountered (you can try it). However, most visual attributes of UIView, such as 'contentmode ', operations on these properties are actually operations on the corresponding layer.

 

The attribute corresponding to CALayer and 'contentmode' is called 'contentsgravity ', but it is an NSString type, rather than the UIKit part, where the value is enumeration. The optional constant values of 'contentsgravity 'include the following:

 

* KCAGravityCenter

* KCAGravityTop

* KCAGravityBottom

* KCAGravityLeft

* KCAGravityRight

* KCAGravityTopLeft

* KCAGravityTopRight

* KCAGravityBottomLeft

* KCAGravityBottomRight

* KCAGravityResize

* KCAGravityResizeAspect

* KCAGravityResizeAspectFill

 

Like 'otentmode', 'contentsgravity 'aims to determine how the content is aligned in the boundaries of the layer. We will use kCAGravityResizeAspect, which is equivalent to UIViewContentModeScaleAspectFit, at the same time, it can also moderately stretch the layer to adapt to the boundaries of the layer.

 

'''Objective-c

Self. layerView. layer. contentsGravity = kCAGravityResizeAspect;

'''

 

Figure 2.2 shows the result

 

 

 

Figure 2.2 correctly set the value of 'contentsgravity'

 

# ContentsScale

 

The 'contentsscale' attribute defines the pixel size and View Size ratio of a boarding chart. By default, it is a floating point number with a value of 1.0.

 

The purpose of 'contentsscale' is not so obvious. It does not always have an impact on the boarding chart on the screen. If you try to set different values for our example, you will find that there is no impact at all. Because 'contents' has set the 'contentsgravity 'attribute, it has been stretched to adapt to the boundary of the layer.

 

If you just want to enlarge the 'contents' image of a layer, you can use the 'transform' and 'affinetransform' attributes of the layer to achieve this goal (see chapter 5 "Transforms 』, but the amplification is not the purpose of 'contentsscale.

 

The 'contentsscale' attribute is actually part of the screen mechanism that supports high resolution (also known as Hi-DPI or Retina. It is used to determine the size of the space that should be created for the host image when drawing the layer, and the degree of stretching of the image to be displayed (assuming that the 'contentsgravity 'attribute is not set ). UIView has a similar function, but rarely uses the 'contentscale' attribute.

 

If 'contentsscale' is set to 1.0, an image is drawn in 1 pixel at each vertex. If it is set to 2.0, an image is drawn in 2 pixels at each vertex, this is a well-known Retina screen. (If you are not very clear about the concepts of pixels and points, the following sections will explain this ).

 

This does not have any impact on our use of kCAGravityResizeAspect, because it is simply stretching the image to adapt to the layer, and does not take resolution into account. However, if we set 'contentsgravity 'to kCAGravityCenter (this value does not stretch the image), it will change significantly (2.3)

 

 

 

Figure 2.3 display the Retina image with the wrong 'contentsscale' attribute

 

As you can see, our snowman is not only a little big, but also a bit of granular pixel. That's because CGImage does not have the concept of stretching, unlike UIImage. When we use the UIImage class to read our snowman images, it reads high-quality Retina versions of images. However, when we use CGImage to set the content of our layers, stretching is lost during conversion. However, you can manually set 'contentsscale' to fix this problem (such as the 2.2 list). Figure 2.4 shows the result.

 

'''Objective-c

@ Implementation ViewController

 

-(Void) viewDidLoad

{

[Super viewDidLoad]; // load an image

UIImage * image = [UIImage imageNamed: @ "Snowman.png"]; // add it directly to our view's layer

Self. layerView. layer. contents = (_ bridge id) image. CGImage; // center the image

Self. layerView. layer. contentsGravity = kCAGravityCenter;

 

// Set the contentsScale to match image

Self. layerView. layer. contentsScale = image. scale;

}

 

@ End

'''

 

 

 

Figure 2.4 After the correct 'contentsscale' is set for the same Retina Image

 

When using code to process a boarding chart, you must manually set the 'contentsscale' attribute of the layer. Otherwise, your image is displayed incorrectly on the Retina device. The Code is as follows:

 

'''Objective-c

Layer. contentsScale = [UIScreen mainScreen]. scale;

'''

 

# MaskToBounds

 

Now our snowman shows the correct size, but you may have discovered another thing: it is beyond the boundary of the view. By default, UIView still draws content or subviews that exceed the boundary. This is also true in CALayer.

 

UIView has an attribute called 'clipstobounds 'that can be used to determine whether to display content beyond the boundary. The attribute corresponding to CALayer is 'maskstobounds'. Set it to YES, and the snowman will be in the boundary ~ (2.5)

 

 

 

Figure 2.5 use 'maskstobounds 'to build layer content

 

# ContentsRect

 

The 'contentsrect 'attribute of CALayer allows us to display a subdomain of a boarding chart in the layer border. This involves how images are displayed and stretched, so it is more flexible than 'contentsgravity '.

 

Unlike 'bounds' and 'framework', 'contentsrect 'is not calculated by point. It uses * Unit coordinates * and the Unit coordinates are specified between 0 and 1, is a relative value (while pixels and points are absolute values ). Therefore, they are relative to the size of the boarding chart. IOS uses the following coordinate system:

 

* Point-the most common coordinate system in iOS and Mac OS. Points are like virtual pixels, also known as logical pixels. A point is a pixel on a standard definition device, but on a Retina device, a point is equal to 2*2 pixels. IOS uses the point as the Coordinate Measuring System of the screen to achieve consistent visual effects on the Retina and General devices.

* Pixels-physical pixel coordinates are not used for screen layout, but they are still related when processing images. UIImage can recognize screen resolution and specify its size in the unit of points. However, some underlying images, such as CGImage, use pixels. Therefore, you must be clear that they are different sizes on the Retina and General devices.

* Unit: for the display related to the image size or layer boundary, the Unit coordinate is a convenient measurement method. When the size changes, you do not need to adjust it again. The Unit coordinates are used in texture coordinate systems such as OpenGL, and the Unit coordinates are also used in Core Animation.

 

The default 'contentsrect 'is {0, 0, 1, 1}, which means that the entire boarding graph is visible by default. If we specify a smaller rectangle, the image will be cropped (2.6)

 

 

 

Figure 2.6 a custom 'contentsrect '(left) and the previously displayed content (right)

 

In fact, it is also possible to set a negative origin number for 'contentsrect 'or a size greater than {1, 1. In this case, the outermost pixel is stretched to fill the remaining area.

 

One of the most interesting uses of 'contentsrect 'is that it can use * image sprites * (image concatenation ). If you have experience in game programming, you must be familiar with the concept of image concatenation. Pictures can change positions independently on the screen. Aside from game programming, this technology is often used to refer to loading and splicing pictures, and it has nothing to do with mobile pictures.

 

Generally, multiple images can be combined and packaged into a large image for one-time loading. Compared to loading different images multiple times, this can bring many advantages: memory usage, loading time, rendering performance, etc.

 

The 2D game engine, such as Cocos2D, uses the concatenation technology, which uses OpenGL to display images. However, we can combine them in a common UIKit application! Is to use 'contentsrect'

 

First, we need a tiled chart-a large image that contains smaller tiled images. 2.7:

 

 

 

Next, we need to load and display these tiled graphs in the app. The rule is simple: load our big picture as usual, and assign it to the 'contents' of the four independent layers ', then, set 'contentsrect 'for each layer to remove the part we don't want to display.

 

Some additional views are required in our project. (To avoid too much code. We will use Interface Builder to access their locations. If you want to, you can still use code to implement them ). Listing 2.3 has the required code, and Figure 2.8 shows the result.

 

'''Objective-c

 

@ Interface ViewController ()

@ Property (nonatomic, weak) IBOutlet UIView * coneView;

@ Property (nonatomic, weak) IBOutlet UIView * shipView;

@ Property (nonatomic, weak) IBOutlet UIView * iglooView;

@ Property (nonatomic, weak) IBOutlet UIView * anchorView;

@ End

 

@ Implementation ViewController

 

-(Void) addSpriteImage :( UIImage *) image withContentRect :( CGRect) rect toLayer :( CALayer *) layer // set image

{

Layer. contents = (_ bridge id) image. CGImage;

 

// Scale contents to fit

Layer. contentsGravity = kCAGravityResizeAspect;

 

// Set contentsRect

Layer. contentsRect = rect;

}

 

-(Void) viewDidLoad

{

[Super viewDidLoad]; // load sprite sheet

UIImage * image = [UIImage imageNamed: @ "Sprites.png"];

// Set igloo sprite

[Self addSpriteImage: image withContentRect: CGRectMake (0, 0, 0.5, 0.5) toLayer: self. iglooView. layer];

// Set cone sprite

[Self addSpriteImage: image withContentRect: CGRectMake (0.5, 0, 0.5, 0.5) toLayer: self. coneView. layer];

// Set anchor sprite

[Self addSpriteImage: image withContentRect: CGRectMake (0, 0.5, 0.5, 0.5) toLayer: self. anchorView. layer];

// Set spaceship sprite

[Self addSpriteImage: image withContentRect: CGRectMake (0.5, 0.5, 0.5, 0.5) toLayer: self. shipView. layer];

}

@ End

'''

 

 

Splicing not only reduces the size of the application, but also effectively improves the loading performance (a single large image is faster than loading multiple small images), but manual arrangement may be troublesome, if you need to make some size changes or other changes on a created combination chart, it is undoubtedly troublesome.

 

Some commercial software on Mac can automatically splice images for you. These tools automatically generate an XML or plist file containing the assembled coordinates, which greatly simplifies the use of image splicing. This file can be loaded with images and set 'contentsrect 'for each spliced layer, so developers do not need to manually write code to place the location.

 

These files are usually used in OpenGL games. However, if you are interested in using the splicing technology in some common apps, there is an open-source library called LayerSprites ([https://github.com/nicklockwood/LayerSprites] (https://github.com/nicklockwood/LayerSprites) that can read the tiled diagrams in Cocos2D format and display them in the common Core Animation layer.

 

# ContentsCenter

 

In this chapter, the last attribute related to the content is 'contentscenter'. You may think that the name may be related to the image location, but this name is misleading. 'Contentscenter' is actually a CGRect that defines the Extensible area and a fixed border in the layer. Changing the value of 'contentscenter' does not affect the display of the boarding chart, unless the size of the layer changes, you can see the effect.

 

By default, 'contentscenter' is {0, 0, 1, 1}. This means that if the layer size changes, the boarding chart will be stretched out evenly Based on contentsGravity. However, if we increase the source value and reduce the size. We will create a border around the image. Figure 2.9 shows the effect of setting 'contentscenter' to {0.25, 0.25, 0.5, 0.5.

 

 

 

Figure 2.9 'contentscenter' example

 

This means we can reset the size at will, and the border will still be continuous. The method works very similar to-resizableImageWithCapInsets in UIImage, but it can be applied to any boarding graph, it even includes Graphics drawn during Core Graphics Runtime (this chapter will discuss later ).

 

 

 

Figure 2.10 use different 'contentscenter' for the same image'

 

Listing 2.4 demonstrates how to write these extensible views. However, another cool feature of contentsCenter is that it can be configured in Interface Builder without writing code. 2.11

 

Listing 2.4 uses 'contentscenter' to set the Extensible View

 

'''Objective-c

@ Interface ViewController ()

 

@ Property (nonatomic, weak) IBOutlet UIView * button1;

@ Property (nonatomic, weak) IBOutlet UIView * button2;

 

@ End

 

@ Implementation ViewController

 

-(Void) addStretchableImage :( UIImage *) image withContentCenter :( CGRect) rect toLayer :( CALayer *) layer

{

// Set image

Layer. contents = (_ bridge id) image. CGImage;

 

// Set contentsCenter

Layer. contentsCenter = rect;

}

 

-(Void) viewDidLoad

{

[Super viewDidLoad]; // load button image

UIImage * image = [UIImage imageNamed: @ "Button.png"];

 

// Set button 1

[Self addStretchableImage: image withContentCenter: CGRectMake (0.25, 0.25, 0.5, 0.5) toLayer: self. button1.layer];

 

// Set button 2

[Self addStretchableImage: image withContentCenter: CGRectMake (0.25, 0.25, 0.5, 0.5) toLayer: self. button2.layer];

}

 

@ End

'''

 

 

Figure 2.11 use Interface Builder to control the 'contentscenter' attribute in the Detection Window

 

# Custome Drawing

 

The CGImage value assigned to 'contents' is not the only method for setting a boarding chart. We can also directly use Core Graphics to draw a boarding chart. You can inherit the UIView and implement the '-drawRect:' method to customize the painting.

 

The '-drawRect:' method is not implemented by default, because for UIView, a boarding chart is not necessary. It does not care whether it is a monotonous color or an image instance. If UIView detects that the '-drawRect:' method is called, it allocates a boarding chart to the view. The pixel size of the boarding chart is equal to the View Size multiplied by the value of 'contentsscale.

 

If you do not need a boarding chart, do not create this method. This will cause a waste of CPU resources and memory. This is why Apple recommends: if there is no custom painting task, do not write an empty-drawRect: Method in the subclass.

 

When a view appears on the screen, the '-drawRect:' method is automatically called. '-DrawRect:' the code in the method uses Core Graphics to draw a boarding chart, then the content will be cached until it needs to be updated (usually because the developer calls the '-setneedsdisplay' method, even if the attribute values that affect the performance are changed, some view types will be automatically repainted, such as the 'bounds 'attribute ). Although the '-drawRect:' method is a UIView method, in fact, the underlying CALayer arranges the re-painting and saves the resulting images.

 

CALayer has an optional 'delegate' attribute, which implements the 'calayerdelegate' protocol. When CALayer requires a specific content information, it will request from the protocol. CALayerDelegate is an informal protocol, which means that no CALayerDelegate @ protocol can be referenced in the class. You only need to call the method you want to call. CALayer will help you with the rest. (The 'delegate' attribute is declared as the id type, and all proxy methods are optional ).

 

When you need to be re-painted, CALayer will request its proxy to give it a boarding chart for display. It does this by calling the following method:

 

'''Objective-c

(Void) displayLayer :( CALayer *) layer;

'''

 

Taking advantage of this opportunity, if the 'contents' attribute is directly set on behalf of the ideal, it can do so, otherwise there is no other way to call it. If the proxy does not implement the '-displayLayer:' method, CALayer tries to call the following method:

 

'''Objective-c

-(Void) drawLayer :( CALayer *) layer inContext :( CGContextRef) ctx;

'''

 

Before calling this method, CALayer creates an empty boarding chart of the appropriate size (the size is determined by 'bounds 'and 'contentsscale') and a drawing Context Environment of the Core Graphics, prepare for drawing the boarding chart, which is passed as the ctx parameter.

 

Let's continue with the project in Chapter 1 so that it can implement CALayerDelegate and do some drawing work (see listing 2.5). Figure 2.12 is its result.

 

Implement CALayerDelegate in listing 2.5

 

'''Objective-c

@ Implementation ViewController

-(Void) viewDidLoad

{

[Super viewDidLoad];

// Create sublayer

CALayer * blueLayer = [CALayer layer];

BlueLayer. frame = CGRectMake (50366f, 50366f, 100366f, 100366f );

BlueLayer. backgroundColor = [UIColor blueColor]. CGColor;

 

// Set controller as layer delegate

BlueLayer. delegate = self;

 

// Ensure that layer backing image uses correct scale

BlueLayer. contentsScale = [UIScreen mainScreen]. scale; // add layer to our view

[Self. layerView. layer addSublayer: blueLayer];

 

// Force layer to redraw

[BlueLayer display];

}

 

-(Void) drawLayer :( CALayer *) layer inContext :( CGContextRef) ctx

{

// Draw a thick red circle

CGContextSetLineWidth (ctx, 10.0f );

CGContextSetStrokeColorWithColor (ctx, [UIColor redColor]. CGColor );

CGContextStrokeEllipseInRect (ctx, layer. bounds );

}

@ End

'''

 

 

 

Figure 2.12 implement CALayerDelegate to draw Layers

 

Note some interesting things:

 

* We explicitly call '-display' on blueLayer '. Unlike UIView, when a layer is displayed on the screen, CALayer does not automatically repaint its content. It handed over the re-painting decision to the developer.

* Even though we didn't use the 'maskstobounds 'attribute, the drawn circle is still cropped along the boundary. This is because when you use CALayerDelegate to draw a boarding chart, there is no support for drawing content beyond the boundary.

 

Now you understand CALayerDelegate and know how to use it. However, unless you create a separate layer, you have almost no chance to use the CALayerDelegate protocol. Because when UIView creates its host layer, it automatically sets the layer's delegate as its own and provides an implementation of '-displayLayer, then all the problems are gone.

 

When using a hosted view layer, you do not need to implement the '-displayLayer:' and '-drawLayer: inContext:' methods to draw your boarding map. The general practice is to implement the '-drawRect:' method of the UIView, And the UIView will help you finish the rest of the work, including calling the '-display' method when you need to redraw.

 

# Summary

 

This chapter describes the boarding chart and related attributes. You learned how to display and place images, how to use splicing technology to display, and how to use CALayerDelegate and Core Graphics to draw layer content.

 

In chapter 3, "layer ry", we will explore the ry of a layer and observe how they place and change the sizes of each other.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.