What is the most robust way to force a UIView to redraw?

I have a UITableView with a list of items. Selecting an item pushes a viewController that then proceeds to do the following. from method viewDidLoad I fire off a URLRequest for data that is required by on of my subviews - a UIView subclass with drawRect overridden. When the data arrives from the cloud I start building my view hierarchy. the subclass in question gets passed the data and it's drawRect method now has everything it needs to render.

But.

Because I don't call drawRect explicitly - Cocoa-Touch handles that - I have no way of informing Cocoa-Touch that I really, really want this UIView subclass to render. When? Now would be good!

I've tried [myView setNeedsDisplay]. This kinda works sometimes. Very spotty.

I've be wrestling with this for hours and hours. Could someone who please provide me with a rock solid, guaranteed approach to forcing a UIView re-render.

Here is the snippet of code that feeds data to the view:

// 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

134721 次浏览

强制 UIView重新渲染的有保证的、坚如磐石的方法是 [myView setNeedsDisplay]。如果你在这方面有困难,你可能会遇到以下问题之一:

  • 您在实际拥有数据之前调用它,或者您的 -drawRect:过度缓存了某些内容。

  • 您希望在调用此方法的同时绘制视图。使用 Cocoa 绘图系统有意无意地要求“现在就画”。这会扰乱整个视图合成系统,影响性能,并可能产生各种各样的人工制品。只有一种方法可以说“这需要在下一个绘制周期绘制。”

如果您需要的是“一些逻辑,绘制,一些更多的逻辑”,那么您需要将“一些更多的逻辑”放在一个单独的方法中,并使用延迟为0的 -performSelector:withObject:afterDelay:调用它。这将在下一个抽签周期之后提出“更多的逻辑”。请参阅 this question以获得这类代码的示例,以及可能需要它的情况(尽管如果可能的话,通常最好寻找其他解决方案,因为它会使代码复杂化)。

如果您认为事情没有被画出来,那么在 -drawRect:中放置一个断点,然后看看什么时候会被调用。如果您正在调用 -setNeedsDisplay,但是在下一个事件循环中没有调用 -drawRect:,那么深入挖掘您的视图层次结构,并确保您没有试图在某个地方超越 -drawRect:。根据我的经验,过于聪明是糟糕绘画的首要原因。当你认为你知道如何最好地欺骗系统去做你想做的事情时,你通常会让它做你不想做的事情。

我在调用 setNeedsDisplay 和 draRect 之间有一个很大的延迟问题: (5秒)。事实证明,我在一个与主线程不同的线程中调用 setNeedsDisplay。在将这个调用移动到主线程之后,延迟消失了。

希望这对你有帮助。

我也有同样的问题,所有来自 SO 或 Google 的解决方案都不适合我。通常 setNeedsDisplay是有用的,但是当它不起作用的时候..。
我尝试过从每个可能的线程和内容中调用视图的 setNeedsDisplay,但仍然没有成功。正如罗伯所说,我们知道

"this needs to be drawn in the next draw cycle."

但不知道为什么这次没有画出来。我找到的唯一解决办法就是在一段时间后手动调用它,让任何阻碍抽签的东西消失,像这样:

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就没有问题。

I hope it will help someone who is lost there, like I was.

退款保证,钢筋混凝土坚实的方式,以强制一个意见,以 同步画 (在返回到调用代码之前)是配置 CALayer的交互作用与您的 UIView子类。

在 UIView 子类中,创建一个 displayNow()方法,它告诉层“ 设定显示航向”然后“ 就这么办”:

Swift

/// Redraws the view's contents immediately.
/// Serves the same purpose as the display method in GLKView.
public func displayNow()
{
self.layer.setNeedsDisplay()
self.layer.displayIfNeeded()
}

目标 C

/// Redraws the view's contents immediately.
/// Serves the same purpose as the display method in GLKView.
- (void)displayNow
{
[self.layer setNeedsDisplay];
[self.layer displayIfNeeded];
}

还要实现一个 draw(_: CALayer, in: CGContext)方法,它将调用您的私有/内部绘图方法 (which works since every ABC1 is a CALayerDelegate):

斯威夫特

/// Called by our CALayer when it wants us to draw
///     (in compliance with the CALayerDelegate protocol).
override func draw(_ layer: CALayer, in context: CGContext)
{
UIGraphicsPushContext(context)
internalDraw(self.bounds)
UIGraphicsPopContext()
}

目标 C

/// 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();
}

并创建自定义 internalDraw(_: CGRect)方法以及自动防故障 draw(_: CGRect):

Swift

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


/// For compatibility, if something besides our display method asks for draw.
override func draw(_ rect: CGRect) {
internalDraw(rect)
}

目标 C

/// 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.displayNow()当你真的-真的需要它画 (例如从 CADisplayLink回调)。我们的 displayNow()方法将告诉 CALayerdisplayIfNeeded(),它将同步调用回到我们的 draw(_: CALayer, in: CGContext)并在 internalDraw(_: CGRect)中进行绘制,在继续前进之前更新视觉效果。


这种方法类似于@Robier 的方法,但是它的优点是除了 setNeedsDisplay()之外,还可以调用 displayIfNeeded(),从而实现同步。

这是可能的,因为 CALayerUIView暴露了更多的绘图功能ーー图层比视图低级,并且明确地设计用于在布局中进行高度可配置的绘图,而且(像 Cocoa 中的许多东西一样)被设计为可灵活使用(作为父类,或者作为委托,或者作为通往其他绘图系统的桥梁,或者仅仅作为它们自己)。正确使用 CALayerDelegate协议使这一切成为可能。

关于 CALayer的可配置性的更多信息可以在 设置核心动画编程指南的层对象部分中找到。

我知道这可能是一个很大的变化,甚至不适合你的项目,但你考虑过 在获得数据之前不执行推送操作吗?这样,你只需要绘制一次视图,用户体验也会更好-推进将移动已经加载。

方法是在 UITableView didSelectRowAtIndexPath中异步请求数据。收到响应后,手动执行 segue 并将数据传递给 prepareForSegue中的 viewController。 同时,您可能想要显示一些活动指示器,用于简单的加载指示器检查 < a href = “ https://github.com/jdg/MBProgressHUD”rel = “ nofollow”> https://github.com/jdg/mbprogresshud

您可以使用 CATransaction 强制重画:

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