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

У меня есть UITableView со списком элементов. Выбор элемента подталкивает viewController, который затем выполняет следующие действия. из метода 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, Дуг

Ответ 1

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

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

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

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

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

Ответ 2

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

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

Ответ 3

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

В вашем подклассе UIView создайте метод - display, который сообщает слою, что "да, он должен отображать", а затем "сделать это так":

/// Redraws the view contents immediately.
/// Serves the same purpose as the display method in GLKView.
/// Not to be confused with CALayer `- display`; naming 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 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 do-слои, более низкоуровневые, чем представления, и явно предназначенные для высоко настраиваемого чертежа в макете и (как и многие вещи в Cocoa) предназначены для гибкого использования (как родительский класс, или как делегат, или как мост к другим системам рисования или только сами по себе).

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

Ответ 4

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

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

Ответ 5

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

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