Какой самый надежный способ заставить UIView перерисовать? - PullRequest
111 голосов
/ 01 октября 2009

У меня есть UITableView со списком элементов. Выбор элемента подталкивает viewController, который затем выполняет следующее. from Метод viewDidLoad Я запускаю URLRequest для данных, которые требуются для одного из моих подпредставлений - подкласс UIView с переопределенным drawRect. Когда данные поступают из облака, я начинаю строить свою иерархию представлений. рассматриваемый подкласс получает данные, и его метод drawRect теперь имеет все, что нужно для рендеринга.

Но.

Поскольку я не вызываю drawRect явно - Cocoa-Touch обрабатывает это - у меня нет способа сообщить Cocoa-Touch, что я действительно, действительно хочу, чтобы этот подкласс UIView отображался. Когда? Теперь было бы хорошо!

Я пробовал [myView setNeedsDisplay]. Это иногда работает. Очень пятнистый.

Я боролся с этим часами. Может ли кто-нибудь, кто пожелает, предоставить мне надежный, гарантированный подход к принудительному повторному рендерингу UIView.

Вот фрагмент кода, который передает данные в представление:

// Create the subview
self.chromosomeBlockView = [[[ChromosomeBlockView alloc] initWithFrame:frame] autorelease];

// Set some properties
self.chromosomeBlockView.sequenceString     = self.sequenceString;
self.chromosomeBlockView.nucleotideBases    = self.nucleotideLettersDictionary;

// Insert the view in the view hierarchy
[self.containerView          addSubview:self.chromosomeBlockView];
[self.containerView bringSubviewToFront:self.chromosomeBlockView];

// A vain attempt to convince Cocoa-Touch that this view is worthy of being displayed ;-)
[self.chromosomeBlockView setNeedsDisplay];

Cheers, Дуг

Ответы [ 5 ]

186 голосов
/ 01 октября 2009

Гарантированный, надежный способ заставить UIView повторно выполнить рендеринг - [myView setNeedsDisplay]. Если у вас возникли проблемы с этим, вы, вероятно, столкнулись с одной из следующих проблем:

  • Вы звоните до того, как получите данные, или ваш -drawRect: что-то перегружает.

  • Вы ожидаете, что представление прорисовывается в момент вызова этого метода. Умышленно нет способа требовать «рисовать прямо сейчас» с использованием системы рисования Какао. Это может нарушить работу всей системы компоновки представлений, повысить производительность и создать всевозможные артефакты. Есть только способы сказать «это нужно нарисовать в следующем цикле розыгрыша».

Если вам нужно «немного логики, нарисовать, немного больше логики», тогда вам нужно поместить «немного больше логики» в отдельный метод и вызывать ее, используя -performSelector:withObject:afterDelay: с задержкой 0. Это «немного больше логики» после следующего цикла розыгрыша. См. этот вопрос для примера такого рода кода и случая, когда он может понадобиться (хотя обычно лучше искать другие решения, если это возможно, поскольку это усложняет код).

Если вы не думаете, что что-то затягивается, установите точку останова в -drawRect: и посмотрите, когда вас вызывают. Если вы звоните -setNeedsDisplay, но -drawRect: не вызывается в следующем цикле событий, то покопайтесь в иерархии представлений и убедитесь, что вы не пытаетесь перехитрить где-нибудь. Избыточная сообразительность - причина плохой прорисовки в моем опыте. Когда вы думаете, что лучше знаете, как заставить систему делать то, что вы хотите, вы обычно получаете именно то, чего не хотите.

51 голосов
/ 26 ноября 2010

У меня была проблема с большой задержкой между вызовами setNeedsDisplay и drawRect: (5 секунд). Оказалось, я вызвал setNeedsDisplay в другом потоке, чем основной поток. После перемещения этого вызова в основной поток задержка исчезла.

Надеюсь, это поможет.

13 голосов
/ 06 апреля 2013

Гарантированный возврат денег, железобетонно-твердый способ принудительного просмотра синхронное рисование (до возврата к вызывающему коду) настроить взаимодействие CALayer с вашим подклассом UIView.

