Visual effects
Well, circles and ellipses are good, but what about rectangles with rounded corners?
Can we do that now?
Steve Jobs
We discussed the frame of the layer in chapter three, "Layer geometry", and the second chapter, "Homestay Map", discusses the layer's homestay map. But a layer is not just a container for pictures or colors, but also a series of built-in features that make it possible to create beautiful, elegant, and impressive interface elements. In this chapter, we will explore some visual effects that can be achieved by using the Calayer property.
Rounded Corners
Rounded rectangles are an iconic aesthetic feature of iOS. This is reflected in every location on iOS, whether it's a home screen icon, a warning bullet box, or even a text box. Depending on the popularity, you might think there must be a way to easily create rounded corners without using Photoshop. Congratulations, you guessed it.
Calayer has a conrnerRadius
property called properties that control the curvature of the layer's corners. It is a floating-point number that defaults to 0 (0 is right-angled), but you can set it to any value. By default, this curvature value affects only the background color without affecting the background picture or sublayer. However, if masksToBounds
set to Yes, everything inside the layer will be intercepted.
We can demonstrate this effect through a simple project. In Interface Builder, we put some views, and they have some sub-views. And some of these sub-views are out of bounds (4.1). You may not be able to see them out of bounds, because when editing the interface, the excess is always cut off by interface builder. But you can trust me:)
Figure 4.1 Two large white views, they all contain smaller red views.
Then in the code, we set the radius of the corner to 20 points, and cut out the excess portion of the first view (see Listing 4.1). Technically, these properties can be set directly on the interface builder's probe board by "User defined runtime Properties" and by ticking the clip subviews selection box. However, in this example, the code can be expressed more clearly. Figure 4.2 is the result of running the code
Listing 4.1 sets cornerRadius
andmasksToBounds
@interface Viewcontroller () @property (nonatomic, weak) Iboutlet UIView *layerview1; @property (nonatomic, weak) Iboutlet UIView *layerview2; @end @implementation viewcontroller-(void) viewdidload{??? [Super Viewdidload]; Set the corner radius on our layers Self.layerView1.layer.cornerRadius = 20.0f; Self.layerView2.layer.cornerRadius = 20.0f; Enable clipping on the second layer self.layerView2.layer.masksToBounds = YES;} @end
In the image on the right, the Red Child view is cropped along the corner radius
As you can see, the child view on the right is clipped along the boundary.
It is also not impossible to control the fillet curvature of each layer individually. If you want to create a layer or view with some rounded corners at a right angle, you might need a different approach. For example, use a layer mask (described later in this chapter) or Cashapelayer (see Chapter Sixth, "dedicated layers").
Layer border
Calayer the other two very useful properties are borderWidth
the and borderColor
. Together, they define the drawing style for the edges of the layer. This line (also called a stroke) is drawn along the layer and bounds
also contains the corners of the layer.
borderWidth
is a floating-point number that defines the border thickness in points, which defaults to 0. borderColor
Defines the color of the border, which is black by default.
borderColor
is a cgcolorref type, not a uicolor, so it is not a cocoa built-in object. However, you must also know the layer reference borderColor
, although the attribute declaration does not prove it. The CGColorRef
behavior in quoting/releasing is NSObject
very similar. However, OBJECTIVE-C syntax does not support this practice, so CGColorRef
even strong references can only be declared by the Assign keyword.
The border is drawn within the bounds of the layer, and before all child content, also before the child layer. If we add a border to the layer in the previous example (listing 4.2), you can see what's going On (4.3).
Listing 4.2 plus borders
@implementation viewcontroller-(void) viewdidload{ [Super Viewdidload]; Set the corner radius on our layers Self.layerView1.layer.cornerRadius = 20.0f; Self.layerView2.layer.cornerRadius = 20.0f; Add a border to our layers self.layerView1.layer.borderWidth = 5.0f; Self.layerView2.layer.borderWidth = 5.0f; Enable clipping on the second layer self.layerView2.layer.masksToBounds = YES;} @end
Figure 4.3 Adding a border to a layer
Careful observation will find that the border does not calculate the shape of the homestay or sublayer, if the layer's sublayers are beyond the bounds, or if the boarding chart has a transparent mask in the transparent area, the border will still be drawn along the boundary of the layer (4.4).
Figure 4.4 The border is a change from the boundary of the layer, not the contents of the layer
Shadow
Another common feature of iOS is the shadow. Shadows can often reach the effect of layer depth hints. It can also be used to emphasize the layers and priorities being displayed (such as a pop-up box before other views), but sometimes they are purely decorative purposes.
Give the shadowOpacity
property a value that is greater than the default value (that is, 0), and the shadow can be displayed below any layer of intent. shadowOpacity
is a floating-point number that must be between 0.0 (invisible) and 1.0 (completely opaque). If set to 1.0, a slightly blurred black shadow is displayed slightly above the layer. To change the performance of the shadow, you can use Calayer's other three properties: shadowColor
, shadowOffset
and shadowRadius
.
Obviously, the shadowColor
property controls the color of the shadow, as well as borderColor
backgroundColor
its type CGColorRef
. Shadows are black by default, and most of the time you need shadows that are also black (other shades of color look not to be a little bit strange ...). )。
shadowOffset
property controls the direction and distance of the shadow. It is a CGSize
value, width controls the lateral displacement of the shadow, and the height controls the longitudinal displacement. shadowOffset
The default value is {0,-3}, meaning that the shadow has an upward displacement of 3 points relative to the y-axis.
Why do you want to default up the shadow? Although core animation evolved from a layer suite (which can be thought of as a private animation frame created for iOS), it was on Mac OS, as mentioned earlier, the y-axis is reversed. This causes the default 3-point displacement of the shadow to be upward. On the Mac, shadowOffset
the default value is shadow down so you can understand why the shadow direction on iOS is up (4.5).
Figure 4.5 shows up on iOS (left) and Mac OS (right) shadowOffset
.
Apple prefers that the shadow of the user interface should be vertically downward, so it is a practice to set the shadow width to 0 on iOS and then set the height to a positive value.
shadowRadius
property controls the Blur of the shadow, and when its value is 0, the shadow has a very definite boundary line like the view. When the value is getting bigger, the boundary line will look more and more blurred and natural. Apple's own application design is more in favor of natural shadows, so a non-0 value is more appropriate.
Generally speaking, if you want the view or control to be very visible and independent of the background (such as a popover mask layer), you should shadowRadius
set a slightly larger value. The more blurred the shadow, the deeper the layer will look (4.6).
Figure 4.6 The larger shadow displacements and angular radii increase the depth of the layer, which is the sense of vision
Shadow cropping
Unlike the layer border, the layer's shadow inherits from the shape of the content, not the boundary and corner radius. To calculate the shape of the shadow, Core animation takes the homestay map (including the child view, if any) into account, and then uses these to perfectly match the layer shape to create a shadow (see Figure 4.7).
Figure 4.7 The shadow is determined based on the outline of the homestay map.
There is a headache when the shadow and clipping are related: The shadow is usually outside the layer's boundary, and if you turn on the masksToBounds
attribute, all the content protruding from the layer will be cut off. If we add the shadow properties of the layer to our previous border example project, you will find the problem (see Figure 4.8).
Figure 4.8 maskToBounds
property cuts out shadows and content
Technically, the result can be understandable, but it's not exactly the effect we want. If you want to trim along the content, you need to use two layers: an empty outer layer that only draws shadows, and a masksToBounds
neatline layer with cropped content.
If we wrap the cropped view in a separate view on the right side of the previous project, we can solve the problem (4.9).
Figure 4.9, to the right, wrap the cropped view with an extra shadow transform view
We only use shadows on the outermost view, and the inner view is cropped. Listing 4.3 is the code implementation, and Figure 4.10 is the run result.
Listing 4.3 uses an additional view to solve the problem of shadow trimming
@interface Viewcontroller () @property (nonatomic, weak) Iboutlet UIView *layerview1; @property (nonatomic, weak) Iboutlet UIView *layerview2; @property (nonatomic, weak) Iboutlet UIView *shadowview; @end @implementation viewcontroller?-(void) viewdidload{[Super Viewdidload]; Set the corner radius on our layers Self.layerView1.layer.cornerRadius = 20.0f; Self.layerView2.layer.cornerRadius = 20.0f; Add a border to our layers self.layerView1.layer.borderWidth = 5.0f; Self.layerView2.layer.borderWidth = 5.0f; Add a shadow to layerView1 self.layerView1.layer.shadowOpacity = 0.5f; Self.layerView1.layer.shadowOffset = Cgsizemake (0.0f, 5.0f); Self.layerView1.layer.shadowRadius = 5.0f; Add same shadow to Shadowview (not layerView2) self.shadowView.layer.shadowOpacity = 0.5f; Self.shadowView.layer.shadowOffset = Cgsizemake (0.0f, 5.0f); Self.shadowView.layer.shadowRadius = 5.0f; Enable clipping on the second layer self.layerView2.layer.masksToBounds = YES;} @end
Figure 4.10 The right view, not the shaded view of the crop shadow.
shadowPath
Property
We already know that the layer shadow is not always square, but rather inherits from the shape of the layer content. This looks good, but the real-time calculation of Shadows is also a very resource-intensive one, especially when layers have multiple sublayers, and each layer has a transparent view of the homestay.
If you know in advance what your shadow shape will look like, you can improve performance by specifying one shadowPath
. shadowPath
is a CGPathRef
type (a pointing CGPath
pointer). CGPath
is a core graphics object that is used to specify any vector graphic. This property allows us to specify the shape of the shadow separately from the layer shape.
Figure 4.11 shows the different shading settings for the same boarding chart. As you can see, the graph we use is simple, but its shadow may be any shape you want. Listing 4.4 is the code implementation.
Figure 4.11 Using the shadowPath
specified arbitrary shadow shape
Listing 4.4 Creating a simple shadow shape
@interface Viewcontroller () @property (nonatomic, weak) Iboutlet UIView *layerview1; @property (nonatomic, weak) Iboutlet UIView *layerview2; @end @implementation viewcontroller-(void) viewdidload{ [Super Viewdidload]; Enable layer shadows self.layerView1.layer.shadowOpacity = 0.5f; self.layerView2.layer.shadowOpacity = 0.5f; Create a square shadow cgmutablepathref Squarepath = cgpathcreatemutable (); Cgpathaddrect (Squarepath, NULL, self.layerView1.bounds); Self.layerView1.layer.shadowPath = Squarepath; Cgpathrelease (Squarepath); ? Create a circular shadow cgmutablepathref Circlepath = cgpathcreatemutable (); Cgpathaddellipseinrect (Circlepath, NULL, self.layerView2.bounds); Self.layerView2.layer.shadowPath = Circlepath; Cgpathrelease (Circlepath);} @end
If it's a rectangle or a circle, it's CGPath
pretty straightforward. But if it is a more complicated graph, the UIBezierPath
class will be more appropriate, it is a objective-c wrapper class provided by Uikit on the Cgpath basis.
Layer Mask
Through masksToBounds
the properties, we can crop the graph along the boundary, and through cornerRadius
the properties we can also set a rounded corner. But sometimes what you want to show is not a rectangle or a rounded rectangle. For example, you want to show a picture with a star frame, or you want some scrolls to gradually fade into a background color instead of an abrupt boundary.
Using a 32-bit PNG image with an alpha channel is often the most convenient way to create a rectangle-free view, and you can assign it a transparent mask to implement. However, this method does not allow you to dynamically generate masks in a coded fashion, nor can you crop the sublayers or sub-views to the same shape.
Calayer has a property called mask
to solve this problem. This property is itself a calayer type, with the same drawing and layout properties as other layers. It is similar to a sublayer, which is laid out relative to the parent layer (that is, the layer that owns the property), but it is not an ordinary sublayer. Unlike the sublayers that are drawn in the parent layer, the mask
layer defines a portion of the visible area of the parent layer.
mask
The properties of a layer Color
are irrelevant, and what really matters is the outline of the layer. mask
properties are like a cookie cutter, the mask
solid portion of the layer is preserved and the others are discarded. (4.12)
If the mask
layer is smaller than the parent layer, only the mask
content within the layer is what it cares about, and everything else will be hidden.
Figure 4.12 Effect of the image and mask layer together
We'll show the code this process, create a simple project, and use the properties of the layer to work on the mask
picture. For the sake of simplicity, we use Interface Builder to create a picture layer that contains uiimageview. So we just need the code to implement the mask layer. Listing 4.5 is the final code, and figure 4.13 is the result after the run.
Listing 4.5 applying a mask layer
@interface Viewcontroller () @property (nonatomic, weak) Iboutlet Uiimageview *imageview; @end @implementation viewcontroller-(void) viewdidload{ [Super Viewdidload]; Create mask layer calayer *masklayer = [Calayer layer]; Masklayer.frame = self.layerView.bounds; UIImage *maskimage = [UIImage imagenamed:@ "Cone.png"]; Masklayer.contents = (__bridge id) maskimage.cgimage; Apply mask to image layer? Self.imageView.layer.mask = Masklayer;} @end
Figure 4.13 After using mask
the Uiimageview
The real calayer of the mask layer is that the mask diagram is not limited to static plots. Any layer that has layers can be used as mask
attributes, which means that your mask can be generated in real time through code or even animations.
Tensile filtration
Finally let's talk about minificationFilter
and magnificationFilter
attributes. In general, when our view displays a picture, it should be displayed correctly (meaning that it is displayed on the screen in the correct proportions and 1:1 pixels in the correct way). The reasons are as follows:
- can display the best picture quality, the pixels are neither compressed nor stretched.
- It's better to use memory, because that's all you have to store.
- The best performance of the CPU is not required for this additional calculation.
Sometimes, however, displaying a non-real-size picture is really the effect we need. For example, an avatar or thumbnail of a picture, such as a large image that can be dragged and stretched. In these cases, it is impractical to store different images for different sizes of the same picture.
When a picture needs to show a different size, an algorithm called stretch filtering works. It acts on the pixels of the original image and generates new pixels as needed to display on the screen.
In fact, redrawing the image size does not have a unified universal algorithm. This depends on the content that needs to be stretched, the need to zoom in or out, and so on. There CALayer
are three types of tensile filtration methods available for this purpose:
- Kcafilterlinear
- Kcafilternearest
- Kcafiltertrilinear
Minification (zoom Out) and magnification (enlarge image) The default filter is kCAFilterLinear
that this filter uses a bilinear filtering algorithm, which in most cases is performing well. The bilinear filtering algorithm produces a smooth, well-behaved stretch by eventually generating new values for multiple pixel sampling. But when the magnification is larger, the picture is blurred.
kCAFilterTrilinear
and kCAFilterLinear
very similar, in most cases they do not see any difference. However, compared with the bilinear filtering algorithm, the three-wire filter algorithm stores the picture (also called multi-map) in the case of multiple sizes, and then obtains the final result by combining the storage of large and small graphs.
The advantage of this method is that the algorithm can get the desired result from a series of images that are already close to the final size, that is to say, do not sample many pixels synchronously. This not only improves performance, but also avoids the problem of sampling failure due to rounding errors in small probabilities
Figure 4.14 for larger graphs, bilinear filtering and tri-linear filtering perform better
kCAFilterNearest
is a more arbitrary approach. From the name it is not difficult to see, this algorithm (also known as the recent filter) is to sample the nearest single pixel, regardless of other colors. This is very fast and does not blur the picture. However, the most obvious effect is to make the compressed picture worse, the image enlarged after the appearance of block or mosaic serious.
Figure 4.15 The recent filtering algorithm is much better for small graphs without slashes
In general, for smaller graphs or large graphs with very few diagonal lines, the most recent filtering algorithm preserves this distinct trait to show better results. But for most graphs, especially those with a lot of diagonal lines or curved contours, the recent filtering algorithm can result in worse results. In other words, linear filtering preserves the shape, and recent filtering preserves the difference in pixels.
Let's experiment. Let's change the clock item in chapter Three and display it in the LCD style digital way. We create a digital display using a simple pixel font (a font that is made of pixels, rather than a vector graphic), stored in a picture, and displayed (4.16) using the flattening technique introduced in chapter two.
Figure 4.16 A simple display of the LCD digital style pixel font using the flattening technique
We placed six views in Interface Builder, two in hours, minutes, seconds, and figure 4.17 shows how these six views are placed in Interface builder. If each has a fade-out outlets object will appear too much, so we use an IBOutletCollection
object to connect them with the controller, so that we can access the view as an array. Listing 4.6 is the code implementation.
Listing 4.6 shows an LCD-style clock
@interface Viewcontroller () @property (nonatomic, Strong) Iboutletcollection (UIView) Nsarray *digitviews; @property ( Nonatomic, weak) Nstimer *timer;?? @end @implementation viewcontroller-(void) viewdidload{[Super Viewdidload];//get spritesheet image UIImage *digits = [U IImage imagenamed:@ "Digits.png"]; Set up digit views for (UIView *view in self.digitviews) {//set contents view.layer.contents = (__bridge ID) Digi Ts. Cgimage; View.layer.contentsRect = CGRectMake (0, 0, 0.1, 1.0); view.layer.contentsGravity = Kcagravityresizeaspect; }//start Timer self.timer = [Nstimer scheduledtimerwithtimeinterval:1.0 target:self selector: @selector (tick) UserInfo: Nil Repeats:yes]; Set initial clock time [self tick];} -(void) Setdigit: (nsinteger) digit Forview: (UIView *) view{//adjust contentsrect to select correct digit view.layer.conte Ntsrect = cgrectmake (digit * 0.1, 0, 0.1, 1.0);} -(void) tick{//convert time to hours, minutes and seconds nscalendar *calendar = [[Nscalendar Alloc] Initwithcalendaridentifier:nsgregoriancalendar]; Nsuinteger units = Nshourcalendarunit | Nsminutecalendarunit | Nssecondcalendarunit; ? nsdatecomponents *components = [Calendar components:units fromdate:[nsdate Date]]; Set hours [self SETDIGIT:COMPONENTS.HOUR/10 forview:self.digitviews[0]]; [Self setDigit:components.hour% forview:self.digitviews[1]]; Set minutes [self SETDIGIT:COMPONENTS.MINUTE/10 forview:self.digitviews[2]]; [Self SetDigit:components.minute% forview:self.digitviews[3]]; Set seconds [self SETDIGIT:COMPONENTS.SECOND/10 forview:self.digitviews[4]]; [Self setDigit:components.second% forview:self.digitviews[5];} @end
4.18, it really works, but the picture looks blurry. The default kCAFilterLinear
options seem to disappoint us.
Figure 4.18 A blurred clock, caused by the default kCAFilterLinear
To be able to do this in figure 4.19, we need to add the following code to the FOR loop:
View.layer.magnificationFilter = Kcafilternearest;
Figure 4.19 Sets a clear display after the most recent filtering
Group Transparency
UIView has a alpha
property called to determine the transparency of the view. Calayer has an equivalent property called opacity
, both of which affect the sub-level. That is, if you set a property on a layer opacity
, its sublayers will be affected by this.
The common practice of iOS is to set the alpha value of a space to 0.5 (50%) so that it appears to be rendered unusable. It's good for a standalone view, but it's a bit odd when a control has a child view, and figure 4.20 shows a custom UIButton with a uilabel embedded in it, an opaque button on the left and the same button for 50% transparency on the right. We can notice that the outline of the label inside is not the same as the background of the button.
Figure 4.20 The right-hand fade button, the label inside is clearly visible
This is caused by a mixture of transparency, and when you display a layer with 50% transparency, each pixel of the layer typically displays its own color, and the other half shows the color underneath the layer. This is the performance of normal transparency. But if the layer contains a sub-layer that also shows 50% transparent, the view you see, 50% comes from the child view, 25% comes in the color of the layer itself, and the other 25% comes from the background color.
In our example, the buttons and emoticons are all white backgrounds. Although they are all 50% visible, the combined visibility is 75%, so the area where the label is located does not look as transparent as the surrounding part. So it looks like the sub-view is highlighted, making the display look awful.
Ideally, when you set the transparency of a layer, you want it to contain the entire layer tree as a whole as a transparent effect. You can do this by setting the Yes in the Info.plist file UIViewGroupOpacity
, but this setting affects the app and the app may be adversely affected. If UIViewGroupOpacity
not set, IOS 6 and previous versions will default to No (there may be some changes in later versions).
Another way is that you can set up a calayer called shouldRasterize
Property (see listing 4.7) for group transparency, and if it is set to Yes, the layer and its sublayers will be integrated into a whole picture before applying transparency, so there is no problem with transparency blending (4.21).
To enable the shouldRasterize
properties, we set the properties of the layer rasterizationScale
. By default, all layer stretches are 1.0, so if you use shouldRasterize
properties, make sure you set the rasterizationScale
properties to match the screen to prevent the retina screen from pixelated.
When shouldRasterize
UIViewGroupOpacity
together, performance issues arise (we will introduce the 12th "speed" and "Layer Performance" in chapter 15th), but the performance collisions are localized (the translator notes: This sentence needs to be translated).
Listing 4.7 using shouldRasterize
attributes to resolve group transparency issues
@interface Viewcontroller () @property (nonatomic, weak) Iboutlet UIView *containerview; @end @implementation viewcontroller-(UIButton *) custombutton{//create button CGRect frame = CGRectMake (0, 0, 150, 50); UIButton *button = [[UIButton alloc] initwithframe:frame]; Button.backgroundcolor = [Uicolor Whitecolor]; Button.layer.cornerRadius = 10; Add Label frame = CGRectMake (20, 10, 110, 30); UILabel *label = [[UILabel alloc] initwithframe:frame]; Label.text = @ "Hello World"; Label.textalignment = Nstextalignmentcenter; [Button Addsubview:label]; return button;} -(void) viewdidload{[Super Viewdidload]; Create Opaque button UIButton *button1 = [self CustomButton]; Button1.center = Cgpointmake (50, 150); [Self.containerview Addsubview:button1]; Create Translucent button UIButton *button2 = [self CustomButton]; ? Button2.center = Cgpointmake (250, 150); Button2.alpha = 0.5; [Self.containerview Addsubview:button2]; Enable rasterization for the translucent buttonButton2.layer.shouldRasterize = YES; Button2.layer.rasterizationScale = [UIScreen mainscreen].scale;} @end
Figure 4.21 the revised figure
Summarize
This chapter describes some of the visual effects that you can apply to layers, such as rounded corners, shadows, and masks. We also learned about stretch filters and group transparency.
In the fifth chapter, "Transformations," we'll look at layer changes and 3D transformations.
[IOS animation]-calayer visual effects