The test environment

Xcode 11.5

iPhone 11 Pro Simulator

IOS 13.5

1. How to set rounded corners to trigger off-screen rendering

We often see rounded corners trigger off-screen rendering. But that’s not accurate, because rounded corners trigger off-screen renderings conditionally!

Let’s start with what the Official Apple documentation says about a cornerRadius:

Setting the radius to a value greater than 0.0 causes the layer to begin drawing rounded corners on its background. By default, the corner radius does not apply to the image in the layer’s contents property; it applies only to the background color and border of the layer. However, setting the masksToBounds property to true causes the content to be clipped to the rounded corners.

We found that setting the cornerRadius greater than 0 only sets the corner for the layer’s backgroundColor and border; You don’t fillet the layer contents unless you also set layer.masksToBounds to true (corresponding to the UIView’s clipsToBounds property).

If you’re thinking that layer.masksToBounds or clipsToBounds set to true will trigger off-screen rendering, that’s not entirely true.

Let’s start by opening the emulator’s off-screen render color markers:

  • Don’t setlayer.masksToBoundsorclipsToBounds, the default value isNO
- (void)viewDidLoad {
    [super viewDidLoad];
    UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(0.0.200.0.200.0)];
    // Set the background color
    view1.backgroundColor = UIColor.redColor;
    // Set the border width and color
    view1.layer.borderWidth = 2.0;
    view1.layer.borderColor = UIColor.blackColor.CGColor;
    // Set the rounded corners
    view1.layer.cornerRadius = 100.0;
    
    view1.center = self.view.center;
    [self.view addSubview:view1];
}
Copy the code

When we see only the background color, borders, and rounded corners, it really doesn’t trigger off-screen rendering.

  • Set up thelayer.masksToBoundsorclipsToBoundsforYES
- (void)viewDidLoad {
    [super viewDidLoad];
    UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(0.0.200.0.200.0)];
    // Set the background color
    view1.backgroundColor = UIColor.redColor;
    // Set the border width and color
    view1.layer.borderWidth = 2.0;
    view1.layer.borderColor = UIColor.blackColor.CGColor;
    // Set the rounded corners
    view1.layer.cornerRadius = 100.0;
  
    // Set crop
    view1.clipsToBounds = YES;
    
    view1.center = self.view.center;
    [self.view addSubview:view1];
}
Copy the code

When we turn on layer.masksToBounds or clipsToBounds, we also don’t trigger off-screen rendering. That’s because we haven’t set the picture yet.

  • Set up thelayer.masksToBoundsorclipsToBoundsforYES, and set the picture
- (void)viewDidLoad {
    [super viewDidLoad];
    UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(0.0.200.0.200.0)];
    // Set the background color
    view1.backgroundColor = UIColor.redColor;
    // Set the border width and color
    view1.layer.borderWidth = 2.0;
    view1.layer.borderColor = UIColor.blackColor.CGColor;
    
    // Set the image
    view1.layer.contents = (__bridge id) [UIImage imageNamed:@"pkq"].CGImage;
    
    // Set the rounded corners
    view1.layer.cornerRadius = 100.0;
    // Set crop
    view1.clipsToBounds = YES;
    view1.center = self.view.center;
    [self.view addSubview:view1];
}
Copy the code

When we turn on layer.masksToBounds or clipsToBounds and set the image at the same time, off-screen rendering is triggered.

  • It’s not just the image. We can also trigger off-screen rendering when we add a subview to the view that has image information such as color, content, or border.

    The image information also includes drawing in the view or the layer’s draw method.

