迫使UIView重绘的最强大的方法是什么?

时间:2009-10-01 12:48:47

标签: cocoa-touch uiview urlrequest

我有一个带有项目列表的UITableView。选择一个项目会推送一个viewController,然后继续执行以下操作。从方法viewDidLoad我为我的子视图所需的数据启动了一个URLRequest - 一个覆盖了drawRect的UIView子类。当数据从云端到达时,我开始构建我的视图层次结构。有问题的子类传递数据,它的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 个答案:

答案 0 :(得分:186)

强制UIView重新呈现的有保证的坚实方法是[myView setNeedsDisplay]。如果您遇到问题,可能会遇到以下问题之一:

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

  • 您希望在调用此方法时绘制视图。有意无法使用Cocoa绘图系统要求“现在就这么画”。这会破坏整个视图合成系统,垃圾性能并可能产生各种伪像。只有方法可以说“这需要在下一个绘制周期中绘制。”

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

如果您认为事情没有被绘制,请在-drawRect:中设置一个断点并查看您何时被召唤。如果您正在呼叫-setNeedsDisplay,但未在下一个事件循环中调用-drawRect:,那么请深入了解您的视图层次结构,并确保您不会在某个地方尝试智取。在我的经历中,过于聪明是导致糟糕绘画的首要原因。当你认为你最了解如何欺骗系统做你想做的事情时,你通常会把它做成你不想要的。

答案 1 :(得分:51)

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

希望这有一些帮助。

答案 2 :(得分:13)

退款保证,加强具体的方法强制观看 同步绘制 (在返回调用代码之前)是配置CALayerUIView子类的交互。

在你的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更多的绘图功能 - 层比视图更低级,并且为了在布局中高度可配置的绘图而明确设计,并且(像Cocoa中的许多东西一样被设计成灵活使用(作为父类,或作为委托者,或作为其他绘图系统的桥梁,或仅仅是他们自己)。

有关CALayer的可配置性的更多信息,请参阅Setting Up Layer Objects section of the Core Animation Programming Guide

答案 3 :(得分:5)

我遇到了同样的问题,SO或Google的所有解决方案都不适用于我。通常,setNeedsDisplay确实有效,但是当它不起作用时...... 我试图从每个可能的线程和东西中尽可能地调用视图的setNeedsDisplay - 仍然没有成功。我们知道,正如Rob所说,

  

“这需要在下一个绘制周期中绘制。”

但由于某种原因,它不会画这个时间。我发现的唯一解决方案是在一段时间后手动调用它,让任何阻止绘制的东西都消失了,就像这样:

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

我希望它会帮助那些迷失在那里的人,就像我一样。

答案 4 :(得分:0)

嗯,我知道这可能是一个很大的变化,甚至不适合你的项目,但你是否认为在你已经拥有数据之前没有执行推送?这样你只需要绘制一次视图,用户体验也会更好 - 推送将在已经加载的情况下移动。

执行此操作的方式是UITableView didSelectRowAtIndexPath您异步询问数据。收到响应后,您手动执行segue并将数据传递到prepareForSegue中的viewController。 同时,您可能希望显示一些活动指示符,用于简单加载指标检查https://github.com/jdg/MBProgressHUD

答案 5 :(得分:0)

我正在使用 CATransaction 强制重绘:

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