Как рисовать части UIView, когда они становятся видимыми внутри UICollectionView?

На Android у меня есть представления элементов с потенциально большой шириной в списке с горизонтальной прокруткой. Представление загружает и рисует фрагменты «изображений» по мере того, как части представления становятся видимыми в списке. Это оптимизация, позволяющая избежать отрисовки всех изображений одновременно, так как это было бы расточительно и медленно. Нарисованный контент, по сути, представляет собой звуковую волну. Сделайте так, как все должно работать. Я не могу разделить фрагменты как отдельные элементы представления в списке. Эта стратегия отлично работает из-за того, как работает архитектура рисования Android.

Теперь я пытаюсь использовать аналогичный шаблон в iOS, но у меня возникли некоторые проблемы, и я не слишком уверен, как найти решение.

В iOS я использую UICollectionView, который рисует ячейки большой ширины, и нам нужна такая же оптимизация загрузки и рисования только видимых фрагментов.


Решение 1.
Проверьте, какие части UIView видны, и нарисуйте только эти видимые фрагменты. Проблема с этим решением заключается в том, что при прокрутке UICollectionView UIView не рисует следующие патроны, которые становятся видимыми. Вот примеры того, о чем я говорю.

UIView загружает начальные фрагменты
Их можно увидеть по разным цветам фрагментов: введите здесь описание изображения

Прокрутите немного
Отображается черный цвет, а содержимое не загружается, потому что нет намека на то, что представление должно отрисовываться снова, поэтому мы не можем загрузить следующий видимый фрагмент . введите здесь описание изображения


Решение 2.
Используйте пользовательский UIView, поддерживаемый CATiledLayer. Это работает идеально, потому что он рисует плитки, когда они становятся видимыми при прокрутке UICollectionView.

Проблема в том, что рисование происходит в фоновом потоке или в следующем цикле рисования, если определено shouldDrawOnMainThread. Это вызывает проблемы, когда изменяется размер UIView или срабатывает моя внутренняя логика масштабирования. Все меняется, потому что цикл рисования не синхронизирован с изменением размера вида.


Итак, как я могу получить уведомление, как CATiledLayer, что часть становится видимой, и я могу нормально рисовать, как CALayer с поддержкой UIView?


ОБНОВЛЕНИЕ 1
Я рассматриваю возможность использования preferredLayoutAttributesFittingAttributes как способа проверить, нужно ли рисовать новый фрагмент. Это вызывается каждый раз, когда ячейка перемещается в новую позицию из-за прокрутки. Надеюсь, это не плохая идея. :)

