View & image correlation

In the course of the interview, the content of the article is basically carried from the big guy blog and self-understanding, may be a little messy, do not like spray!!

Ali, Byte: A Set of effective iOS Interview questions

1, the principle of Auto Layout, performance

  • nature

In iOS 6, Apple introduced the Layout algorithm Cassowary and implemented its own Layout engine Auto Layout.

Cassowary uses constraints to describe the relationships between views, so Auto Layout no longer focuses on frames but on the relationships between views. We only need to describe the set of constraints that represent the Layout relationships between views. Auto Layout includes not only the Cassowary algorithm, but also a whole set of engine systems, such as the Layout’s run-time life cycle, to manage the creation, updating and destruction of layouts uniformly.

This set of Layout Engine system is called Layout Engine, it is the core of Auto Layout, leading the entire interface Layout. Before each view gets its own Layout, the Layout Engine computes the constraints into the final frame. Each time the constraint changes, the Layout Engine recalculates and then goes to the Deferred Layout Pass to update the frame, which is then listened on again.

  • The principle of

Attribute1 = view2.attribute * multiplier + 8. A constraint is an equation. The developer describes the relationship between views as a set of constraints. Layout Engine solves these constraint equations through priority, and the result is the frame of each view. Then layoutSubviews are used for layoutSubviews layer by layer.

  • performance

Don’t worry about performance after iOS 12. In the past, the main reason is to create a new NSISEnginer (AutoLayout linear programming solver) to re-solve the constraint when updating, and the nested view needs to update the constraint during the layout process, so it is almost impossible to see… View Layout algorithms from Auto Layout talking about performance.

2. Difference between UIView and CALayer

  • contact

Each view has a layer, the view holds and manages the layer, and the view is a proxy for the layer.

  • The difference between

UIView is responsible for responding to events, participating in the response chain, and providing content to the layer.

CALayer does the drawing, the animation.

3. Event response chain

  • Hit-testing

Hit-testing looks for hit-test views, which are the view where the touch event is located.

After a touch event occurs, the system places the event in a queue managed by UIApplicaiton. UIAplication sends the event to the keyWindow, which finds the most suitable view in the view tree to respond.

  1. Whether the view itself can accept touch events;
  2. Whether the touch point is within its range;
  3. Iterate through subviews from back to front, repeating 1 and 2.
  4. If there is no subview that matches the criteria, you are the best fit.

Ensure that isUserInteractionEnabled is set to YES, isHidden is set to NO, and alpha is greater than 0.01.

Two important methods:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;   
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
Copy the code
  • Event passing mechanism

After the touch View is found by hit-testing. Call the View’s Touches method to do this:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
Copy the code

If the view calls the [super touches] method, the event is passed up the responder chain (child view to parent view).

The order is view -> superView -> viewController -> rootViewController -> Window -> UIApplication. If the last UIApplication is not processed, it is discarded.

