Какой самый надежный способ заставить UIView перерисовать?

У меня есть 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];

Ура, Дуг

6 ответов

Решение

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

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

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

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

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

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

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

Гарантированный возврат денег, надежный железобетонный способ заставить представление рисовать синхронно (до возврата к вызывающему коду) - это настроить 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 с большей функциональностью рисования, чем UIView s do - слои являются более низкими уровнями, чем представления, и разработаны явно для целей высоко настраиваемого рисования в макете и (как и многие другие объекты в Какао) предназначены для гибкого использования (в качестве родительского класса или в качестве делегатора, или как мост к другим системам рисования, или просто самостоятельно).

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

У меня была та же проблема, и все решения от 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,

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

Вы можете использовать CATransaction для принудительной перерисовки:

      [CATransaction begin];
[someView.layer displayIfNeeded];
[CATransaction flush];
[CATransaction commit];

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

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

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