Какой самый надежный способ заставить 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];
Ну, я знаю, что это может быть большим изменением или даже не подходить для вашего проекта, но вы рассматривали возможность не выполнять толчок, пока у вас уже нет данных? Таким образом, вам нужно только нарисовать вид один раз, и пользовательский опыт также будет лучше - толчок будет двигаться уже загружен.
То, как вы делаете это в UITableView
didSelectRowAtIndexPath
Вы асинхронно запрашиваете данные. Получив ответ, вы вручную выполните переход и передаете данные в свой viewController в prepareForSegue
, В то же время вы можете захотеть показать какой-нибудь индикатор активности, для простой проверки индикатора загрузки https://github.com/jdg/MBProgressHUD