В своем подклассе UIView создайте метод - display, который сообщает слою, что « да, он нуждается в отображении », а затем «, чтобы сделать его таким »:

/// Redraws the view's contents immediately.
/// Serves the same purpose as the display method in GLKView.
/// Not to be confused with CALayer's `- display`; naming's really up to you.
- (void)display
{
    CALayer *layer = self.layer;
    [layer setNeedsDisplay];
    [layer displayIfNeeded];
}

Также реализуйте метод - drawLayer:inContext:, который будет вызывать ваш собственный / внутренний метод рисования (который работает, поскольку каждый UIView является CALayerDelegate) :

/// Called by our CALayer when it wants us to draw
///     (in compliance with the CALayerDelegate protocol).
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context
{
    UIGraphicsPushContext(context);
    [self internalDrawWithRect:self.bounds];
    UIGraphicsPopContext();
}

И создайте свой собственный метод - internalDrawWithRect: вместе с отказоустойчивым - drawRect::

/// Internal drawing method; naming's up to you.
- (void)internalDrawWithRect:(CGRect)rect
{
    // @FILLIN: Custom drawing code goes here.
    //  (Use `UIGraphicsGetCurrentContext()` where necessary.)
}

/// For compatibility, if something besides our display method asks for draw.
- (void)drawRect:(CGRect)rect {
    [self internalDrawWithRect:rect];
}

А теперь просто звоните [myView display] всякий раз, когда вам действительно нужно это нарисовать. - display сообщит от CALayer до displayIfNeeded, который будет синхронно перезванивать в - drawLayer:inContext: и выполнять рисование в - internalDrawWithRect:, обновляя визуальный элемент с тем, что вписано в контекст, прежде чем двигаться дальше.


Этот подход похож на описанный выше @ RobNapier, но имеет преимущество вызова - displayIfNeeded в дополнение к - setNeedsDisplay, что делает его синхронным.

Это возможно, потому что CALayer s предоставляет больше функциональных возможностей рисования, чем UIView s - слои имеют более низкий уровень, чем представления, и разработаны специально для целей высоко настраиваемого рисования в макете и (как и многие другие вещи в Какао) предназначены для гибкого использования (в качестве родительского класса, или в качестве делегатора, или в качестве моста к другим системам рисования, или просто самостоятельно).

Дополнительную информацию о настраиваемости CALayer s можно найти в разделе Настройка объектов слоя в Руководстве по программированию базовой анимации .

5 голосов
/ 22 февраля 2013

У меня была та же проблема, и все решения от SO или Google не работали для меня. Обычно setNeedsDisplay работает, но когда нет ...
Я попытался вызвать setNeedsDisplay представления только всеми возможными способами из всех возможных потоков и прочего - все еще безуспешно. Мы знаем, как сказал Роб, что

"это должно быть нарисовано в следующем цикле рисования."

Но по какой-то причине на этот раз не получилось. И единственное решение, которое я нашел, - это вызвать его вручную через некоторое время, чтобы пропустить все, что блокирует рисование, например:

dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 
                                        (int64_t)(0.005 * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
    [viewToRefresh setNeedsDisplay];
});

Это хорошее решение, если вам не нужно, чтобы представление перерисовывалось очень часто. В противном случае, если вы делаете какие-то движущиеся (действия) вещи, обычно не возникает проблем с простым вызовом setNeedsDisplay.

Надеюсь, это поможет тому, кто там потерян, как я.

0 голосов
/ 05 июня 2013

Что ж, я знаю, что это может быть большим изменением или даже не подходить для вашего проекта, но считали ли вы, что не выполняет push, пока у вас уже нет данных ? Таким образом, вам нужно только нарисовать вид один раз, и пользовательский опыт также будет лучше - толчок будет двигаться уже загружен.

То, как вы это делаете, находится в UITableView didSelectRowAtIndexPath, который вы асинхронно запрашиваете данные. Получив ответ, вы вручную выполните переход и передаете данные в viewController в prepareForSegue. При этом вы можете захотеть показать какой-нибудь индикатор активности, для простой проверки индикатора загрузки https://github.com/jdg/MBProgressHUD

...