CoreGraphics analysis of iOS drawing framework

Because the CoreGraphics framework has too many APIs, people who are new to the framework or who do not know much about the framework will feel a little confused about the choice of APIs when drawing, and even feel that the graphics drawing of iOS is a bit cumbersome. Therefore, this article mainly introduces the drawing method of iOS and analyzes the drawing principle of the CoreGraphics framework.

An introduction to the drawing system

There are many kinds of drawing frameworks for iOS. The most commonly used one is UIKit, whose bottom layer is implemented by CoreGraphics, and most of the graphical interfaces are also completed by UIKit, and UIImage, NSString, UIBezierPath, UIColor, etc. all know how to Drawing yourself also provides some methods to meet our commonly used drawing needs. In addition to UIKit, there are CoreGraphics, Core Animation, Core Image, OpenGL ES and other frameworks to meet different drawing requirements. An overview of each framework is as follows:

  • UIKit: The most commonly used view framework, with the highest degree of encapsulation, all are OC objects

  • CoreGraphics: The main drawing system, often used to draw custom views, pure C API, using Quartz2D as the engine

  • CoreAnimation: Provides powerful 2D and 3D animation effects

  • CoreImage: Provides various filter processing for images, such as Gaussian blur, sharpening, etc.

  • OpenGL-ES: Mainly used for game drawing, but it is a set of programming specifications implemented by device manufacturers

drawing system

1843940-6bffaf8661105b1f.png

2. Drawing method

The actual drawing consists of two parts: view drawing and view layout . The functions they implement are different. Before understanding these two concepts, you need to understand what the drawing cycle is, because they are all drawn in the drawing cycle.

Drawing cycle:

  • iOS consolidates all drawing requests in the run loop and draws them all at once

  • You can't draw in the child thread, and you can't perform complex operations, otherwise the main thread will be stuck

1. View drawing

Call UIView's drawRect: method to draw. If you call a view's setNeedsDisplay method, the view is marked for repainting and will be repainted on the next draw cycle, automatically calling the drawRect: method.

2. View layout

Call UIView's layoutSubviews method. If you call the setNeedsLayout method of a view, then the view is marked as needing to be re-layout, UIKit will automatically call the layoutSubviews method and the layoutSubviews method of its subviews.

When drawing, we should use layout as much as possible and draw less because layout uses GPU and drawing uses CPU. GPU has advantages for graphics processing, while CPU has to deal with many things and is not good at processing graphics, so try to use GPU to process graphics.

3. Drawing state switching

There are various corresponding state switches for iOS drawing, such as: pop/push, save/restore, context/imageContext and CGPathRef/UIBezierPath, etc., which will be introduced separately below:

1.pop / push

Set the drawing context (context)

push : UIGraphicsPushContext(context) pushes the context onto the stack and sets the context to the current drawing context

pop : UIGraphicsPopContext pops the context at the top of the stack and restores the previous context, but the drawing state remains unchanged

The view drawn below is black

- (void)drawRect:(CGRect)rect {
    [[UIColor redColor] setFill];
    UIGraphicsPushContext(UIGraphicsGetCurrentContext());
    [[UIColor blackColor] setFill];
    UIGraphicsPopContext();
    UIRectFill(CGRectMake(90, 340, 100, 100)); // black color
}

2.save / restore

Set the state of the drawing (state)

save : CGContextSaveGState pushes the current drawing state on the stack, just the drawing state, not the drawing context

restore : restore the drawing state just saved

The view drawn below is red

- (void)drawRect:(CGRect)rect {
    [[UIColor redColor] setFill];
    CGContextSaveGState(UIGraphicsGetCurrentContext());
    [[UIColor blackColor] setFill];
    CGContextRestoreGState(UIGraphicsGetCurrentContext());
    UIRectFill(CGRectMake(90, 200, 100, 100)); // red color
}

3.context / imageContext

iOS drawing must be drawn in a context, so get a context before drawing. If you are drawing a picture, you need to get a picture context; if you are drawing other views, you need a non-picture context. For contextual understanding, it can be considered as a canvas, and then draw operations on it.

context : graphics context, you can get the context of the current view through UIGraphicsGetCurrentContext:

imageContext : The image context, you can get an image context through UIGraphicsBeginImageContextWithOptions:, then after the drawing is complete, call UIGraphicsGetImageFromCurrentImageContext to get the drawn image, and finally remember to close the image context UIGraphicsEndImageContext.

4.CGPathRef / UIBezierPath

The drawing of graphics needs to draw a path, and then render the path, and CGPathRef is the path drawing class in the CoreGraphics framework. UIBezierPath is an OC-oriented class that encapsulates CGPathRef, which is more convenient to use, but some advanced features are still inferior to CGPathRef.

Fourth, the specific drawing method

Since there are two commonly used drawing frameworks for iOS, UIKit and CoreGraphics, there are many drawing methods. Here are some common drawing methods for iOS.

1. Context of image type

The drawing of the picture context does not need to be done in the drawRect: method, it can be drawn in a common OC method

Implemented using UIKit

// Get the image context 
UIGraphicsBeginImageContextWithOptions(CGSizeMake( 100 , 100 ), NO, 0 );
 // Draw 
UIBezierPath* p = [UIBezierPath bezierPathWithOvalInRect:CGRectMake( 0 , 0 , 100 , 100 )];
[[UIColor blueColor] setFill];
[p fill];
// Get the drawn image from the image context 
UIImage* im = UIGraphicsGetImageFromCurrentImageContext();
 // Close the image context 
