简体   繁体   中英

“[CALayer renderInContext]” crashes on iPhone X

I have a custom UIView that I want to render as an UIImage . The custom UIView is a subclass of UIImageView .

Inside this view, I am rendering some UI elements (drawing a bunch of circles over the image). The number of circles added can go up to the order of thousands.

I am using this simple code snippet to render the view as an UIImage :

// Create the UIImage (code runs on the main thread inside an @autorelease pool)
UIGraphicsBeginImageContextWithOptions(viewToSave.image.size, viewToSave.opaque, 1.0);
[viewToSave.layer renderInContext:UIGraphicsGetCurrentContext()];
imageToSave = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

// Since I'm rendering some stuff inside the .layer of the UIView,
// I don't think I can use "drawViewHierarchyInRect: afterScreenUpdates:"

Here is the memory allocations taken from Instruments, in an example with ~3000 circles added as sub-views:

内存分配(堆+匿名VM)

Now here's the strange part... This runs fine and I can render the image multiple times (consecutive) and save it in the image gallery on devices like iPhone 5, iPhone 5s, iPhone 6s, iPad Air 2, iPad Mini 4... But the same code triggers a memory warning on iPhone X and eventually crashes the application...

Unfortunately, I do not have access to an iPhone X and the person who reported this doesn't have access to a Mac, so I cannot investigate deeper.

I really don't know if I am doing anything wrong... Are you aware if there is something different about the iPhone X? I've been struggling this issue for quite a while...

I guess that the problem has to do with how CALayer:renderInContext: handles drawing of thousands of views in a context that requires them to be scaled up. Would it be possible to try render the sub-views yourself? Then compare and verify if it works better by using instrumentation.

UIImage *imageToSave = [self imageFromSubLayer:viewToSave];

- (UIImage *)imageFromSubLayers:(UIImageView *)imageView {
    CGSize size = imageView.image.size;
    UIGraphicsBeginImageContextWithOptions(size, YES, .0);
    CGContextRef context = UIGraphicsGetCurrentContext();
    for (CALayer *layer in imageView.layer.sublayers)
        [layer renderInContext:context];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return image;
}

I recently wrote a method into an app I'm making to convert UIView's into UIImages (so i could display gradients on progress views/tab bars). I ended up settling with the following code, I'm using this code to render tab bar buttons, it works on all devices including the X.

Objective C:

UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:gradientView.bounds.size];
    UIImage *gradientImage = [renderer imageWithActions:^(UIGraphicsImageRendererContext * _Nonnull rendererContext) {
        [gradientView drawViewHierarchyInRect:gradientView.bounds afterScreenUpdates:true];
    }];

Swift 4:

let renderer = UIGraphicsImageRenderer(size: gradientView.bounds.size)
        let image = renderer.image { ctx in
            gradientView.drawHierarchy(in: gradientView.bounds, afterScreenUpdates: true)
        }

I used this code in a sample project I wrote up, here is a link to the project files:

Swift , Objective C

as you will see both projects will run on iPhone X perfectly!

I know, the following sounds weird. But try to make the target image one pixel larger than the one that you are drawing. This solved it for me (my particular problem: "[CALayer renderInContext]" crashes on iPhone X ).

In code:

UIGraphicsBeginImageContextWithOptions(
    CGSizeMake(viewToSave.image.size.width + 1, 
               viewToSave.image.size.height + 1), 
    viewToSave.opaque, 
    1.0
);

As allways, the answer lays in the smallest (and not included in the question) detail...

What I really didn't consider (and found out after quite a while), is that the iPhone X has a scale factor equal to @3x . This is the difference between the iPhone X and all other devices that were running the code just fine...

At some point in my code, I was setting the .contentScaleFactor of the subviews to be equal to [UIScreen mainScreen].scale . This means that on higher-end devices, the image quality should be better.

For the iPhone X, [UIScreen mainScreen].scale returns 3. For all of the other devices I have tested with, [UIScreen mainScreen].scale returns 2. This means that on the iPhone X, the ammount of memory used to render the image is way higher.

Fun fact number two: From another useful SO post , I found out that on the iPhone X, if you try to allocate more than 50% of it's total amount of memory (1392 MB), it crashes. On the other hand, for example, in the case of the iPhone 6s, the percentage is higher: 68% (1396 MB). This means that for some older devices you have more meory to work with than on the iPhone X.

Sorry for missleading, it was a honest mistake from my part. Thank you all for your answers!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM