122

我有一个带有项目列表的 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];

干杯,道格

4

6 回答 6

196

UIView强制 a重新渲染的有保证的、坚如磐石的方法是[myView setNeedsDisplay]. 如果您遇到问题,您可能会遇到以下问题之一:

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

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

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

如果您认为事情没有被绘制出来,请在其中设置一个断点,-drawRect:看看您何时被调用。如果您正在调用-setNeedsDisplay,但-drawRect:在下一个事件循环中没有被调用,那么深入您的视图层次结构并确保您没有试图在某个地方智取。在我的经验中,过度聪明是导致画不好的第一大原因。当你认为你最清楚如何欺骗系统做你想做的事情时,你通常会让它做你不想要的事情。

于 2009-10-01T13:02:25.600 回答
53

我在调用 setNeedsDisplay 和 drawRect 之间遇到了很大的延迟问题:(5 秒)。原来我在与主线程不同的线程中调用了 setNeedsDisplay。将此调用移至主线程后,延迟消失了。

希望这个对你有帮助。

于 2010-11-26T10:16:35.753 回答
19

强制视图同步绘制 (在返回调用代码之前)的保证退款、钢筋混凝土的方法是配置CALayer' 与您的UIView子类的交互。

在你的 UIView 子类中,创建一个displayNow()方法来告诉图层“<em>设置显示路线”然后“<em>让它这样”:

迅速

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

Objective-C

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

还要实现一个draw(_: CALayer, in: CGContext)方法,该方法将调用您的私有/内部绘图方法(因为 everyUIView是 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()
}

Objective-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)

迅速

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

Objective-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()方法将告诉CALayerto displayIfNeeded(),这将同步回调到我们的draw(_:,in:)并进行绘图internalDraw(_:),在继续之前用绘制到上下文中的内容更新视觉。


这种方法类似于@RobNapier 上面的方法,但优点是调用displayIfNeeded()除了setNeedsDisplay(),这使得它是同步的。

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

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

于 2013-04-06T01:29:28.210 回答
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.

我希望它能帮助像我一样迷失在那里的人。

于 2013-02-22T15:21:02.427 回答
1

您可以使用 CATransaction 强制重绘:

[CATransaction begin];
[someView.layer displayIfNeeded];
[CATransaction flush];
[CATransaction commit];
于 2021-03-12T15:50:21.433 回答
0

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

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

于 2013-06-05T15:40:04.327 回答