UIGraphicsEndImageContext();

Implemented using CoreGraphics

// Get the image context 
UIGraphicsBeginImageContextWithOptions(CGSizeMake( 100 , 100 ), NO, 0 );
 // Drawing 
CGContextRef con = UIGraphicsGetCurrentContext();
CGContextAddEllipseInRect(con, CGRectMake( 0 , 0 , 100 , 100 ));
CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor);
CGContextFillPath(with);
// Get the drawn image from the image context 
UIImage* im = UIGraphicsGetImageFromCurrentImageContext();
 // Close the image context 
UIGraphicsEndImageContext();

2.drawRect:

Implement graphics redrawing in the drawRect: method of the UIView subclass. The drawing steps are as follows:

  • get context

  • draw graphics

  • render graphics

UIKit methods

- (void) drawRect: (CGRect) rect {
    UIBezierPath* p =         [UIBezierPathbezierPathWithOvalInRect:CGRectMake(0,0,100,100)];
    [[UIColor blueColor] setFill];
    [p fill];
}    

CoreGraphics

- (void) drawRect: (CGRect) rect {
    CGContextRef con = UIGraphicsGetCurrentContext();
    CGContextAddEllipseInRect(con, CGRectMake( 0 , 0 , 100 , 100 ));
    CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor);
    CGContextFillPath(with);
}

3.drawLayer:inContext:

The drawing task can also be implemented in the drawLayer:inContext: method of the UIView subclass. It is a proxy method of a layer. In order to be able to call this method, a proxy object needs to be set for the delegate of the layer, and the proxy object cannot be a UIView object. , because the UIView object is already the proxy object of its internal root layer (implicit layer), and then setting it as the proxy object of another layer will cause problems.

When a view is added to another view, the layer changes as follows:

  • First implicitly set the CALayerDelegate of this view's layer to this view

  • Call the drawInContext method of this view's self.layer

  • Due to the annotation of the drawLayer method: If defined, called by the default implementation of -drawInContext: shows that if([self.delegate responseToSelector:@selector(drawLayer:inContext:)]) in drawInContext executes the drawLayer:inContext: method, here we Because drawLayer:inContext: is implemented, it will be executed

  • [super drawLayer:layer inContext:ctx] will make the system automatically call the drawRect: method of this view, so far self.layer is drawn

  • Add a child layer to self.layer, when calling [layer setNeedsDisplay]; the drawInContext method of this layer will be called automatically

  • If drawRect is not rewritten, the drawInContext method of its layer will not be called, and the drawLayer:inContext method will not be called.

Call drawLayer:inContext: of the inner root layer:

// If drawRect is not rewritten, the drawInContext method of its layer will not be called, and the drawLayer:inContext method will not be called 
-( void )drawRect:(CGRect)rect{
    NSLog(@"2-drawRect:");
    NSLog( @" CGContext in drawRect:%@ " ,UIGraphicsGetCurrentContext());
     // The current graphics context obtained is exactly the     [super drawRect:rect] passed in drawLayer ;

}

#pragma mark - CALayerDelegate
-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{
    NSLog(@"1-drawLayer:inContext:");
    NSLog( @" CGContext in drawLayer:%@ " ,ctx);
     // If this sentence is removed, drawRect will not be executed!!!!!!!! 
    [super drawLayer:layer inContext:ctx];
}

 

Call the drawLayer:inContext of the external proxy object:

Since the UIView object cannot be set as the proxy of the CALayerDelegate, we need to create an NSObject object, and then implement the drawLayer:inContext: method, so that the required graphics can be drawn in the proxy object. In addition, when setting the proxy, you do not need to comply with the proxy protocol of CALayerDelegate, that is, this method is NSObject, and there is no need to explicitly specify the protocol.

// MyLayerDelegate.m
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
{
    CGContextAddEllipseInRect(ctx, CGRectMake(100,100,100,100));
    CGContextSetFillColorWithColor(ctx, [UIColor blueColor].CGColor);
    CGContextFillPath(ctx);
}
// ViewController.m 
@interface ViewController () @property (nonatomic, strong) id myLayerDelegate;
 @end 
@implementation ViewController
 - ( void )viewDidLoad {
 // Set layer's delegate to NSObject subclass object 
    _myLayerDelegate = [[MyLayerDelegate alloc] init ];
    MyView *myView = [[MyView alloc] initWithFrame:self.view.bounds];
    [self.view addSubview:myView];
    CALayer *layer = [CALayer layer];
    layer.backgroundColor = [UIColor magentaColor].CGColor;
    layer.bounds = CGRectMake(0, 0, 300, 500);
    layer.anchorPoint = CGPointZero;
    layer.delegate = _myLayerDelegate;
    [layer setNeedsDisplay];
    [myView.layer addSublayer:layer];
}

Detailed implementation process

When UIView needs to be displayed, its internal layer will prepare a CGContextRef (graphics context), then call the drawLayer:inContext: method of the delegate (here is UIView), and pass in the prepared CGContextRef object. And UIView will call its own drawRect: method in the drawLayer:inContext: method. Usually, the CGContextRef object passed in by the layer is obtained through UIGraphicsGetCurrentContext() in drawRect:. All drawings completed in drawRect: will be filled in the CGContextRef of the layer, and then copied to the screen.

The analysis of the iOS drawing framework is as above. If there are any shortcomings, please point out and make progress together. (The pictures in this article are from the Internet, and the copyright belongs to the original author)

 

Source: http://www.cocoachina.com/ios/20170809/20187.html

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324533098&siteId=291194637