Homestay Map
Picture is worth thousands of pictures--ben Shneiderman
We introduced the Calayer class in the first chapter, "Layer tree," and created a simple layer with a blue background. The background color is fine, but it's too boring if it just shows a monotonous color. In fact, the Calayer class can contain a picture of your liking, which we will explore in the future Calayer's homestay map (the one contained in the layer).
Contents property
Calayer has a property called contents
, the type of this property is defined as an ID, which means that it can be any type of object. In this case, you can contents
assign any value to the attribute, and your app will still be able to compile the pass. However, in practice, if you give contents
the assignment not cgimage, then the layer you get will be blank.
contents
This strange performance is caused by the historical causes of Mac OS. It is defined as the ID type because on Mac OS systems, this property works on values of cgimage and nsimage types. If you try to assign the value of UIImage to it on the iOS platform, you can only get a blank layer. Some iOS developers who are beginning to know core animation may be confused about this.
The headache is not just the problem we have just mentioned. In fact, the type you really want to assign should be cgimageref, which is a pointer to the cgimage structure. UIImage has a Cgimage property that returns a "Cgimageref" and if you want to assign the value directly to Calayer contents
, you will get a compilation error. Because Cgimageref is not a true cocoa object, it is a core foundation type.
Although the core foundation type looks like the Cocoa object at run time (called toll-free bridging), they are not type compatible, but you can convert by bridged keyword. If you want to assign a map to a layer's homestay, you can do this in the following way:
The contents of the set layer are displayed as a picture layer.contents = (__bridge ID) image. Cgimage;
If you do not use ARC (automatic reference counting), you do not need to __bridge this part. But why don't you have to arc?!
Let's continue to revise our new project in the first chapter so that we can show a picture rather than just a background color. We've built a layer in code, so we don't need extra layers. Then we will directly set the properties of the Layerview host layer to a contents
picture.
Listing 2.1 is the updated code.
@implementation viewcontroller-(void) 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
Diagram 2.1 Displays a picture in the host layer of the UIView
We did a very interesting thing with these simple code: We used Calayer to display a picture in a normal uiview. This is not a uiimageview, it is not the method we usually use to display pictures. By manipulating the layers directly, we used some new functions to make the UIView more interesting.
Contentgravity
You may have noticed that our snowman looks a little ... Fat ==! The picture we loaded is not exactly a square one, and it has been stretched a little bit to accommodate this view. The same problem is encountered when using Uiimageview, the solution is to set the contentMode
property to a more appropriate value, like this:
The setting content is displayed to fit View.contentmode = Uiviewcontentmodescaleaspectfit;
This approach is basically a solution to the situation we're experiencing (you can try:) ), but UIView most of the visual-related properties, for example, are operations on the contentMode
corresponding layer.
Calayer is contentMode
called with the corresponding property contentsGravity
, but it is a nsstring type, not a corresponding Uikit part, and the value inside is an enumeration. contentsGravity
the optional constant values are some of the following:
- Kcagravitycenter
- Kcagravitytop
- Kcagravitybottom
- Kcagravityleft
- Kcagravityright
- Kcagravitytopleft
- Kcagravitytopright
- Kcagravitybottomleft
- Kcagravitybottomright
- Kcagravityresize
- Kcagravityresizeaspect
- Kcagravityresizeaspectfill
and cotentMode
, in contentsGravity
order to determine how the content is aligned in the boundaries of the layer, we will use Kcagravityresizeaspect, which has the same effect as Uiviewcontentmodescaleaspectfit, It can also be stretched at a moderate scale on the layer to fit the layer's boundaries.
self.layerView.layer.contentsGravity = Kcagravityresizeaspect;
Figure 2.2 You can see the results
Figure 2.2 correctly set contentsGravity
the value
Contentsscale
contentsScale
Property defines the scale of the pixel size and view size of a homestay map, which by default is a floating-point number with a value of 1.0.
contentsScale
's purpose is not so obvious. It does not always affect the boarding map on the screen. If you try to set a different value for our example, you'll see that it doesn't have any effect at all. Because the contents
property is set contentsGravity
, it has been stretched to fit the bounds of the layer.
If you simply want to enlarge the image of the layer contents
, you can do this by using the layer's transform
and affineTransform
attributes (see Chapter Fifth, "Transforms", which explains it), which is not contengsScale
the purpose.
contentsScale
Properties are part of a screen mechanism that supports high-resolution (aka hi-dpi or retina). It is used to determine the amount of space that should be created for a homestay map when the layer is drawn, and the stretching of the picture that needs to be displayed (assuming no properties are set contentsGravity
). UIView has a property that is similar in function but very seldom used contentScaleFactor
.
If contentsScale
set to 1.0, the picture will be drawn at 1 pixels per point, and if set to 2.0, the picture will be drawn at 2 pixels per point, which is the retina screen we are familiar with. (This will be explained later in this section if you are not very clear about the concept of pixels and dots.)
This does not have any effect on our use of kcagravityresizeaspect, as it stretches the picture to fit the layer and does not take into account the resolution issue at all. But if we contentsGravity
set the setting to Kcagravitycenter (the value does not stretch the picture), there will be a noticeable change (2.3)
Figure 2.3 Displaying the retina picture with the wrong contentsScale
attributes
As you can see, our snowman is not only a bit large but also a bit of a pixel-like grain. That's because unlike UIImage, Cgimage has no concept of stretching. When we used the UIImage class to read our snowman pictures, he read the images of the high-quality retina version. But when we use Cgimage to set the content of our layers, this factor of stretching is lost at the time of conversion. However, we can contentsScale
fix this problem by manual setup (e.g. 2.2 list), and figure 2.4 is the result
@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 The same retina picture is set correctly contentsScale
after
When working with the homestay map in code, be sure to remember to manually set the properties of the layer, contentsScale
otherwise your picture is not displayed correctly on the retina device. The code is as follows:
Layer.contentsscale = [UIScreen mainscreen].scale;
Masktobounds
Now our snowman is finally showing the right size, but you may have found something else: he is beyond the bounds of the view. By default, UIView still draws content that is beyond the bounds or sub-view, which is also the case under Calayer.
UIView has a property called that clipsToBounds
can be used to decide whether to show content beyond the bounds, calayer the corresponding property is called masksToBounds
, set it to Yes, the snowman is in the border ~ (2.5)
Figure 2.5 using masksToBounds
to build the layer content
Contentsrect
The Calayer contentsRect
property allows us to display a subdomain of the homestay map in the layer border. This involves how the picture is displayed and stretched, so it's contentsGravity
much more flexible.
And bounds
, frame
Unlike, contentsRect
not calculated by point, it uses the unit coordinates , which are specified between 0 and 1, which is a relative value (pixels and points are absolute values). So they are relative to the size of the homestay map. iOS uses the following coordinate systems:
- Point-the most common coordinate system in iOS and Mac OS. A point is like a virtual pixel, also known as a logical pixel. On a standard device, a point is a pixel, but on a retina device, a point is equal to 2*2 pixels. iOS uses dots as the coordinate measurement system for screens to have consistent visual effects on retina devices and common devices.
- Pixel--physical pixel coordinates are not used for screen layout, but still have a relative relationship to the picture. UIImage is a screen resolution solution, so specify a point to measure the size. But some of the underlying images show that pixels are used like cgimage, so you have to be aware that they show different sizes on retina devices and normal devices.
- Units-for displays related to image size or layer boundaries, the unit coordinates are a convenient measure and do not need to be adjusted when the size changes. The unit coordinates are used much in OpenGL's texture coordinate system, and the unit coordinates are used in the Core animation.
The default contentsRect
is {0, 0, 1, 1}, which means that the entire homestay map is visible by default, and if we specify a smaller rectangle, the picture will be cropped (2.6)
Figure 2.6 A custom contentsRect
(left) and previously displayed content (right)
In fact contentsRect
, it is possible to set the origin of a negative number or a size greater than {1, 1}. In this case, the outermost pixels are stretched to fill the rest of the area.
contentsRect
The most interesting part of the app is a usage called image sprites(picture flattening). If you have experience with game programming, then you must be familiar with the concept of picture flattening, where images can be changed independently on the screen. Aside from game programming, this technique is often used to refer to images loaded in pieces, with no relation to moving pictures.
Typically, a picture can be packaged and integrated into a large image once loaded. There are a number of benefits to this: memory usage, loading time, rendering performance, and so on, compared to loading different images multiple times.
The game engine into cocos2d uses the flattening technique, which uses OpenGL to display pictures. But we can use flattening in a common Uikit application, right! is to usecontentsRect
First, we need a flattened chart--a large picture with a smaller flattened figure. 2.7 is shown below:
Next, we'll load and display these flattener images in the app. The rule is simple: load our big picture as usual, assign it to four separate layers contents
, and set each layer contentsRect
to remove the parts we don't want to show.
Some additional views are needed in our project. (To avoid too much code.) We will use Interface Builder to visit their location, if you would like to do it in code. Listing 2.3 has the required code, and figure 2.8 shows the results
@interface Viewcontroller () @property (nonatomic, weak) Iboutlet UIView *coneview; @property (nonatomic, weak) Iboutlet UI View *shipview; @property (nonatomic, weak) Iboutlet UIView *iglooview; @property (nonatomic, weak) Iboutlet UIView *anchor View; @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
Flattening not only gives the app a neat way to load, it also effectively improves load performance (larger images are loaded faster than multiple plots), but if there are manual arrangements, they still have some inconvenience, if you need to make some size changes or other changes in a product and diagram that you've already created, is undoubtedly more troublesome.
There are some commercial software on your Mac that can automatically flatten pictures for you, which automatically generate an XML or plist file that contains flattened coordinates, and the use of flattened images is greatly simplified. This file can be loaded with the image and set for each flattened layer so that the contentsRect
developer does not have to manually write the code to put it in place.
These files are often used in OpenGL games, but if you are interested in using flattening techniques in some common apps, then a source library called Layersprites (https://github.com/nicklockwood/ Layersprites), which can read a flattened image in the cocos2d format and display it in the normal core animation layer.
Contentscenter
The last and most relevant property of this chapter is contentsCenter
that you might think it might have something to do with the location of the picture, but that's a misleading name. contentsCenter
is actually a cgrect, which defines a fixed border and an area that can be stretched on the layer. Changing contentsCenter
The value does not affect the display of the homestay map, unless the size of the layer changes, you can see the effect.
By default, contentsCenter
{0, 0, 1, 1}, which means that if the size (by conttensGravity
decision) changes, the boarding chart will be stretched evenly. But if we increase the value of the origin and reduce the size. We'll create a border around the picture. Figure 2.9 shows the contentsCenter
effect set to {0.25, 0.25, 0.5, 0.5}.
contentsCenter
example of Figure 2.9
This means that we can resize it arbitrarily, and the border will still be contiguous. The effect of his work and the-resizableimagewithcapinsets in UIImage: The method works very similarly, except that it can be applied to any boarding map, even to the graphics that are drawn at the core graphics runtime (described later in this chapter).
Figure 2.10 The same picture uses a differentcontentsCenter
Listing 2.4 Demonstrates how to write these stretched views. Another cool feature of Contentscenter, however, is that it can be configured in Interface Builder without having to write code at all. 2.11
Listing 2.4 Using the contentsCenter
settings to stretch the view
@interface Viewcontroller () @property (nonatomic, weak) Iboutlet UIView *button1; @property (nonatomic, weak) Iboutlet UIV Iew *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 Controlling properties with the interface Builder probe window contentsCenter
Custome Drawing
The contents
value assigned to Cgimage is not the only way to set up a homestay map. We can also directly draw a homestay map with the core graphics directly. You can customize the drawing by inheriting the UIView and implementing the -drawRect:
method.
-drawRect:
method does not have a default implementation, because for UIView, the homestay map is not necessary, it does not care whether it is a monotonous color or an example of a picture. If UIView detects -drawRect:
that the method is called, it assigns a homestay map to the view, which is equal to contentsScale
the value of the view size multiplied by the image.
If you don't need a homestay map, do not create this method, which can result in wasted CPU resources and memory, which is why Apple recommends that you do not write an empty-drawrect in the subclass if you do not have a custom-drawn task: method.
The method is automatically called when the view appears on the screen -drawRect:
. -drawRect:
the code inside the method uses the core graphics to draw a homestay map, and then the content is cached until it needs to be updated (usually because the developer calls the -setNeedsDisplay
method, although some view types are automatically redrawn when the property values that affect the performance are changed), such as bounds
property). Although the -drawRect:
method is a UIView method, the fact is that the underlying Calayer arranges the redraw work and saves the resulting picture.
Calayer has an optional delegate
property that implements the CALayerDelegate
protocol, and when Calayer needs a content-specific information, it is requested from the protocol. Calayerdelegate is an informal agreement, in fact, that there is no calayerdelegate @protocol can let you in the class to quote. You just need to call the method you want to call, and Calayer will do the rest for you. ( delegate
attributes are declared as ID types, and all proxy methods are optional).
When it needs to be redrawn, Calayer will ask its agent to give him a homestay map to display. It does this by invoking the following method:
-(void) Displaylayer: (Calayercalayer *) layer;
Take advantage of this opportunity, if the agent wants to set contents
properties directly, it can do so, otherwise there is no other way to call. If the proxy does not implement -displayLayer:
the method, Calayer will instead attempt to invoke the following method:
-(void) Drawlayer: (Calayer *) layer Incontext: (cgcontextref) CTX;
Before calling this method, Calayer created an empty boarding map of the appropriate size (size bounds
and contentsScale
decision) and a drawing context for a core graphics, preparing for drawing the homestay map, which he passed in as a CTX parameter.
Let's go on to the first chapter of the project and let it implement calayerdelegate and do some drawing work (see Listing 2.5). Figure 2.12 is his result.
Listing 2.5 Implementation Calayerdelegate
@implementation viewcontroller-(void) viewdidload{ [Super Viewdidload]; ? Create Sublayer Calayer *bluelayer = [Calayer layer]; Bluelayer.frame = CGRectMake (50.0f, 50.0f, 100.0f, 100.0f); 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 Laye R 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 Implementing Calayerdelegate to draw a layer
Take a look at some interesting things:
- We have explicitly called on the Bluelayer
-display
. Unlike UIView, when a layer is displayed on the screen, Calayer does not automatically redraw its contents. It gives the developer the power to redraw the decision.
- Although we do not use
masksToBounds
attributes, the drawn circle is still clipped along the boundary. This is because when you use Calayerdelegate to draw a homestay map, there is no rendering support for content outside the bounds.
Now you understand calayerdelegate and know how to use it. But unless you create a separate layer, you have little chance of using the Calayerdelegate protocol. Because when UIView creates its host layer, it automatically sets the layer's delegate to itself and provides an -displayLayer:
implementation, and all the problems are gone.
When using a layer that hosts a view, you do not have -displayLayer:
to implement and -drawLayer:inContext:
method to draw your homestay map. The usual practice is to implement the UIView -drawRect:
method, and UIView will help you with the rest of the work, including calling the method when redrawing is required -display
.
Summarize
This chapter describes the homestay map and some related properties. You learned how to display and place pictures, use flattening techniques to display them, and draw layer content with calayerdelegate and core graphics.
In chapter three, "Layer geometry," we will explore the geometry of a layer and see how they place and change the dimensions of each other.
[IOS animation]-calayer display mode