【问题标题】:What is the most robust way to force a UIView to redraw?强制 UIView 重绘的最可靠方法是什么?
【发布时间】:2010-12-02 23:46:29
【问题描述】:

我有一个带有项目列表的 UITableView。选择一个项目会推动一个 viewController,然后继续执行以下操作。从方法 viewDidLoad 我为我的一个子视图所需的数据触发 URLRequest - 覆盖 drawRect 的 UIView 子类。当数据从云端到达时,我开始构建我的视图层次结构。有问题的子类传递了数据,它的 drawRect 方法现在拥有它需要渲染的一切。

但是。

因为我没有显式调用 drawRect - Cocoa-Touch 处理它 - 我无法通知 Cocoa-Touch 我真的非常希望这个 UIView 子类呈现。什么时候?现在就好了!

我已经尝试过 [myView setNeedsDisplay]。这种方法有时有效。非常参差不齐。

我已经为此苦苦挣扎了好几个小时。有人可以为我提供一种坚如磐石、有保证的方法来强制 UIView 重新渲染。

这是向视图提供数据的代码的 sn-p:

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

干杯, 道格

【问题讨论】:

    标签: cocoa-touch uiview urlrequest


    【解决方案1】:

    您可以使用 CATransaction 强制重绘:

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

    【讨论】:

      【解决方案2】:

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

      在您的 UIView 子类中,创建一个 displayNow() 方法,告诉图层“设置显示路线”然后“让它如此”:

      斯威夫特

      /// 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) 方法,该方法将调用您的私有/内部绘图方法(因为每个UIView 都是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() 方法将告诉CALayerdisplayIfNeeded(),这将同步回调到我们的draw(_:,in:) 并在internalDraw(_:) 中进行绘制,在继续之前使用绘制到上下文中的内容更新视觉效果。


      这种方法与上面的@RobNapier 类似,但优点是除了调用setNeedsDisplay() 之外还调用displayIfNeeded(),使其同步。

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

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

      【讨论】:

      • 请注意drawRect: 的文档明确指出“您永远不应该自己直接调用此方法。”此外,CALayer 的display 明确表示“不要直接调用此方法”。如果您想直接在图层contents 上同步绘制,则无需违反这些规则。您可以随时在图层的contents 上绘制(甚至在后台线程上)。只需为此视图添加一个子层。但这与将它放在屏幕上不同,它需要等到正确的合成时间。
      • 我说要添加一个子层,因为文档还警告直接与 UIView 层的contents 混淆(“如果层对象绑定到视图对象,则应避免设置此内容直接属性。视图和层之间的相互作用通常会导致视图在后续更新期间替换此属性的内容。“)我不是特别推荐这种方法;过早的绘图会破坏性能和绘图质量。但如果你出于某种原因需要它,那么contents 就是如何获得它。
      • @RobNapier Point 通过直接致电drawRect: 获得。无需演示此技术,并且已修复。
      • @RobNapier 至于您建议的contents 技术……听起来很诱人。我最初尝试了类似的方法,但无法让它工作,并发现上述解决方案的代码少得多,没有理由不具备同样的性能。但是,如果您确实有适用于 contents 方法的有效解决方案,我会有兴趣阅读它(没有理由您不能对这个问题有两个答案,对吧?)
      • @RobNapier 注意:这与 CALayer 的 display 无关。它只是 UIView 子类中的一个自定义公共方法,就像在 GLKView 中所做的一样(另一个 UIView 子类,并且我所知道的唯一一个 Apple 编写的需要 DRAW NOW! 功能)。跨度>
      【解决方案3】:

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

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

      【讨论】:

        【解决方案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 就没有问题。

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

        【讨论】:

          【解决方案5】:

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

          希望这会有所帮助。

          【讨论】:

          • 这绝对是我的错误。我的印象是我在主线程上运行,但直到我 NSLogged NSThread.isMainThread 我才意识到有一个极端情况我没有在主线程上进行层更改。谢谢你让我免于拔头发!
          【解决方案6】:

          强制UIView 重新渲染的可靠方法是[myView setNeedsDisplay]。如果您遇到问题,您可能会遇到以下问题之一:

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

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

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

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

          【讨论】:

          • Rob,这是我的清单。 1)我有数据吗?是的。我在一个方法中创建视图 - hideSpinner - 从 connectionDidFinishLoading 在主线程上调用:因此:[self performSelectorOnMainThread:@selector(hideSpinner) withObject:nil waitUntilDone:NO]; 2)我不需要立即绘图。我只是需要它。今天。目前,它是完全随机的,并且不受我的控制。 [myView setNeedsDisplay] 完全不可靠。我什至在 viewDidAppear: 中调用了 [myView setNeedsDisplay]。努丁。可可根本不理我。令人抓狂!!
          • 我发现使用这种方法时,setNeedsDisplaydrawRect: 的实际调用之间通常会有延迟。虽然调用可靠性在这里,但我不会将其称为“最强大”的解决方案——理论上,一个强大的绘图解决方案应该在返回到需要绘图的客户端代码之前立即完成所有绘图。
          • 我在下面评论了您的答案并提供了更多详细信息。试图通过调用文档中明确表示不调用的方法来强制绘图系统无序运行是不可靠的。充其量是未定义的行为。它很可能会降低性能和绘图质量。在最坏的情况下,它可能会死锁或崩溃。
          • 如果您需要在返回之前进行绘制,请在图像上下文中绘制(就像许多人期望的画布一样)。但不要试图欺骗合成系统。它旨在以最少的资源开销快速提供高质量的图形。其中一部分是在运行循环结束时合并绘图步骤。
          • @RobNapier 我不明白你为什么变得如此防御。请再检查一次;没有 API 违规(尽管它已根据您的建议进行了清理,但我认为调用自己的方法不是违规),也没有发生死锁或崩溃的可能性。这也不是“把戏”;它以正常方式使用 CALayer 的 setNeedsDisplay/displayIfNeeded。此外,我已经使用 Quartz 以与 GLKView/OpenGL 平行的方式进行绘制已经有一段时间了;事实证明,它比您列出的解决方案安全、稳定且速度更快。如果你不相信我,试试看。你在这里没有什么可失去的。
          猜你喜欢
          • 2023-04-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-07-31
          • 2016-01-15
          • 2018-03-19
          相关资源
          最近更新 更多