2009-10-01 2 views
100

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

Но.

Потому что я не называю 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, Doug

ответ

176

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

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

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

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

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

+0

Роб, Вот мой контрольный список. 1) Есть ли у меня данные? Да. Я создаю представление в методе - hideSpinner - вызывает основной поток из connectionDidFinishLoading: таким образом: [self performSelectorOnMainThread: @selector (hideSpinner) withObject: nil waitUntilDone: NO]; 2) Мне не нужно рисовать мгновенно. Мне просто нужно это. Cегодня. В настоящее время это совершенно случайно, и я не в своем силе. [myView setNeedsDisplay] полностью ненадежна. Я даже зашел так далеко, чтобы вызвать [myView setNeedsDisplay] в viewDidAppear :. Nuthin. Какао просто игнорирует меня. Maddening !! – dugla

+0

Глядя на ваш код, первое, что вы хотите убедиться, это то, что contatinerView сам на экране. Поместите в него что-то еще (например, UILabel) и посмотрите, не нарисовано ли это. Во-вторых, убедитесь, что self.containerView не равен нулю. Основной причиной «ничего не происходит» является отправка сообщения в ноль. Вам не нужно устанавливать setNeedsDisplay здесь, но если бы вы это сделали, было бы более типичным отправить его в containerView, а не в chromosomeBlockView. Вы просите контейнерView перерисовать себя и его подпрограммы (которые вы перегруппировали), одним из которых является chromosomeBlockView. –

+0

Я обнаружил, что при таком подходе часто возникает задержка между 'setNeedsDisplay' и фактическим вызовом' drawRect: '. В то время как надежность вызова здесь, я бы не назвал это «самым надежным» решением - надежное решение для рисования должно теоретически делать весь чертеж непосредственно перед возвратом к клиентскому коду, требующему ничьей. –

49

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

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

+0

Это была определенно моя ошибка. У меня создалось впечатление, что я работал в основном потоке, но только когда я NSLogged NSThread.isMainThread, я понял, что есть угловой случай, когда я не делал изменения слоя в основном потоке. Спасибо, что спасли меня от вытягивания волос! –

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.

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

+1

Спасибо, это помогло мне после 2 часов исследований :) – Alex

12

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

Более подробную информацию о конфигурируемости CALayer s можно найти в Setting Up Layer Objects section of the Core Animation Programming Guide.

+0

Обратите внимание, что документация для 'drawRect:' явно говорит: «Вы никогда не должны вызывать этот метод непосредственно сами». Кроме того, 'display' CALayer явно говорит:« Не вызывайте этот метод напрямую ». Если вы хотите синхронно нарисовать содержимое '' 'слоев прямо, нет необходимости нарушать эти правила. Вы можете просто нарисовать «содержимое» слоя в любое время (даже в фоновом потоке). Для этого просто добавьте подуровень. Но это отличается от размещения на экране, что должно подождать до правильного времени компоновки. –

+0

Я говорю, чтобы добавить подуровень, так как документы также предупреждают о том, что они беспорядочно взаимодействуют с содержимым слоя 'UIView '(« Если объект слоя привязан к объекту представления, вы должны избегать непосредственного указания содержимого этого свойства. и слои обычно приводят к представлению, заменяющему содержимое этого свойства во время последующего обновления ».) Я не рекомендую этот подход; преждевременный рисунок подрывает производительность и качество рисунка. Но если вам это нужно по какой-то причине, то «содержимое» - это как его получить. –

+0

@RobNapier Точка, занятая вызовом 'drawRect:' непосредственно. Не было необходимости продемонстрировать эту технику и было исправлено. –

0

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

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

Смежные вопросы