2009-10-01 47 views
100

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

干杯, 道格

回答

176

的保证,岩石固的方式来强制UIView重新渲染是[myView setNeedsDisplay]。如果您遇到麻烦,你可能遇到了这些问题之一:

  • 你说它你确实有数据之前,或者你-drawRect:超过缓存的东西。

  • 您期待在您调用此方法的时刻绘制视图。使用Cocoa绘图系统故意没有办法要求“现在画第二个”。这将破坏整个视图合成系统,垃圾性能并可能会创建各种制造工艺。只有方法可以说“这需要在下一个抽奖周期中进行。”

如果你需要的是“一些逻辑,画,多了一些逻辑,”那么你需要把“一些更多的逻辑”,在一个单独的方法以及使用-performSelector:withObject:afterDelay:具有0延迟调用它那在下一个抽奖周期结束后会提出“更多的逻辑”。请参阅this question以获取该类代码的示例以及可能需要的示例(尽管通常最好是在可能的情况下寻找其他解决方案,因为它会使代码复杂化)。

如果您不认为事情正在进行中,请在-drawRect:中放置一个断点并查看您何时接到电话。如果你打电话给-setNeedsDisplay,但-drawRect:没有在下一个事件循环中调用,那么深入你的视图层次结构,并确保你没有尝试智胜是在某个地方。根据我的经验,过分聪明是导致糟糕绘画的首要原因。当你认为自己最清楚如何欺骗系统做你想做的事情时,你通常会把它做到你不想要的地步。

+0

罗布, 这里是我的清单。 1)我有数据吗?是。我在一个方法中创建视图 - hideSpinner - 从connectionDidFinishLoading在主线程上调用:因此: [self performSelectorOnMainThread:@selector(hideSpinner)withObject:nil waitUntilDone:NO]; 2)我不需要立即绘制。我只需要它。今天。目前,它完全是随机的,并且不受我的控制。 [myView setNeedsDisplay]是完全不可靠的。 我甚至竟然在viewDidAppear:中调用[myView setNeedsDisplay]。 Nuthin'。可可根本无视我。 Maddening !! – dugla 2009-10-01 13:33:01

+0

看着你的代码,你想确认的第一件事是,contatinerView本身就在屏幕上。把其他东西(例如UILabel),看看是否吸引。其次,确保self.containerView不是零。 “没有任何反应”的主要原因是向nil发送消息。你不应该在这里需要setNeedsDisplay,但是如果你这样做了,将它发送到containerView而不是chromosomeBlockView会更典型。你问容器视图重绘自己和它的子视图(你已经重新排列),其中一个碰巧是chromosomeBlockView。 – 2009-10-01 17:37:28

+0

我发现,通过这种方法,'setNeedsDisplay'和'drawRect:'的实际调用之间通常会有一段延迟。虽然通话可靠性在这里,但我不会称之为“最强大的”解决方案 - 理论上,强大的绘图解决方案应该在返回要求抽取的客户端代码之前立即完成所有绘图。 – 2013-05-08 02:36:52

49

我在调用setNeedsDisplay和drawRect之间有一个很大的延迟问题:(5秒)。原来,我在不同于主线程的线程中调用了setNeedsDisplay。将此呼叫移至主线后,延迟就消失了。

希望这有一些帮助。

+0

这绝对是我的错误。我的印象是,我在主线程上运行,但直到我NSLogged NSThread.isMainThread,我意识到有一个角落的情况下,我没有在主线程上进行图层更改。谢谢你挽救我拔出我的头发! – 2013-06-03 05:34:29

4

我有同样的问题,所有来自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就没有问题。

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

+1

谢谢,经过2个小时的研究后,我帮了我的忙。:) – Alex 2018-01-25 00:17:10

12

的退款保证,钢筋混凝土,实心办法迫使着眼于画同步是配置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将告诉CALayerdisplayIfNeeded,它将同步回拨到我们的- drawLayer:inContext:中,并执行- internalDrawWithRect:中的绘图,在继续之前更新与绘制到上下文中的内容相关的视觉。


这种方法类似于@ RobNapier头顶,但除了呼吁- displayIfNeeded- setNeedsDisplay,这使得它同步的优势。

这是可能的,因为CALayer的expose多个图形官能度大于UIView小号DO-层是电平低于视图和明确的布局内的高度可配置的图的目的而设计的,和(在可可等许多事情)是旨在灵活使用(作为父级,或作为代理人,或作为与其他绘图系统的桥梁,或者仅仅是自己使用)。

关于CALayer s的可配置性的更多信息可以在Setting Up Layer Objects section of the Core Animation Programming Guide中找到。

+0

请注意,'drawRect:'的文档明确指出“你不应该直接调用这个方法。”另外,CALayer的'display'明确地说“不要直接调用这个方法”。如果你想直接同时在图层内容上绘制,就不需要违反这些规则。您可以随时在任何时候绘制图层的“内容”(即使在后台线程中)。只需为该视图添加一个子图层即可。但这不同于将它放在屏幕上,需要等待正确的合成时间。 – 2013-05-08 04:05:22

+0

我说要添加一个子图层,因为文档也直接与UIView的图层的内容进行混淆。(“如果图层对象绑定到视图对象,则应该避免直接设置该属性的内容。而层通常会导致视图在随后的更新期间替换此属性的内容。“)我并不特别推荐这种方法;过早拉伸会影响性能和拉丝质量。但如果你出于某种原因需要它,那么'contents'就是如何得到它的。 – 2013-05-08 04:13:10

+0

@RobNapier直接调用'drawRect:'得到的点。没有必要证明这种技术,并已修复。 – 2013-05-08 16:48:03

0

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

你这样做的方式是在UITableViewdidSelectRowAtIndexPath你异步要求的数据。一旦收到响应,您就手动执行segue并将数据传递给prepareForSegue中的viewController。 同时您可能希望显示一些活动的指标,为简单的加载指示灯检查https://github.com/jdg/MBProgressHUD