"[CALayer renderInContext]" вылетает на iPhone X

У меня есть обычай UIView что я хочу сделать как UIImage, Обычай UIView это подкласс UIImageView,

В этом представлении я рендерил некоторые элементы пользовательского интерфейса (рисуя несколько кружков над изображением). Количество добавленных кругов может доходить до тысяч.

Я использую этот простой фрагмент кода, чтобы сделать представление как 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:"

Вот распределение памяти, взятое из Instruments, в примере с ~3000 кружками, добавленными как подвиды:

РАСПРЕДЕЛЕНИЕ ПАМЯТИ (HEAP + ANONYMOUS VM

Теперь вот странная часть... Это работает нормально, и я могу рендерить изображение несколько раз (последовательно) и сохранять его в галерее изображений на устройствах, таких как iPhone 5, iPhone 5s, iPhone 6s, iPad Air 2, iPad Mini 4.. Но тот же код вызывает предупреждение о памяти на iPhone X и в конечном итоге вылетает приложение...

К сожалению, у меня нет доступа к iPhone X, и человек, который сообщил об этом, не имеет доступа к Mac, поэтому я не могу исследовать его глубже.

Я действительно не знаю, делаю ли я что-то не так... Знаете ли вы, есть ли что-то другое в iPhone X? Я долго боролся с этой проблемой...

4 ответа

Решение

Как всегда, ответ лежит в мельчайших (и не включенных в вопрос) деталях...

Что я действительно не учел (и узнал через некоторое время), так это то, что iPhone X имеет масштабный коэффициент, равный @ 3x. В этом разница между iPhone X и всеми другими устройствами, на которых выполнялся код...

В какой-то момент в моем коде я настраивал .contentScaleFactor из подпредставлений, чтобы быть равным [UIScreen mainScreen].scale, Это означает, что на более дорогих устройствах качество изображения должно быть лучше.

Для iPhone X, [UIScreen mainScreen].scale возвращает 3. Для всех других устройств, которые я тестировал, [UIScreen mainScreen].scale возвращает 2. Это означает, что на iPhone X объем памяти, используемый для рендеринга изображения, намного выше.

Забавный факт номер два: из другого полезного поста SO я узнал, что на iPhone X, если вы попытаетесь выделить более 50% его общего объема памяти (1392 МБ), он вылетает. С другой стороны, например, в случае iPhone 6s этот процент выше: 68% (1396 МБ). Это означает, что для некоторых старых устройств у вас есть больше возможностей для работы, чем на iPhone X.

Извините за ошибку, это была честная ошибка с моей стороны. Спасибо всем за ваши ответы!

Я думаю, что проблема связана с тем, как CALayer:renderInContext: обрабатывает рисование тысяч видов в контексте, который требует их увеличения. Можно ли попробовать отрисовать подвиды самостоятельно? Затем сравните и проверьте, работает ли он лучше, используя контрольно-измерительные приборы.

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;
}

Я знаю, следующее звучит странно. Но попробуйте сделать целевое изображение на один пиксель больше того, которое вы рисуете. Это решило это для меня (моя конкретная проблема: "[CALayer renderInContext]" вылетает на iPhone X).

В коде:

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

Недавно я написал метод в приложении, которое я делаю, чтобы конвертировать UIView в UIImages (чтобы я мог отображать градиенты на экранах прогресса / панелях вкладок). Я остановился на следующем коде, я использую этот код для рендеринга кнопок панели вкладок, он работает на всех устройствах, включая X.

Цель C:

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

Свифт 4:

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

Я использовал этот код в примере проекта, который я написал, вот ссылка на файлы проекта:

Свифт, Цель C

как вы увидите, оба проекта будут отлично работать на iPhone X!

Другие вопросы по тегам