- (UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes

ОБНОВЛЕНИЕ 2
После долгих испытаний и экспериментов. Использование решения 1 не вариант. Когда UIView достигает огромной ширины, использование памяти выходит за пределы крыши, в то время как при использовании CATiledLayer использование памяти минимально, как я и ожидал. :/


person Jona    schedule 09.02.2018    source источник
comment
Вы действительно пробовали func draw(_ rect: CGRect) ?   -  person MichaelV    schedule 28.02.2018
comment
@MichaelVorontsov, проблема не в этом. :P Рисование не проблема. Проблема заключается в запуске рисования только для видимой области. В моем обновлении № 2 я преодолел ограничение, которое оставляет CATiledLayer более или менее ответом. :/   -  person Jona    schedule 05.03.2018
comment
func setNeedsDisplay(CGRect) ? Вы можете указать дополнительный флаг для рисования только тогда, когда вас просят нарисовать конкретный прямоугольник, и ничего не рисовать, когда флаг опущен.   -  person MichaelV    schedule 05.03.2018


Ответы (1)


Я не знаю, можно ли полностью отключить эффект. Вы можете смягчить его, установив для параметра fadeDuration значение 0. Для этого , вы должны создать подкласс CATiledLayer и перезаписать метод класса fadeDuration.

Вы также можете попробовать реализовать метод класса shouldDrawOnMainThread, но это закрытый метод, и вы рискуете получить отказ в App Store:

@implementation MyTiledLayer

+(CFTimeInterval)fadeDuration {
    return 0.0;
}

+ (BOOL)shouldDrawOnMainThread {
    return YES;
}

@end

или в Свифте:

class MyTiledLayer: CATiledLayer {
    override class func fadeDuration() -> CFTimeInterval {
        return 0.0
    }

    class func shouldDrawOnMainThread() -> Bool {
        return true;
    }
}

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

По-видимому, нет простого способа перерисовать вид при прокрутке прокрутки. Но вы можете реализовать scrollViewDidScroll: и запустить перерисовку вручную. Метод visibleRect всегда возвращает полную площадь слоя, но вы можете использовать

CGRect theVisibleRect = CGRectIntersection(self.frame, self.superlayer.bounds);

или в Свифте

let theVisibleRect = frame.intersection(superlayer.bounds)

для определения видимой области слоя.

person clemens    schedule 22.02.2018
comment
Спасибо за Ваш ответ. Так что странная проблема, о которой я упоминаю, — это не исчезновение анимации. Проблема в том, что мне нужно рисовать наши кешированные изображения сразу после изменения размера UIView. Поскольку CATiledLayer отправляет запрос на отрисовку в потоке bg, возникает проблема синхронизации между размером UIView и отображаемым содержимым слоя. Это вызывает эффект дрожания при изменении размера представления слева. - person Jona; 22.02.2018
comment
@Jona: Да, я знаю этот эффект, и я думаю, что вы не можете полностью избавиться от него. Но уменьшение продолжительности затухания смягчает это. Вы также можете попробовать реализовать метод класса shouldDrawOnMainThread, но это закрытый метод, и вы рискуете получить отказ в App Store. Я обновил свой пост для этого. - person clemens; 22.02.2018
comment
Ах очень круто! Спасибо, что поделились этим. Я только что попробовал этот метод. Это заставляет рисовать в основном потоке. Однако, к сожалению, он отправляет обновление чертежа в следующий цикл основного потока. Таким образом, проблема с синхронизацией все еще возникает. Я хотел бы реализовать свой собственный CATiledLayer. Я просто не могу понять, как включить отображение только видимых плиток. - person Jona; 22.02.2018
comment
@Jona: я думаю, что вы не можете избавиться от рисования в следующем цикле цикла выполнения, даже если вы реализуете свой собственный класс мозаичного слоя. Вы не можете этого сделать, потому что вызовы методов рисования всегда исходят из цикла выполнения. Вы не можете рисовать на экране напрямую. - person clemens; 22.02.2018
comment
Таким образом, при использовании стандартного UIView с поддержкой CALayer все работает идеально при изменении его размера. Вызов обновления пользовательского интерфейса происходит в том же цикле, в котором изменяется размер представления. Так что все синхронизируется правильно. Мне нужно поведение CATiledLayer, которое говорит мне рисовать определенную часть представления, но все из одного и того же основного цикла потока. Это должно быть как-то возможно! :P Надеюсь, я вас не путаю... - person Jona; 22.02.2018
comment
Спасибо за предложение. Примерно так я и начал. Он работает отлично, но когда вы начинаете прокручивать большой UIView, он не дает мне никаких намеков на то, что конкретная часть теперь видна. Я пытался переопределить различные методы, чтобы увидеть, какой из них вызывается при прокрутке представления коллекции, но ничего не нашел. Таким образом, единственным способом было бы аннулировать представление коллекции при прокрутке, но это кажется излишним. - person Jona; 22.02.2018
comment
@Jona: Вы установили contentMode в redraw? - person clemens; 22.02.2018
comment
Да, я использую contentMode с перерисовкой. Кстати, я немного улучшил свой вопрос после нашего обсуждения здесь. - person Jona; 22.02.2018
comment
@Jona: я расширил свой пост. - person clemens; 23.02.2018