4. Call timing of drawRect & LayoutSubviews

  • drawrect

    1. InitWithFrame:, but frame cannot be CGRectZero;

    2. Call setNeedsDisplay or setNeedsDisplayInRect:, drawing in the next drawing cycle;

    3. Change the frame when contentMode is UIViewContentModeRedraw.

  • layoutSubviews

    1. InitWithFrame:, but frame cannot be CGRectZero;

    2. SetNeedsLayout is marked as needed for the next Layout Cycle call;

    3. LayoutIfNeeded layout immediately;

    4. The frame itself changes.

    5. Add subview, subview frame changes;

    6. View added to UIScrollView, scroll UIScrollView;

    7. Rotate the screen (using automatic layout);

  • updateConstraints

    1. InitWithFrame:, but needs to be rewritten attribute requiresConstraintBasedLayout ` and returns YES;

    2. SetNeedsUpdateConstraint marks the need to update constraint for the next layout Cycle call;

    3. SetUpdateConstraintsIfNeeded, immediately update the constraints.

5, UI refresh principle

When the App processes interactive events, it creates a new UIView, it also modifies the existing View, and then it updates the constraints, adjusts the layout, and renders and displays

  1. setNeedsUpdateConstraints -> updateConstraints

  2. setNeedsLayout -> layoutSubviews

  3. Uiview. setNeedsDisplay -> calayer. setNeedsDisplay -> Wait for the next drawing cycle

  4. Calayer. display -> System drawing process/asynchronous drawing process

    Backing Store + CGContextRef -> layer. drawInContext -> delegate.drawInContext -> UIViewAccessibility.drawLayer:inContext: -> UIView(CALayerDelegate).drawLayer:inContext: -> UIView.drawRect

    4.2 Asynchronous drawing process: delegate.displayLayer: -> Other threads, create your own bitmap, draw your own -> finally in the main thread setContents

6. Implicit animation vs. display animation

  • An implicit animation

When the animatable properties of the individual layer are modified, there is an animation from the current value to the target value, which is an implicit animation. Implicit animation is done by the system framework.

  • According to the animation

An explicit animation is an animation that creates a CAAnimation object and submits it to the Layer for execution.

  • The difference between

Implicit animation always exists, turned off on demand; Display animations need to be created manually.

7. What is off-screen rendering

The GPU opens up a chunk of memory outside the current screen buffer for rendering.

There is no turning back when GPU is drawing. After a layer is drawn, it has formed a whole with previous layers and cannot be modified again.

For some layer with special effects, GPU can not complete rendering through a single traversal, but can only apply for another memory area, with the help of this temporary area to complete more complex and multiple modification and reduction operations.

  1. New render buffers need to be created and there is a significant memory overhead

  2. And you need to Switch the render Context Context Switch, which wastes a lot of events.

8, imageName & imageWithContentsOfFile difference

ImageNamed caches used images into memory. This cache persists even if the generated object is released by AutoReleasepool. ImageNamed tries to read from the cache first, which is more efficient, but incurs additional CPU time.

ImageWithContentsOfFile loads images directly from a file. Images are not cached, which is slow to load but doesn’t waste memory.

Unless an image is used frequently, use image with Content file in an economical manner.

9. Will multiple same images be loaded repeatedly

Depending on the loading method:

ImageNamed: Caches loaded images. Try to load it from cache first.

ImageWithContentsOfFile: it is not cached, so it is loaded repeatedly.

10, when is the picture decoded, how to optimize

Picture display process

  1. Copy data from disk to kernel buffer (system call);

  2. Copy data from the kernel buffer to the user control (where the process resides)

  3. Generate UIImageView, assign image data to UIImageView;

  4. If the image is not decoded, decoded to bitmap data;

  5. CATransaction captures changes in the UIImageView layer tree;

  6. The main thread Runloop submits the CATransaction in the final drawing Cycle;

    • If the data is not byte aligned, Core Animation copies another data for byte alignment.
  7. GPU processes bitmap data for rendering.

That is, images are decoded only when they need to be displayed.

Why decode it?

PNG, JPEG and other commonly used image formats are compressed, while the screen display is bitmap.

How to decode it?

Data Buffer: Raw Data stored in memory. Images can be stored in different formats, such as JPEG, PNG. The Data Buffer cannot be used to describe the bitmap pixel information of the image.

Image Buffer: How an Image is stored in memory. Each element describes a pixel. It is stored in memory in the same way as a bitmap.

Frame Buffer: A Buffer of frames used for display on the display. Stored in vRAM (Video RAM).

The process of converting a Data Buffer into an Image Buffer can be called decoding. In other words, convert undecoded CGImages into bitmaps. The core methods are:

CGContextRef
CGBitmapContextCreate(
    void * __nullable data,
    size_t width, size_t height,
    size_t bitsPerComponent,
    size_t bytesPerRow,
    CGColorSpaceRef cg_nullable space,
    uint32_t bitmapInfo);
Copy the code
  • Argument parsing
    • Data: If the value is NULL, the system automatically allocates and releases the required memory. If not NULL, it should point to a piece of memory with bytesPerRow * height;

    • Width/height: the height and width of the image;

    • BitsPerComponent: The number of bits used for each color component of a pixel. The RGB space is one byte, i.e. 8 bits.

    • BytesPerRow: The number of bytes used in each line of the image, width * bytes per pixel at least. If the value is 0 or NULL, the system automatically calculates the value and optimizes the cache line alignment.

    • Space: color space, usually RGB;

    • BitmapInfo: the layout of the bitmap information, about the same understanding for ARGB (kCGImageAlphaPremultipliedFirst) or RGBA (kCGImageAlphaPremultipliedLast). If there is a transparent channel, the incoming kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst, Otherwise the incoming kCGBitmapByteOrder32Host | kCGImageAlphaNoneSkipFirst.

      Which CGImageAlphaInfo should we use? , UIGraphicsBeginImageContextWithOptions – Apple, or SDWebImage and YYImage under study.

Quartz 2D Programming Guide

When to decode?

After the image is set to UIImageView.image or layer.contents, CGImage data is decoded before the layer is submitted to the GPU. This step happens in the main thread and is inevitable.

How to optimize?

Force decoding ahead of time.

  • Canvas redraw decoded

The image is drawn to the canvas with CGContextDrawImage(), and the data from the canvas is extracted as the image.

if(! cgImage)returnNULL; size_t width = CGImageGetWidth(cgImage); size_t height = CGImageGetHeight(cgImage); size_t bytesPerRow = width * 4; size_t bytesTotal = bytesPerRow * height; / / / in memory where to draw the void * bitmapdata = calloc (bytesTotal, sizeof (uint8_t));if(! bitmapdata) { fprintf(stderr,"Failed to calloc 'bitmapdata'");
    returnNULL; } /// color space // static CGColorSpaceRef colorSpaceRef; static dispatch_once_t onceToken; Dispatch_once (&oncetoken, ^{ colorSpaceRef = CGColorSpaceCreateWithName(kCGColorSpaceSRGB); }); /** * * You use thisfunction to configure the drawing environment for rendering into a bitmap. The format for the bitmap is a ARGB 32-bit integerpixel format using host-byte order. If the opaque parameter is YES, the alpha channel is ignored and the bitmap is treated as fully opaque (kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host). Otherwise, each pixel uses a premultipled ARGB format (kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host). * From https://developer.apple.com/documentation/uikit/1623912-uigraphicsbeginimagecontextwitho?language=occ * */ /// Mobile devices are small - end storage. CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host; BOOL hasAlpha = CGImageHasAlpha(cgImage); bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst; / / / here actually and [CALayer drawInContext:] and UIGraphicsBeginImageContext () the same CGContextRef context = CGBitmapContextCreate(bitmapData, /* Where to draw in memory. */ width, /* bitmap width */ height, /* bitmap height */ 8, /* the number of bits per color component of a pixel, */ color space */ bitmapInfo/bitmap layout information */); */ color space */ bitmapInfo/bitmap layout information */if(! context)returnNULL; // decode CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage); / / / from the context to take out the decoded images CGImageRef rstImage = CGBitmapContextCreateImage (context); CFRelease(context);return rstImage;
Copy the code
  • Decoded with CGDataProviderCopyData

Such as subheadings

size_t width = CGImageGetWidth(cgImage); size_t height = CGImageGetHeight(cgImage); CGColorSpaceRef colorSpace = CGImageGetColorSpace(cgImage); size_t bitsPerComponent = CGImageGetBitsPerComponent(cgImage); size_t bitsPerPixel = CGImageGetBitsPerPixel(cgImage); size_t bytesPerRow = CGImageGetBytesPerRow(cgImage); CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(cgImage); CGDataProviderRef dataProvider = CGImageGetDataProvider(cgImage); CFDataRef data = CGDataProviderCopyData(dataProvider); /// CGDataProviderRef newProvider = CGDataProviderCreateWithCFData(data); CGImageRef newImage = CGImageCreate(width, height, bitsPerComponent, /* Pixel color fraction bits */ bitsPerPixel, /* per pixel fraction bits */ bytesPerRow, /* Per line fraction */ colorSpace, /* Color fraction */ bitmapInfo, /* Bitmap layout information */ newProvider, /* Picture data */ NULL, /* Image decoding array. Remap each color component */ according to the provided upper and lower limitsfalse, /* Whether to interpolate. Corresponding pixels is smoother * / kCGRenderingIntentDefault / * how to deal with is not the color of the graphics context target color gamut space * /); // release the intermediate variable CFRelease(data); CFRelease(newProvider);return newImage;
Copy the code

11. How to optimize image rendering

Optimization starts from two directions: space and time.

Images need to be transferred to the GPU, so less space means less time.

  • space
  1. Limit image file size;

  2. Limit image caching. Do not cache unless you use it frequently;

  3. Downsampling of network pictures (local pictures who will put so large, downsampling at the same time decoding);

  4. Catalogs with Image Asset, high compression rate (disadvantage is only loaded with imageNamed:);

  5. Use Mmap to map files directly into memory and access them directly from disk. NSData can assume that a piece of disk space is in the content.

  6. Limit the render format to SRGB (4 bytes per pixel) and display P3 wide color gamut to 8 bytes per pixel (for devices after iPhone 7);

  7. Using UIGraphicsImageRenderer UIGraphicsBEginImageContextWithOptions instead.

  • time
  1. Pre-forced decoding;

  2. Use images that fit the size: byte alignment.

12. What is the phenomenon if the refresh rate of GPU exceeds the 60Hz refresh rate of iOS screen and how to solve it

  • After the GPU is drawn, it is put into the frame buffer. The video controller periodically reads the contents of the buffer and displays them on the screen.

If the buffer allows overwriting, there must be some frames that have not been read by the video controller has been completely overwritten or partially overwritten, so there will be frame drop or picture tear; If overwriting is not allowed, drop frames directly.

  • How to solve

Double buffering mechanism + VSync signal.

Do things ~ ~ ~

A,Auto LayoutAutomatic layout

Auto Layout, automatic Layout It’s essentially a linear equation parsing engine.

When developing an interface with Auto Layout, we need to focus on the relationship between views rather than the position and size of the frame. When we describe the set of constraints on the Layout relationship between views, Auto Layout resolves the final frame for each view and applies it to each view.

1.Auto LayoutPast lives

When Apple introduced the 4-inch iPhone 5 in 2012, the change in size ratio made the already laborious manual layout even more difficult. In iOS 6, Apple introduced Auto Layout, an automatic Layout engine based on Layout algorithm Cassowary.

1997 Alan Borning, Kim Marriott, Peter Stuckey et al published a paper “Solving Linear Arithmetic Constraints for User Interface Applications”, A Cassowary constraint-solving algorithm is proposed to solve the layout problem, and the code is published on Cassowary website.

However, the syntax of Using Auto Layout is so disgusting that even the Visual Format Language (VFL) hasn’t made Auto Layout popular, with constraints on one widget that can write several screens and performance issues. It is not until the advent of navigation (Swift SnapKit) that Auto Layout is widely used for its simple chained syntax.

2,Auto LayoutThe nature of

Auto Layout includes not only the Cassowary algorithm, but also a whole set of Layout engine relationships such as the Layout’s runtime life cycle, which is used to manage the creation, update and destruction of layouts in a unified manner.

This set of Layout Engine system is called Layout Engine, is the core of Auto Layout, leading the entire interface Layout.

2.1 Layout Cycle

The Layout of Auto Layout is delayed. It is not done immediately after the constraint changes. The goal is to implement batch update constraints and draw views, avoiding frequent traversal of the view hierarchy and optimizing performance. Of course, we can use layoutIfNeeded to force immediate updates.

At WWDC 2020, Apple gave this image:

A Layout Cycle is a process that is executed within a Runloop.

Application Run Loop

Runloop automatically collects the changed constraints and sends them to the Layout Engine.

Runloop then computes the data from the Layout Engine and renders the corresponding view on the GPU.

Constraints Change

The process consists of two steps: changing the expression and recalculating the Layout with the Layout Engine.

Change constraint expression

Constraints are stored as linear expressions in the Layout Engine. Any changes to Constraints are Constraints Change.

These changes include:

  1. Constrained objectactivate / deactivate;
  2. Set up theconstant;
  3. Set up thepriority;
  4. The view ofadd / removeRemoving a view automatically removes the associated constraint.
Layout EngineRecalculate layout

Constraint expressions express variables related to view position and size.

When the constraints are updated, the Layout Engine recalculates the Layout, and these variables are likely to be updated.

After recalculation, the superview of the view whose position and size changed due to constraint update would be marked as appreciable layout, that is, call superview.setNeedsLayout to mark.

After that, the view’s new frame is in the Layout Engine. But the view has not updated its position, size. The Deferred Layout Pass is performed.

Deferred Layout Pass

The Deferred Layout Pass does two steps on the view tree: update the constraint and reassign the view’s frame.

Update the constraints

Unlike Constraints Change, the update constraint means “traversing the view tree from bottom to top (subview to superview), Update the view constraints by calling updateConstraints (UIViewController for updateViewConstraints) on all views marked with appreciable Layout.

We can override the updateCnstraints method to listen in on this process.

Why bottom-up? Because the constraints of the child view affect the constraints of the parent view.

We can call setNeedsUpdateConstraints to trigger this process manually.

Apple recommends that this process be triggered manually in two cases:

  1. When changing constraints is too slow (batching makes updating constraints faster here);
  2. When the view changes frequently (avoid multiple changes)
Reassign the view’s frame

  • Top-down (from parent view to child view)Traverse the calllayoutSubviewsUIViewController corresponds tolayoutWillLayoutSubviews) so that the view can lay out its children.

We can override layoutSubviews to listen to this process, but only if Auto Layout fails. (When overridden, you will find that the view itself has a new frame before the method call, while the subview’s frame is still the old value.)

Why top-down? Because you can’t determine the child view until you determine the parent view.

  • fromLayout EngineTo copy the location and size information of the subview and set it to the corresponding view.

As shown below, iOS is setCenter: and setBounds; MacOS is setFrame:.

2.2 Render Loop

I won’t cover interface rendering here, but I’ll mention Render Loop. I’ll just translate it as a render loop.

Render Loop is a procedure that executes 120 times per second to ensure that all views are ready for each frame. Its implementation is divided into three steps:

  1. Update/modify constraints: Update constraints layer by layer from the subview up to the window;

  2. Adjust layout: Adjust layout layer by layer from the parent view.

  3. Render and display: Render layer by layer from the superview.

Here’s a stolen image from WWDC 2018: High-performance Auto Layout:

Layout EngineRender Loop

When a view is Constrain Change, the Layout Engine solves the constraint equation on the view and the result is the view’s frame. (Corresponding to 2.2 Auto Layout – Applicaiton Run Loop)

Once the equation is solved, the Layout Engine tells the corresponding view to call the parent view’s setNeedsLayout method to update the constraints. (Corresponding to the previous 2.2 Auto Layout cycle – Constraints change-layout Engine recalculates the Layout)

When the update constraints are complete, progress to the layout phase. Each view reads the frame of the child view from the Layout Engine, and then calls layoutSubviews to adjust the Layout of the child view. – Deferred Layout Pass – Reassign the view frame

Render LoopSpecific operation of

The corresponding method of each Render Loop step is as follows:

What to do Who will do Who can let it dry
Update the constraints updateConstraints setNeedsUpdateConstraints,updateConstraintsIfNeeded
Adjust the layout layoutSubviews setNeendsLayout,layoutIfNeeded
Rendering shows drawRect: setNeedsDisplayInRect:
Update the constraints

- (void)updateConstraints

When?

  1. Called when initWithFrame:

    But you need to rewrite attributes requiresConstraintBasedLayout and returns YES.

  2. The next Layouty Cycle is automatically called when it is marked as needing an update:

    Call setNeedsUpdateConstraints;

    When the constraint changes, the next Render Loop will automatically call LayoutSubviews for layout.

  3. Trigger immediately (manually) if there is a tag that needs to be updated:

    Call updateConstraintsIfNeeded.

    When the constraint changes, the next Render Loop will automatically call LayoutSubviews for layout.

Adjust the layout

- (void)layoutSubviews

When?

  1. Called when initWithFrame:

    However, the rect value cannot be CGRectZero.

  2. Marked as needed, the next Layout Cycle is automatically called:

    Call setNeedsLayout.

  3. Trigger immediately (manually) when there is a tag that needs to be laid out:

    Call layoutIfNeeded

  4. When its frame changes, the constraint causes the frame to change.

  5. When a subview or subview frame changes, the constraint causes the frame to change.

  6. View is added to UIScrollView, scroll UIScrollView.

According to

- (void)drawRect:(CGRect)rect

When?

  1. Trigger when initWithFrame:

    The frame value cannot be CGRectZero.

  2. The next render loop will automatically call:

    Calling setNeedsDisplay.

3,Auto Layoutthinking

Before using Auto Layout, we need to get into the habit of thinking in terms of view relationships rather than position and size.

  • Use the layout constraint NSLayoutConstraint to describe the relationship between two views, which is essentially an equation. But this equation can be either linear or inequality.

Attribute1 (relationship) view2.attribute2 * multiplier + constant

RedView’s head is equal to (1 times the tail of BlueView, offset 8 to the right). RedView is to the right of BlueView, and the horizontal interval between the two is 8.

  • Only NSLayoutAttribute layout attributes of the same type can constrain each other.

Start with a table of commonly used layout properties:

Attributes belonging to the same category can be applied to the formula above to constrain each other (I’ll write the abbreviation below).

Leading and Trailing correspond to Left and Right, but Left and Right cannot be associated with Leading and Trailing. Moreover, the correspondence is different in languages with different reading habits:

From left to right To the left to the right
Leading Left Right
Trailing Right Left

Based on these principles, it is recommended to use Leading and Trailing in development rather than Left and Right, since internationalization is also an issue.

  • The equality relations that constrain equations

As I said, this equation can be either an equation or an inequality. This means that the [equal =] sign in the middle of this equation can be something else. Exactly, it could be equal to =, it could be less than or equal to less than or equal to or greater than or equal to or greater than or equal to.

Apple provides an enumeration like this:

typedef NS_ENUM(NSInteger, NSLayoutRelation) { NSLayoutRelationLessThanOrEqual = -1, Less than NSLayoutRelationEqual = 0, / / / / / / equal NSLayoutRelationGreaterThanOrEqual = 1, / / / is greater than or equal to};Copy the code
  • The priority of the constraint

As we mentioned earlier, there is a fault tolerant Layout Pass. If there is a missing or conflicting constraint in all the constraints we add. A program note is a program, not a person, and it cannot decide subjectively which is more important.

Thus, Apple provides the priority of the layout constraint, which is essentially a float value (Swift is a structure initialized by float).

The system provides the following default priorities:

/// Is essentially a float value. typedef float UILayoutPriority NS_TYPED_EXTENSIBLE_ENUM; /// mandatory constraint, which means that the constraint must be satisfied. /// The highest priority, also the default priority. /// The default priorities will crash if they conflict. UILayoutPriority UILayoutPriorityRequired = 1000; / / / button UIButton content expansion constraints priority / / / is the outward expansion of UILayoutPriority UILayoutPriorityDefaultHigh = 750; / / / button UIButton content compression constraints priority / / / is tightly with its text UILayoutPriority UILayoutPriorityDefaultLow = 250; /// The view is based on the target size parameter. /// Do not use it to prioritize a constraint. Use - [UIView systemLayoutSizeFittingSize:]. / / / when we call [view systemLayoutSizeFittingSize: CGSizeMake (50, 50)], the view will be calculated by one of the most accord with CGSizeMake (50, 50) size. UILayoutPriority UILayoutPriorityFittingSizeLevel = 50; / / / drag controls recommended priority, it may be possible to adjust the window size UILayoutPriority UILayoutPriorityDragThatCanResizeScene = 510; /// The window wants to keep the same size priority. /// In general, do not use it to prioritize constraints. UILayoutPriority UILayoutPrioritySceneSizeStayPut = 500; /// Drag the priority of the split view separator. Don't adjust the window size UILayoutPriority UILayoutPriorityDragThatCannotResizeScene = 490;Copy the code

To prevent crashes due to layouts, it is recommended that the priority value that must be constrained be set to 999. This preserves its priority and avoids the risk of collapse. How do you set UILayoutPriority? – stack overflow

  • instrinsicContentSize / Compression Resistance Size Priority / Hugging Priority

Some views have instrinsicContentSize properties, such as UIlabel, UIButton, UITextField, UIImageView, selection controls, progress bars, sections, etc. They can calculate their size from their internal content, such as UILabel after setting text and font.

Based on the content of this kind of control, Apple provides two specific constraints: the shrinkage constraint Content Hugging and the expansion constraint Content Compression Resistance. These two constraints are referred to simply as CHCRS. You can understand these two constraints by looking at this diagram.

In this case, it can be done with these two functions:

/* * @abstract Sets the compression priority of the specified axis * * @param priority * the higher the value, the more tightly the contents of the view. * that is, it doesn't get bigger as the superview gets bigger. * * @param axis * Axial, divided into horizontal and vertical. Have UILayoutConstraintAxisHorizontal and UILayoutConstraintAxisVertical * / - (void)setContentHuggingPriority:(UILayoutPriority)priority forAxis:(UILayoutConstraintAxis)axis; /* * @abstract sets the priority of scaling up the specified axis * * @param priority * The priority of scaling up the specified axis * * @param priority * The larger the Content Compression Resistance, the more complete the subview Content display * * @param axis * Axial, divided into horizontal and vertical. Have UILayoutConstraintAxisHorizontal and UILayoutConstraintAxisVertical * / - (void)setContentCompressionResistancePriority:(UILayoutPriority)priority forAxis:(UILayoutConstraintAxis)axis;Copy the code

Here is a test example:

The test code is:

[self.label1 setContentHuggingPriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisHorizontal];
    
[self.label2 setContentCompressionResistancePriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisHorizontal];
Copy the code
  • Hierarchy of constraints

It is best to use two views that are directly related to each other for constraints, such as a parent and a brother. It is best not to use view1.subview1 and view2.subview2.

In this picture, view1 and view2 are brothers. Subview1 is a subview of view1, subview2 is a subview of view2. But subview1 has nothing to do with subview2.

So, according to the above. Subview1 and subview2 are best not used to constrain each other.

4, the use ofAuto Layout

Thanks to the majority of developers, there are many ways to use Auto Layout now, we will introduce one by one.

Either way, an Auto Layout is eventually converted to an instance of the NSLayoutConstraint object. Let’s start by recognizing a few important properties and methods:

@property (nullable,readonly, assign) id firstItem; /// as a dependency view of the relationship. In other words, which view is used to constrain the object view @property (Nullable,readonly, assign) id secondItem; // the current object view needs to constrain a property @property (readonly) NSLayoutAttribute firstAttribute; // Which attribute of the dependent view is used to constrain the specified attribute of the object view @property (readonly) NSLayoutAttribute secondAttribute; // the equation is equal or unequal @property (readonly) NSLayoutRelation relation; // the multiples @property (readonly) CGFloat multiplier; /// @property CGFloat constant; // NSLayoutConstraint is enabled @Property (getter=isActive) BOOL active; @property (nullable, copy) NSString * Identifier; // NSLayoutConstraint @property (nullable, copy) NSString *identifier; // class method: Enable all constraints in the passed argument + (void)activateConstraints:(NSArray<NSLayoutConstraint *> *)constraints; /// Class method: Deactivate all constraints in the passed argument + (void)deactivateConstraints:(NSArray<NSLayoutConstraint *> *)constraintsCopy the code

All right, here I go…

4.1 nativeNSLayoutConstraint

If I had to develop in this way, I would probably prefer to use manual layout, or encapsulate a concise usage scheme myself…

No more words, just start!!

Objective:

theView.x = 50,

theView.y = 100,

Width = superview.width * 0.5 + 50

Height = superview.height * 0.5 + 100

UIView *theView = [UIView new]; [self.view addSubview:theView]; theView.backgroundColor = [UIColor systemBlueColor]; theView.translatesAutoresizingMaskIntoConstraints = NO; /// theView.leading = 1 * superview.centerX + 50 NSLayoutConstraint *leading = [NSLayoutConstraint constraintWithItem:theView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeading multiplier:1 constant:50]; /// theView.top = 1 * superview.top + 100 NSLayoutConstraint *top = [NSLayoutConstraint constraintWithItem:theView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1 constant:100]; // theview. width = 0.5 * superview.width + 50 NSLayoutConstraint *width = [NSLayoutConstraint constraintWithItem:theView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.view Attribute: NSLayoutAttributeWidth multiplier: 0.5 constant: 50]; // theview. height = 0.5 * superview.height + 100 NSLayoutConstraint *height = [NSLayoutConstraint constraintWithItem:theView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.view Attribute: NSLayoutAttributeHeight multiplier: 0.5 constant: 100]; [self.view addConstraints:@[centerX, centerY, width, height]]; [NSLayoutConstraint activateConstraints:@[leading, top, width, height]];Copy the code

With very few line breaks, this long…

NSLayoutConstraintThe advantages and disadvantages of

Pros: The only pros is that it really is Auto Layout.

Disadvantages: Redundant code and poor readability; It is not convenient to update or delete constraints. You need to use variables to record constraints or find corresponding constraints by matching.

4.2 Visual LanguageVFL

VFL, Visual Format Language, is also a Visual Language.

With this layout, the NSLayoutConstraint object is also created to create the constraint. NSLayoutConstraint Every time an object is created, it is a constraint.

VFL uses string encoding to create constraints that can pass in any number of views and any number of layout relationships.

You can therefore use VFL to create multiple constraint relationships at once, with methods that return a collection of NSLayoutConstraint objects.

UIView *blueView = [UIView new]; [self.view addSubview:blueView]; blueView.backgroundColor = [UIColor systemBlueColor]; blueView.translatesAutoresizingMaskIntoConstraints = NO; UIView *redView = [UIView new]; [self.view addSubview:redView]; redView.backgroundColor = [UIColor systemRedColor]; redView.translatesAutoresizingMaskIntoConstraints = NO; // NSDictionary *views = @{// @"blueView": blueView,
//        @"redView": redView, // }; / / / Apple for such a quick macro views to create such a dictionary NSDictionary * = NSDictionaryOfVariableBindings (blueView, redView); // We can create a dictionary and pass in metrics as a specific number to use in the VFL statement. We can replace all 50 in the horizontal layout VFL statement below with space // NSDictionary *spaceMetrics = @{@"space"50: @}; /* * explain * @"H:|-50-[blueView(100)]-50-[redView(==blueView)]"* border - the gap width of 50 - width to blueView - blank width of 50-100 with high blueView width are the same redView * * most the right side and if statements - 50 - |, And remove blueView width is specified, which is H: | - 50 - [blueView] - 50 - [redView (= = blueView)] - 50 - | * boundary width - the width of 50 blank - blueView - for 50 - and blank BlueView width and height are the same redView - width 50 blank - border * and at this point, the width of blueView and redView will be adaptive to (superview.width -50-50-50) / 2. * * * * NSLayoutFormatAlignAllTop | NSLayoutFormatAlignAllBottom made all views according to their top edge and bottom edge of the, is the vertical direction at the same location * * * @"V:|-100-[blueView(200)]"* Boundary - blank height of 100 - blueView height of 200 * */ /// Horizontal NSArray *horizontalConstraints = \ [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-50-[blueView(100)]-50-[redView(==blueView)]"options:NSLayoutFormatAlignAllTop | NSLayoutFormatAlignAllBottom metrics:nil views:views]; / / / vertical NSArray * verticalConstraints = \ [NSLayoutConstraint constraintsWithVisualFormat: @"V:|-100-[blueView(200)]"
                                        options:kNilOptions
                                        metrics:nil
                                          views:views];

[NSLayoutConstraint activateConstraints:horizontalConstraints];
[NSLayoutConstraint activateConstraints:verticalConstraints];
Copy the code

I do not know this way, want to learn specific friends please move to IOS development of automatic Layout -VFL language, IOS Auto Layout alignment options, Apple official tutorial VFL

VFLThe advantages and disadvantages of

Advantages: Simple code, multiple views can be laid out at once

Disadvantages:

  1. Height = 2 * blueview. height;
  2. It is, after all, a string and cannot be checked in the compiler;
  3. There is a learning cost.

I personally don’t recommend this layout, but it’s good to understand

4.3 Interface Builder

Apple recommends using Interface Builder for layout. That’s really fast development.

I am usually pure code development, I will not demonstrate here. And even if the demo, also can’t see the demo process, just a drag good also don’t have what good-looking!!

Advantages:

  1. Can directly see the layout effect, no need to run;
  2. Storyboards come with layout checks, warning of constraint ambiguity, and constraint relationship errors.
  3. Meet responsible interface can be combined with code development.

Disadvantages:

  1. Multiplayer development is a nightmare;
  2. When requirements change, it is much more difficult to modify than pure code;
  3. The product manager will look at you directly, do you panic?

4.4 Native anchor pointsNSLayoutAnchor

IOS 9, Apple provides a new Auto Layout development method: Anchor point NSLayoutAnchor.

Compared to NSLayoutConstraint, this scenario is much faster to develop.

NSLayoutAnchor has multiple objects that are directly UIView properties that correspond to the layout attribute NSLayoutAttribute of NSLayoutConstraint.

NSLayoutAnchor has three subclasses that correspond to different layout attributes:

A subclass The anchor type The anchor attribute
NSLayoutXAxisAnchor The X axis direction leadingAnchor,trailingAnchor,leftAnchor,rightAnchor,centerXAnchor
NSLayoutYAxisAnchor Y direction topAnchor,bottomAnchor,centerYAnchor,firstBaselineAnchor,lastBaselineAnchor
NSLayoutDimension size widthAnchor,heightAnchor

Code on!!

UIView *blueView = [UIView new]; [self.view addSubview:blueView]; blueView.backgroundColor = [UIColor systemBlueColor]; blueView.translatesAutoresizingMaskIntoConstraints = NO; UIView *redView = [UIView new]; [self.view addSubview:redView]; redView.backgroundColor = [UIColor systemRedColor]; redView.translatesAutoresizingMaskIntoConstraints = NO; /// blueView.leading = 1 * superview.leading + 0 [blueView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor].active = YES; /// blueView.top = 1 * superview.top + 100 [blueView.topAnchor constraintEqualToAnchor:self.view.topAnchor constant:100].active = YES; / / / blueView. Width = 0.3 * superview in width + 20 [blueView. WidthAnchor constraintEqualToAnchor: self. The widthAnchor Multiplier: 0.3 constant: 20]. Active = YES; /// blueView.height = 0 * nil + 50 [blueView.heightAnchor constraintEqualToConstant:50].active = YES; /// redView.leading = 1 * blueView.trailing + 20 [redView.leadingAnchor constraintEqualToAnchor:blueView.trailingAnchor constant:20].active = YES; /// redView.trailing = 1 * superview.trailing - 10 [redView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-10].active = YES; /// redView.top = 1 * blueView.bottom + 30 [redView.topAnchor constraintEqualToAnchor:blueView.bottomAnchor constant:30].active = YES; /// redView.bottom = 1 * superview.bottom - 50 [redView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor constant:-50].active = YES;Copy the code

I will directly set the active property of the returned NSLayoutConstraint object to YES, which is activated. It is also possible to assign the returned object to a variable and then use +[NSLayoutConstraint activateConstraints:] to enable the constraint.

In addition, like NSLayoutAttribute, leadingAnchor/trailingAnchor and leftAnchor/rightAnchor are not mutually binding.

NSLayoutAnchorThe advantages and disadvantages of

Advantages:

  1. The original;
  2. Multipliers: multipliers: 0; multipliers: 0; multipliers: 0;
  3. Relatively neat, relative, of courseNSLayoutConstraint;
  4. Xcode will point out some errors in the compiler, such as different types of attributes not being mutually binding.

Disadvantages:

  1. IOS 9 and above is only supported, but this is not a drawback now;
  2. Update, delete andNSLayoutConstraintThe same;
  3. It’s not simple enough to use.

4.5 UIStackView

IOS 9, Apple didn’t just introduce NSLayoutAnchor, they introduced this. High production as high as??

To be honest, I know about it, but I’ve never used it…

UIStackView is perfect for this layout:

That is, a bunch of views are laid out in one direction, one after the other. However, it is necessary to use nested:

The two views on the far left, dark green and purple. A single UIStackView cannot do this.

UIStackView attributes and methods:

/// The layout can be divided into two types: Vertical and horizontal UILayoutConstraintAxisHorizontal UILayoutConstraintAxisVertical @ property (nonatomic) UILayoutConstraintAxis axis;  // arrangeSubiviews Layout along the axis @Property (nonatomic) UIStackViewDistribution; /// arrangeSubiviews layout along the axis @property(nonatomic) UIStackViewDistribution; // arrangedSubviews are arranged along the vertical axis of the layout @property(nonatomic) UIStackViewAlignment alignment; // arrangeSubiviews spacing @property(nonatomic) CGFloat spacing; // Add view to arrangeSubiviews, /// add view as UIStackView subview - (void)addArrangedSubview:(UIView *)view; /// remove view from arrangeSubiviews // but view is still UIStackView subview - (void)removeArrangedSubview:(UIView *)view; /// Insert view into arrangeSubiviews // Note stackIndex is out of bounds, otherwise it will crash /// stackIndex is [UIStackView subviews].count, -AddArrangedSubView: - (void)insertArrangedSubview:(UIView *) View atIndex:(NSUInteger)stackIndex;Copy the code

For details on the distribution and alignment properties, see UIStackView

Code on!!

- (void)didClickButton:(UIButton *)sender { switch (sender.tag) { case 100: { UILabel *theView = [UILabel new]; theView.textAlignment = NSTextAlignmentCenter; Theview.backgroundcolor = [UIColor colorWithRed: arc4Random () % 256/255.0 Green: arc4Random () % 256/255.0 Blue: arc4Random () % 256/255.0 alpha:1]; NSMutableString *title = [NSMutableString stringWithString:@"Test"]; int length = arc4random() % 3; for (int i = 0; i < length; ++i) { [title appendFormat:@"Test"]; } theView.text = @"TestTest"; [stackView addArrangedSubview:theView]; [UIView animateWithDuration:1 animations:^{ [self->stackView layoutIfNeeded]; }]; break; } case 101: { UIView *theView = [stackView subviews].lastObject; if (nil == theView) return; [stackView removeArrangedSubview:theView]; [theView removeFromSuperview]; [UIView animateWithDuration:0.5 animations:^{[self->stackView layoutIfNeeded];}]; break; } default: break; } } - (void)display_UIStackView { stackView = [[UIStackView alloc] initWithFrame:CGRectMake(0, 100, self.view.bounds.size.width, 300)]; [self.view addSubview:stackView]; stackView.backgroundColor = [UIColor systemBlueColor]; / / / layout direction stackView. Axis = UILayoutConstraintAxisHorizontal; /// Spacing between subviews stackview. spacing = 10; What rules to / / / child controls based on layout stackView. Distribution = UIStackViewDistributionFill; UIButton *addButton = [[UIButton alloc] initWithFrame:CGRectMake(50, 500, 100, 40)]; [self.view addSubview:addButton]; addButton.tag = 100; [addButton setTitle: @ "add" forState: UIControlStateNormal]; [addButton setTitleColor:[UIColor systemGreenColor] forState:UIControlStateNormal]; [addButton addTarget:self action:@selector(didClickButton:) forControlEvents:UIControlEventTouchUpInside]; UIButton *removeButton = [[UIButton alloc] initWithFrame:CGRectMake(200, 500, 100, 40)]; [self.view addSubview:removeButton]; removeButton.tag = 101; [removeButton setTitle: @ "remove" forState: UIControlStateNormal]; [removeButton setTitleColor:[UIColor systemRedColor] forState:UIControlStateNormal]; [removeButton addTarget:self action:@selector(didClickButton:) forControlEvents:UIControlEventTouchUpInside]; }Copy the code

UIStackViewThe particularity of

  1. View layout is correct only with addArrangedSubview: To UIStackView; Use addSubview: The added view is actually in uistackView. subviews, but its frame is (0, 0, 0, 0).

  2. UIStackView is only involved in layout, not rendering. That is, setting backgroundColor to a UIStackView has no effect, nor do rounded corners and other effects;

  3. With the isHidden status set to YES for a view in arrangedSubviews, the view still exists in arrangedSubviews and in the normal layout position, but will not be displayed and will not affect the layout of other views. If it’s not UIStackView, even if view.ishidden is YES, the constraint still exists and still affects the layout.

  4. UIView. IsHidden is an animation attributes, but add to UIStackView. ArrangedSubviews view of its isHidden is already to animation.

  5. To use the layout of UIStackView, you usually need to set the view’s instrinsicContentSize and CHCR, InstrinsicContentSize/Compression Resistance Size Priority/Hugging Priority!

UIStackViewThe advantages and disadvantages of

Advantages:

  1. FDStackView, which is native and maintained by Baidu team, can be backward compatible to iOS 6 with no code invasion and no learning cost. It is good to use UIStackView directly.

  2. Add and remove native animations.

Disadvantages:

  1. Slightly more complex layouts need to be nested (I think this simple layout is also like using VFL directly);

  2. Cost of learning.

Next I’ll post UIStackView also offers a similar functionality!!

4.6 Masonry

Auto Layout third-party open source framework, code address: navigation. Swift version is SnapKit.

No more blowing. In a word: Auto Layout preferred solution!!

Let’s take a look at the UIStackView functionality:

NSMutableArray<UIView *> *redviews = [NSMutableArray array]; for (int i = 0; i < 5; ++i) { UIView *theView = [UIView new]; theView.backgroundColor = [UIColor systemRedColor]; [theBackView addSubview:theView]; [redviews addObject:theView]; } /// Specify the leftmost interval, the rightmost interval, the interval between each view is 10, /// redviews.leading = 1 * superview.leading + 10 /// redviews.leading = 1 * superview.leading + 10 /// redviews.trailing = 1 * superview.trailing + 10 [redviews mas_distributeViewsAlongAxis:MASAxisTypeHorizontal withFixedSpacing:10 leadSpacing:10 tailSpacing:10]; ^(MASConstraintMaker *make) {/// redviews.top = 1 * superview.top + 80 make.top.equalTo(theBackView).offset(20); /// redviews.height = 1 * redView.width + 0 make.height.equalTo(@200); }];Copy the code

Effect:

So I’m going to update it to make it green. Requirements:

  1. The width and height of the three green blocks are 30 * index;
  2. The bottom coordinates of the three color blocks are the same;
  3. The five intervals are of the same width.

Of course, it can be done in a different way, so I’m responsible for the introduction

NSMutableArray *greenviews = [NSMutableArray array]; CGFloat greenNum = 3; CGFloat greenBaseWidth = 30; / / / (greenNum * (1 + greenNum) / 2) arithmetic progression first N only and CGFloat greenPadding = (theBackView. Bounds. The size, width - greenBaseWidth *  (greenNum * (1 + greenNum) / 2) ) / (greenNum + 1); for (int i = 0; i < greenNum; ++i) { UIView *theView = [UIView new]; theView.backgroundColor = [UIColor systemGreenColor]; [theBackView addSubview:theView]; [greenviews addObject:theView]; [theView mas_makeConstraints:^(MASConstraintMaker *make) { /// theView.leading = 1 * theBackView.mas_leading + ( EqualTo (thebackview.mas_leading). Offset (greenPadding * (I +1) + greenBaseWidth * (i * (1 + i) / 2)); /// theView.bottom = 1 * theBackView.bottom - 20 make.bottom.equalTo(theBackView.mas_bottom).offset(-80); /// theView.width = greenBaseWidth * (i + 1) /// theView.height  = greenBaseWidth * (i + 1) make.width.height.mas_equalTo(greenBaseWidth * (i + 1)); }]; }Copy the code

Let’s have a practical… Make the child view spread over the parent view:

Specify some of its attributes:

  1. Head:

    Leading = 1 * grayview.

    top = 1 * grayview.top + 10;

    width = 60;

    Height = 60.

  2. Nickname:

    Leading = 1 * leading + 20;

    Top = 1 * heads. Top

    Height = 1 * head height;

    Width: adaptive.

  3. Description:

    Leading = 1 * grayview.

    Top = 1 * head.

    trailing = 1 * grayview.trailing – 10

    bottom = 1 * grayview.bottom – 10

  4. grayview:

    Top = 1 * superview.top + 70;

    CenterX = 1 * superview.centerX;

    Width = 1 * superview.width -20;

    Height = 1 * superview. height-100;

You can see that from the diagram above and the constraints. You can directly determine that frame has only one head. Even GrayView can’t determine its frame.

UIView *grayview = [UIView new]; [self.view addSubview:grayview]; grayview.backgroundColor = [UIColor systemBlueColor]; CGFloat avatarWidth = 60; self.avatarImageView = [UIImageView new]; self.avatarImageView.image = [UIImage imageNamed:@"theFox.jpg"]; self.avatarImageView.clipsToBounds = YES; self.avatarImageView.layer.cornerRadius = avatarWidth / 2; self.nameLabel = [UILabel new]; self.nameLabel.text = @"Title"; self.descLabel = [UILabel new]; self.descLabel.text = @"Desc"; self.descLabel.numberOfLines = 0; for (UIView *theView in @[self.avatarImageView, self.nameLabel, self.descLabel]) { [grayview addSubview:theView]; } [self.avatarImageView mas_makeConstraints:^(MASConstraintMaker *make) { /// avatarImageView.leading = 1 * containerView.leading + 20 make.leading.equalTo(grayview).offset(20); /// avatarImageView.top = 1 * containerView.top + 10 make.top.equalTo(grayview).offset(10); /// avatarImageView.width = 1 * 0 + avatarWidth /// avatarImageView.height = 1  * 0 + avatarWidth make.width.height.mas_equalTo(avatarWidth); }]; [self.nameLabel mas_makeConstraints:^(MASConstraintMaker *make) { /// nameLabel.leading = 1 * avatarImageView.trailing +  20 make.leading.equalTo(self.avatarImageView.mas_trailing).offset(20); /// nameLabel.trailing = 1 * containerView.trailing - 20 make.trailing.equalTo(grayview).offset(-20); /// nameLabel.top = 1 * avatarImageView.top + 0  make.top.equalTo(self.avatarImageView); /// nameLabel.height = 1 * avatarImageView.height + 0 make.height.equalTo(self.avatarImageView); }]; [self.descLabel mas_makeConstraints:^(MASConstraintMaker *make) { /// descLabel.leading = 1 * containerView.leading + 10  make.leading.equalTo(grayview).offset(10); /// descLabel.trailing = 1 * containerView.trailing - 10 make.trailing.equalTo(grayview).offset(-10); /// descLabel.top = 1 * avatarImageView.bottom + 10 make.top.equalTo(self.avatarImageView.mas_bottom).offset(10); /// descLabel.bottom = 1 * containerView.bottom - 10 make.bottom.equalTo(grayview).offset(-10); }]; [grayview mas_makeConstraints:^(MASConstraintMaker *make) { /// containerView.centerX = 1 * self.view.centerX + 0 make.centerX.equalTo(self.view); /// containerView.top = 1 * self.view.top + 70 make.top.equalTo(self.view).offset(70); . / / / containerView width 1 * or less self. The width - 20; make the width. The lessThanOrEqualTo (self. The view). Offset (20); / / / ContainerView. Height is 1 or less * self. The height - 100 make. Height. LessThanOrEqualTo (self. The view). Offset (100);}];Copy the code

It now looks like this:

I’ll add a UIButton in the bottom left corner to randomly change the nickname and description.

UIButton *theButton = [[UIButton alloc] initWithFrame:CGRectMake(0, self.view.bounds.size.height - 60, 100, 50)];
[self.view addSubview:theButton];
[theButton setTitle:@"Just Do It" forState:UIControlStateNormal];
[theButton addTarget:self action:@selector(justDoIt) forControlEvents:UIControlEventTouchUpInside];


- (void)justDoIt {
    int numTitle = arc4random() % 10;
    int numDesc = arc4random() % 300;
    NSMutableString *theTitle = [NSMutableString stringWithString:@"Title"];
    NSMutableString *theDesc = [NSMutableString stringWithString:@"Desc"];
    
    for (int i = 0; i < numTitle; ++i) {
        [theTitle appendString:@"Title"];
    }
    
    for (int i = 0; i < numDesc; ++i) {
        [theDesc appendString:@"Desc"];
    }
    
    dispatch_async(dispatch_get_main_queue(), ^{
        self.nameLabel.text = theTitle;
        self.descLabel.text = theDesc;
    });
}
Copy the code

Second,UIViewCALayer

1.UIView

Most of the content of this section has been translatedUIView | Apple Developer Documentation

An object that manages the content for a rectangular area on the screen.

An object that manages the content of a rectangular area on the screen.

UIView is the basic building block of an application’s user interface. A view object renders content within its boundary region and handles interaction events related to the content.

View objects are the main way to interact with users in an application, and they bear many responsibilities:

  • Drawing and animation

    Views use UIKit or Core Graphics to draw content.

    Some properties can be animated when set to new values.

  • Layout and subview management

    A view can contain zero or more subviews.

    Views can adjust the size and position of their children.

    Use Auto Layout to define rules for changing the view tree to adjust the size and position of the view.

  • The event processing

    UIView inherits from UIResponder and can respond to touches and other types of events.

    Views can add UIGestureRecognizer to handle common gestures.

Views can be nested within other views to create view trees, which are a convenient way to organize related content. The nested view creates the relationship between the subview and superview.

By default, portions of a child view beyond the parent view are not clipped, and you can use the clipsToBounds property to change this behavior.

The geometry of each view is defined by the frame and bounds. Frame defines the position and size of a view in its superview coordinate system, and Bounds defines its size in its own coordinate system. The Center property provides a convenient way to reposition frame and bounds without modifying them.

The View Drawing Cycle

The UIView class uses draw on demand mode to render content. When the view is displayed for the first time, or when all or part of the view is visible due to layout changes, the view is asked to draw its content.

The system takes a snapshot of what the view displays and uses this snapshot as a visual display of the view. As long as the contents of the view are not changed, the view’s drawing code will not be called again. This snapshot can be used for most view-related operations (such as stretching, panning, etc.).

Once the contents of the View change, we don’t need to redraw those changes directly. We use the setNeedsDisplay or setNeedsDisplayInRect: methods to mark the view as dirty. These two methods tell the system that the content of the view has changed and needs to be redrawn in the next drawing cycle.

The system draws at the end of the current runloop. It is this delay mechanism that allows us to adjust multiple views (including but not limited to redraw, add/remove, hide, resize, reposition, etc.) all at once, and these specific adjustments are displayed in the same frame.

Changing the view’s collection shape does not automatically cause the view to be redrawn. The View’s contentMode determines how the view’s shape changes are interpreted. Most ContentModes do not create new snapshots, but instead stretch or rearrange existing snapshots within the view’s boundaries.

Try setting contentMode to UIViewContentModeRedraw. Subclass UIView and rewrite drawRect:

1.2 the animation

Certain attributes can be animated when changed. An animation created by changing the animation starts at the current value and ends at the specified value.

The following properties can be animated:

  • frame

  • bounds

  • center

  • Transform: to transform (such as translation, scaling, rotation, etc.)

  • Alpha: Transparency

  • backgroundColor

Animations: + (void)animateWithDuration:(NSTimeInterval) Duration animations:(void (^)(void)) Animations can be used to animate the above properties:

- (void)doAnimation:(NSNumber *)flag { int theFlag = [flag intValue]; switch (theFlag) { case 1: {[UIView animateWithDuration:0.25 animations:^{self. theview.frame = CGRectMake(50, 200, 200, 150);}]; break; } Case 2: {[UIView animateWithDuration:0.25 animations:^{self. theview. bounds = CGRectMake(0, 0, 300, 300);}]; break; } Case 3: {[UIView animateWithDuration:0.25 animations:^{self.theview.center = self.view.center;}]; break; } case 4: {[UIView animateWithDuration: 0.25 animations: ^ {self. TheView. Transform = CGAffineTransformMakeRotation (M_PI_4);}]. break; } case 5: {self.theview. alpha = 0.1; [UIView animateWithDuration:0.25 animations:^{self.theview.alpha = 1;}]; break; } case 6: {[UIView animateWithDuration: 0.25 animations: ^ {self. TheView. BackgroundColor = [UIColor blackColor];}]. break; } default: { break; } } if (theFlag < 6) { [self performSelector:@selector(doAnimation:) withObject:@(theFlag + 1) afterDelay:1]; }}Copy the code

Of course, Apple has a UIViewPorpertyAnimator class in iOS 10 that makes it a lot easier for us to do interactive animations. I won’t do the extension here, if you are interested please Google.

1.3 withAuto LayoutA linkage

  • Update the constraints

If there is need to update the constraints, but don’t try so hard, the call [the self setNeedsUpdateConstraints] to mark up for the next layout update cycle.

At once if there is need to update the constraints of the calls [self updateConstraintsIfNeeded].

  • Adjust the layout

Call [self setNeedsLayout] to mark the layout adjustment for the next Layout cycle if there is a layout that needs adjusting, but it is not in a hurry.

If there is a layout that needs to be adjusted immediately, call [self layoutIfNeeded].

  • Rendering and display

If the entire layer needs to be redrawn, call [self setNeedsDisplay] to draw the entire content in the next drawing cycle;

If you just need to redraw part, call [the self setNeedsDisplayInRect: the rect] to the next time the drawing from cycle drawing content within the specified rectangle.

2,CALayer

Most of the content of this section has been translatedCALayer | Apple Developer Documentation

An object that manages image-based content and allows you to perform animations on that content.

An object that manages graphic-based content and allows animation to be performed on its content.

CALayer is usually used to provide backup storage for UIView, but CALayer can display content without UIView. We could call CALayer a layer.

The main job of the layer is to manage the visual content we provide, but the layer itself also contains visual properties that can be set, such as background colors, borders, shadows, and so on.

In addition to managing visual content, Layer also maintains the geometry of its content displayed on the screen (such as position, size, transformation). Changing the Layer properties is the way to animate the layer content or geometry. Layer manages the duration and pace of its animations by implementing the protocol CAMediaTiming.

If a Layer object is created by a view, the view automatically becomes a proxy for the layer. Do not change this relationship. If the layer is created by ourselves, we can provide a proxy for the layer that dynamically provides content and performs tasks for the layer.

Layer internal maintenance of three layer tree, respectively: presentationLayer Tree (animation tree), modelLayer tree (model tree), Render Tree (render tree). When we animate, the animation property we modify is presentationLayer Tree. ModelLayer actually provides the content that is displayed in the interface. This section explains the difference and connection between CALayer and UIView

2.1 layer.contents

We all know that if we want to set a background image for a view, we can either add a subview of type UIImageView to the view or a sublayer to view.layer:

CALayer *theLayer = [CALayer layer];
[self.myView.layer addSublayer:theLayer];
theLayer.frame = self.myView.bounds;
theLayer.contents = (__bridge id)[UIImage imageNamed:@"theFox.jpg"].CGImage;
Copy the code

You can even do it in one line of code:

self.myView.layer.contents = (__bridge id)[UIImage imageNamed:@"theFox.jpg"].CGImage;
Copy the code

But it will stretch, and the stretch option comes from the property contentsGravity.

Contents is a property of type ID. Assigning a CGImage to this property displays the given image on layer.

In fact, layer display also depends on contents. But why is this attribute of type ID? In fact, this property has been around since the early Days of macOS, when CGImage and NSImage were displayed, but on iOS, NSImage was not available, so only CGImage was left. But the type of this attribute remains, I think, for the sake of consistency of library principles.

Properties associated with contents

ContentsGravity: Specifies the alignment of contents

ContentsScale: Specifies the scale of the contentes

ContentsRect: Specifies the display area of contents

ContentsCenter: Specifies the stretch area of contents

  • contentsGravity

This is an NSString star!! Specifies how layer contents map to its rectangular region, i.e. alignment. Corresponding UIView. ContentMode.

kCAGravityCenter
kCAGravityTop
kCAGravityBottom
kCAGravityLeft
kCAGravityRight
kCAGravityTopLeft
kCAGravityTopRight
kCAGravityBottomLeft
kCAGravityBottomRight
kCAGravityResize
kCAGravityResizeAspect
kCAGravityResizeAspectFill
Copy the code

The default value is kCAGravityResize.

Uiview.contentmode is a sibling of uiView.contentMode.

Keep in mind, however, that Resize will be resized, regardless of the screen resolution.

  • contentsScale

This property specifies the scale of contents. It determines the ratio of the layer’s actual drawing to the physical screen display. Corresponding UIView. ContentScaleFactor.

If the physical size is (w, h), then the logical size is (W/contentsScale, h/contentsScale). Its value affects not only the contents provided by CGImage, but also those drawn by drawInContext: (if the value is 2, drawInContext: draws twice the layer.bounds when drawn).

The default value is 1.0. However, if the layer is directly under the UIView (created when the view is created), this value is automatically set to [UIScreen mainScreen].scale; But for our self-made Layer, the value is 1.0.

Remember to set contentsGravity without Resize when testing NOTE.

  • contentsRect

This value allows us to set the layer to display the contents area.

The default value is {0, 0, 1, 1} for the entire contents. Instead of a one-point count of frame/bounds, this property uses unit coordinates, with each small argument taking the value [0, 1].

This property is great for using spliced diagrams. In some cases, you will need to use a large number of small ICONS, such as the function list (please click on the operation: wechat -> me, the small icon of the function list). There are many benefits to doing this: memory usage, load time, rendering performance, etc.

Make up ~ ~ ~

int hNum = 2;
int vNum = 3;

int totalCount = hNum * vNum;

CGFloat margin = 10;

CGFloat vStart = 80;
CGFloat totalHeight = self.view.bounds.size.height - vStart - 110;

CGFloat viewWidth = (self.view.bounds.size.width - margin * (hNum + 1)) / hNum;
CGFloat viewHeight = (totalHeight - margin * (vNum + 1)) / vNum;


for(int row = 0; row < vNum; ++row) {///for(int col = 0; col < hNum; UIView *theView = [[UIView alloc] initWithFrame: CGRectMake(margin * (col + 1) + viewWidth * col, vStart + margin * (row + 1) + viewHeight * row, viewWidth, viewHeight)]; [self.view addSubview:theView]; theView.layer.contents = (__bridge id)theImage; TheView. Layer. ContentsRect = CGRectMake ((hNum * row + col) * 1.0 / totalCount, 0, 1.0 / totalCount, 1); } } UIView *wholeImage = [[UIView alloc] initWithFrame:CGRectMake(0, self.view.bounds.size.height - 105, self.view.bounds.size.width, 100)]; [self.view addSubview:wholeImage]; wholeImage.layer.contents = (__bridge id)theImage;Copy the code

  • contentsCenter

This is a CGRect, CGRect, CGRect. Used to identify the area being stretched. Is similar with the UIImage. ResizableImageWithCapInsets:.

Here are the pictures circulating on the Internet:

Then explain:

Once we set the CGRect contentsCenter, we extend the boundaries of the RECt, and the contents are split into nine pieces.

The first is the area inside this RECT, which is the green area;

And then the two areas above and below recT, the blue area;

And then there are the two areas left and right of rect, which are the red areas;

And finally, the four areas in the four corners of recT, the yellow areas.

  1. The blue region stretches only horizontally;

    The vertical ends do not stretch in the vertical direction

    The blue area is perpendicular to recT, which does not stretch

  2. The red region stretches only vertically;

    The horizontal ends do not stretch horizontally

    The red area is at the recT level, which does not stretch horizontally

  3. The green zone stretches horizontally and vertically at the same time;

    Inside the RECT, it stretches in both directions.

  4. The yellow region does not stretch;

    At the four corners, no stretching.

To be honest, after reading so many online explanations, I thought…

After thinking for a long time, I found a vortex diagram that I think is suitable for demonstrating this property.

NOTE: x * 2 + width = 1

2.2 Dedicated Layer class

class use
CAEmitterLayer Realize particle emission system based on Core Animation.CAEmitterLayerControls the creation and location of particles.
CAGradientLayer Draws the gradient that fills the layer.
CAMetalLayer Create and use drawable textures to render layer content using Metal.
CAEAGLLayer / CAOpenGLLayer Set up backup storage and graphics context, and use OpenGL ES to render layer content (macOS, OpenGL)
CAReplicatorLayer Used when one or more copies of sublalyer need to be made. The replicator makes copies and uses the properties we specify to change the appearance and properties of the copy.
CAScrollLayer Manage large sliding areas consisting of multiple sublayers
CAShapeLayer Draw a Bessel curve in three dimensions. CAShapeLayer is excellent at drawing path-based shapes because it always outputs clear paths, as opposed to drawing to layer backup storage, which doesn’t work well when scaled. However, clean results need to be drawn and cached on the main thread.
CATextLayer Renders plain text or attribute strings
CATiledLayer Manages large images that can be broken up into smaller pieces, allowing for zooming in and out of content to render separately
CATransformLayer Render real 3D layer structure

2.3 withAuto LayoutA linkage

If the entire layer needs to be redrawn, call [self setNeedsDisplay] to draw the entire content in the next drawing cycle;

If you just need to redraw part, call [the self setNeedsDisplayInRect: the rect] to the next time the drawing from cycle drawing content within the specified rectangle.

3,UIViewCALayerThe link

UIView is responsible for responding to events and managing the CALayer’s life cycle. CALayer is responsible for drawing the content.

UIView is a Layer’s CALayerDelegate.

Think about PhotoShop. We refer to UIView as PSD, and CALayer as layer. A view is a stack of layers.

  1. View is the manager of layer and provides space for layer to play.

  2. A view has at least one layer;

  3. The view can be drawn on the screen thanks to Layer. The layer has a graphics context, and the view actually draws that context to the screen, and the layer will cache that drawing, and the view can access the context through the layer. When the view needs to be drawn to the screen, the Layer drawing method is called. Of course, this process can also use drawRect: to draw custom content. The content drawn in this method is stored in the context.

  4. The view hierarchy determines the layer hierarchy. The view hierarchy can change the layer hierarchy, not vice versa. Add a subview to a view and its layer will add a sublayer for the response; But adding sublayer directly to layer does not affect the view subviews);

Copy the next paragraph… To see the article, go to View-Layer Synergy

All views have a layer inside them, and the view gets most of its data directly from this layer. Therefore, changes to the layer will also be reflected in the View. This means that you can modify the interface using either Core Animation or UIKit.

However, there are some single layer, such as AVCaptureVideoPreviewLayer and CAShapeLayer, they do not need to be attached to the view can be displayed on the screen. In either case, it’s Layer that makes the difference. However, the layer attached to the view behaves slightly differently than the individual layer.

If we modify any property of the standalone layer (almost any property), we will see an animation that transitions from the old value to the new value. However, if we modify the same property of layer in the view, it will move directly from one frame to the next. Although the main character is the layer in both cases, once the layer is attached to the view, its default implicit animation behavior disappears.

Almost all attributes of layer are implicitly animatable. You can see in the documentation that their bio ends with Animatable.

This applies to most number attributes, such as position, size, color, opacity, and even isHidden and doubleSided.

The paths property is also animatable, but does not support implicit animation.

In the section “How to Animate Layer-Backed Views” of “Animating Layer Content” in Core Animation Programming Guide, Explains why layer in view does not have the ability to animate implicitly.

The UIView class disables layer animations by default but reenables them inside animation blocks.

By default, the UIView class disables layer animation, but it is re-enabled in the Animation block.

This is exactly the behavior we see: modifying properties outside the animation block without animation; However, the animation appears when properties are modified in the animation block.

Whenever an animatable property changes, Layer always looks for the appropriate action to implement the change. In Core Animation’s jargon, such animations are called actions (caactions).

Technically, CAAction is a yes, it can be used to do a lot of things. But in practice, we usually use it for animation.

Layer finds actions in the way described in the document, which consists of five steps. The first step is the most interesting when we look at the view’s interaction with the layer.

Layer to its proxy (if it is a layer attached to a view, the proxy is the view; A separate layer, whose proxy is made up of our own) sends actionForLayer:forKey: to ask for an action with a property change. Agents can respond in three ways:

  1. Returns an action object, in which case Layer uses the action;
  2. Returns nil, at which point layer will go somewhere else to look;
  3. Return an NSNull object, layer will not execute the action, and the search is done.

The interesting point is that for a layer attached to a view, the view is a proxy for that layer.

In iOS, if a layer is attached to a UIView object, the layer delegate property must be set to that UIView object.

The previously obscure behavior is now clear: Anytime the Layer asks for an action, the View always returns an NSNull object, unless properties are modified in the Animation block. But don’t believe me. We can test it. Simply ask the view with a Layer property that can be animated, such as Position:

NSLog (@ "in animtion block outside: % @", [the self. The view actionForLayer: self. The layer forKey: @ "position"]); [UIView animateWithDuration:0.25 animations:^{NSLog(@" in the animtion block: %@", [self.view actionForLayer:self.view.layer forKey:@"position"]); }];Copy the code

From the results, in the animation block outside returns a < null >, this is the NSNull objects, while in animtion block is a _UIViewAdditiveAnimationAction object in return.

The log that prints nil is (null) and NSNull is < NULL >.

For the layer attached to the view, the search for the action only goes to the first step. For individual layers, see actionForKey: -Apple Developer Documentation for more of the four steps.

4,UIViewCALayerThe difference between

  • UIView is responsible for responding to events, participating in the response chain, and providing content to the layer.

  • CALayer does the drawing, the animation.

3. UI drawing

The UI interface is an integral part of development, more so than anything else for the user. IOS provides a very rich and high-performance UI tool library. Using UIKit and Core Animation directly can satisfy most ergonomics.

However, we still encounter some problems with display, with Caton at the top of the list. If you want to solve a problem, you have to understand the nature of the problem. Once you understand how the UI gets to the screen, what steps it takes, and assemble some tools, you can solve the problem at its root.

1, screen imaging principle

1.1 VSync, HSync

This is the schematic of an old CRT monitor. CRT electron gun scans from top to bottom line by line as shown in the figure above. After scanning, the display displays a frame, and then the electron gun returns to the initial position to continue the next scan.

To synchronize the display with the system’s video controller, the display (or other hardware) generates a series of timing signals using a hardware clock.

When the electron gun switches to a new row and is ready to scan, the display emits a horizontal synchronization, or HSync; When one frame is drawn and the gun is returned to its original position before the next frame is drawn, the display sends a vertical synchronization signal, or VSync.

Typically, the display refreshes at a fixed rate, which is the frequency at which the VSync signal is generated.

1.2 Image display principle

The display of a frame on the screen is done by the CPU, GPU and monitor working together in the way shown above. The CPU calculates the display content and submits it to THE GPU. After the GPU rendering is completed, the rendering result is put into the frame cache area. Then the video controller will read the data of frame buffer line by line according to the VSync signal and transfer it to the display through digital-analog conversion.

In the simplest case, there is only one frame buffer. In this case, reading and flushing of the frame buffer will be relatively efficient. To solve the efficiency problem, the display system introduces two frame buffers, also known as double buffering. In this case, the GPU will pre-render one frame into a buffer for the video controller to read, and after the next frame is rendered, the GPU will point the video controller’s pointer directly to the second buffer.

Double-buffering does solve efficiency problems, but it also introduces new ones. When the video controller has not finished reading the current buffer, that is, when part of the screen content is just displayed, THE GPU submits a new frame to the frame buffer and exchanges the two buffer Pointers, the video controller will read the unread part from the new frame data, causing the upper and lower tearing of the picture. Something like this:

The video controller reads the frame buffer line by line.

The top half of the data is from the frame buffer before the swap, and the bottom half of the data is from the frame buffer after the swap.

To solve this problem, gpus usually have a mechanism called VSync. After VSync is enabled, the GPU will wait for the VSync signal from the display frame to be sent before performing the buffer update of the current frame and rendering of the next frame. This will solve the image tearing problem and increase the flow of the image. But this requires more computing resources and some latency.

The GPU waits for the VSync signal to wait for the video controller to finish reading the last frame and then swap the Pointers of the two buffers and render the next frame. Not to render the current frame, the current frame is already in another buffer. The GPU will render the next frame in the buffer where the video controller has just read the data. If you render the current frame, you lose the meaning of the double buffer.

It may be a bit convoluted: wait for the first frame to display the completed VSync signal, point the video controller’s pointer to the already rendered buffer of the second frame, then start rendering the third frame (in the buffer of the first frame).

Currently, iOS devices have dual buffering and triple buffering.

To sum up, the screen imaging process is as follows:

  1. The CPU calculates the content to be displayed and submits it to the GPU.

  2. GPU performs texture synthesis and submits the rendering result to the frame buffer;

  3. The total frame buffer of the video controller reads the input line by line and transfers the data to the display.

  4. Display imaging.

1.3 Core Animation Pipeline

In iOS, view rendering is actually done by a separate Render Server process. It was delivered to SpringBoard in iOS 5 and before, and then called BackBoard.

All of our views and animations are implemented by Core Animation’s CALayer. Here is Core Animation’s pipeline diagram:

Core Animation realizes rendering through Core Animation Pipeline, which performs rendering work in the form of pipelining:

  • Commit Transaction

    • Layout: Build UI, Layout, text calculation, etc.

    • Display: View drawing, basically drawRect;

    • Prepare: Additional steps, generally do picture decoding;

    • Commit: Package the layer recursively and submit it to Render Server.

      This step occurs in the drawing Cycle before Runloop in BeforeWaiting and Exit.

  • Render Server

    • Deserialize the data to get the number of layers;

    • Filter out the blocked part of the layer according to the layer order of layer tree, RGBA value, layer frame, etc.

    • Convert layer tree to render tree

    • Submit the render tree to OpenGL/Metal;

    • OpenGL/Metal generates a drawing Command, waits for VSync signal to arrive, and then submits it to the Command Buffer for GPU to read and execute.

  • GPU

    Wait for the VSync signal to arrive, then read the instruction from the command buffer and execute it.

    • Vertext Shader: vertex Shader

    • Shape Assembly: Shape Assembly

    • Geometry Shader

    • Rasterization: Rasterization

    • Fragment Shader: Fragment Shader

    • Tests and Blending: Tests and Blending

  • Display

When the VSync signal arrives, the video controller reads the data line by line from the frame buffer to control what is displayed on the screen

1.4 Reasons for stalling and frame dropping

As we all know, iOS devices have a screen refresh rate of 60hz (a high refresh rate of 120hz can be turned on after iOS 14…). . That’s 60 frames per second, which equates to 16.67ms of render time per frame.

VSync signals are generated every 16.67ms. After the arrival of VSync signal, the graphics service of the system will notify the App through CADisplayLink and other mechanisms, and the main thread of the App will start to calculate the display content in the CPU, including view creation, layout calculation, picture decoding, text drawing, etc. The CPU then delivers the calculated content to the GPU, which transforms, composes, renders, and then submits the results to the frame buffer. Wait for the next VSync signal to appear on the screen.

Due to the VSync mechanism, if the CPU or GPU does not complete its work during a VSync period, the rendering result will be submitted to the frame buffer later than the next VSync signal arrives, and the frame will be discarded until the next opportunity to display. The display remains unchanged. In other words, the second frame that should have existed between the first and the third frame was not realized, causing the drop frame. That’s why the interface gets stuck.

To sum up: ** THE CPU and GPU have not completed the rendering of the next frame before the arrival of the next VSync signal, which will cause frame drop lag.

Columns limited to 20,000 words, continued:Ali, Bytes: A set of efficient iOS interview questions (9 – View & Image related – part 2)