- (void)viewDidLoad {
    [super viewDidLoad];
    UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(0.0.200.0.200.0)];
    // Set the background color
    view1.backgroundColor = UIColor.redColor;
    // Set the border width and color
    view1.layer.borderWidth = 2.0;
    view1.layer.borderColor = UIColor.blackColor.CGColor;
    // Set the rounded corners
    view1.layer.cornerRadius = 100.0;
    // Set crop
    view1.clipsToBounds = YES;
    
    / / the child views
    UIView *view2 = [[UIView alloc] initWithFrame:CGRectMake(0.0.100.0.100.0)];
    // Any of the following attributes
    // Set the background color
    view2.backgroundColor = UIColor.blueColor;
    // Set the content
    view2.layer.contents = (__bridge id) ([UIImage imageNamed:@"pkq"].CGImage);
    // Set the border
    view2.layer.borderWidth = 2.0; 
    view2.layer.borderColor = UIColor.blackColor.CGColor;
    [view1 addSubview:view2];
    
    view1.center = self.view.center;
    [self.view addSubview:view1];
}
Copy the code

2. The real reason rounded corners trigger off-screen rendering

Layers are drawn on top of each other in a “painter’s algorithm”.

Oil painting algorithm: first draw the objects farther away from the observer in the scene, then draw the objects closer.

The problem of eliminating hidden faces can be solved by drawing the red part first, then the yellow part, and finally the gray part. That is, the scene is sorted according to the physical distance and the distance of the observer, and it can be drawn from the far and the near.

When we set the cornerRadius and masksToBounds for rounded corners + cropping, the masksToBounds cropping property is applied to all layers.

Originally, we draw from the back to the front, and once we’ve drawn one layer, we can discard it. But now you need to save it in the Offscreen Buffer in turn, waiting for rounded corners + clipping, which triggers off-screen rendering.

  • Background color, border, background color + border, rounded corners + clipping, according to the document, since contents = nil there is NO content to clipping, masksToBounds is set to YES or NO and it doesn’t matter.

  • Once we set content for contents, whether it’s an image, draw content, subview with image information, etc., plus rounded corners + cropping, it triggers off-screen rendering.

    You don’t have to directly assign contents!

3. Optimizations for iOS9 and beyond

For rounded corners, Apple has made some improvements in iOS 9 and later.

  • layer.contents/imageView.image

    We’re just going to set contents or UIImageView’s image, and we’re going to add rounded corners and crop, and it’s not going to render off screen. But if you add a background color, border, or other layer with image content, you still get an off-screen rendering.

    - (void)viewDidLoad {
        [super viewDidLoad];
        UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(0.0.200.0.200.0)];
        // Set the image
        view1.layer.contents = (__bridge id) [UIImage imageNamed:@"qiyu"].CGImage;
        // Set the rounded corners
        view1.layer.cornerRadius = 100.0;
        // Set crop
        view1.clipsToBounds = YES;
        
        view1.center = self.view.center;
        [self.view addSubview:view1];
    }
    Copy the code

This is understandable, since only single-layer content needs to be rounded and cropped, so off-screen rendering is not necessary. However, if you add a background color, border, or other layer with image content, it will result in adding rounded corners and cropped layers, so it will still trigger off-screen rendering (like the third example in 1).

So, we need to be careful when using a view like UIButton:

  • UIButton

    - (void)viewDidLoad {
        [super viewDidLoad];
        self.view.backgroundColor = [UIColor whiteColor];
        // Create a button view
        UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(0.0.200.0.200.0)];
        button.backgroundColor = UIColor.whiteColor;
        button.center = self.view.center;
        [self.view addSubview:button];
        // Set the image
        [button setImage:[UIImage imageNamed:@"pkq"] forState:UIControlStateNormal];
    }
    Copy the code

    We’re going to set an image for UIButton, which is actually going to add a UIImageView.

  • Adding rounded corners and clipping to a UIButton triggers off-screen rendering.

    // Set the rounded corners
    button.layer.cornerRadius = 100.0;
    // Set crop
    button.clipsToBounds = YES;
    Copy the code
  • If you change the UIImageView in UIButton to add rounded corners and cropping, the off-screen rendering will not be triggered.

    // Set the rounded corners
    button.imageView.layer.cornerRadius = 100.0;
    // Set crop
    button.imageView.clipsToBounds = YES;
    Copy the code

Further reading

More on rendering issues can be found in the following article.

  • IOS rendering principle analysis

Give me a like if you found this article helpful