如何,简单地等待iOS中的任何布局? [英] How to, simply, wait for any layout in iOS?

查看:120
本文介绍了如何,简单地等待iOS中的任何布局?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在开始之前请注意,这与后台处理无关。没有计算涉及到背景。

Before beginning note that this has nothing to do with background processing. There is no "calculation" involved that one would background.

仅UIKit

view.addItemsA()
view.addItemsB()
view.addItemsC()

让我们说一个6s的iPhone

Let's say on a 6s iPhone

这将会发生:

但是我想说我希望这种情况发生:

But let's say I want this to happen:

(注意一秒钟只是一个简单的例子。为了更全面的例子,请参阅本文末尾。)

(Note "one second" is just a simple example for clarity. See the end of this post for a fuller example.)

你如何在iOS中做到这一点?

How do you do it in iOS?

您可以尝试以下方法。 它似乎不起作用

You can try the following. It does not seem to work.

view.addItemsA()

view.setNeedsDisplay()
view.layoutIfNeeded()

view.addItemsB()

你可以试试这个:

 view.addItemsA()
 view.setNeedsDisplay()
 view.layoutIfNeeded()_b()
 delay(0.1) { self._b() }
}

func _b() {
 view.addItemsB()
 view.setNeedsDisplay()
 view.layoutIfNeeded()
 delay(0.1) { self._c() }...




  • 请注意,如果值太小 - 这种方法很简单,显然,什么都不做即可。 UIKit将继续努力。 (它还能做什么?)。如果价值太大,那就毫无意义。

    • Note that if the value is too small - this approach simply, and obviously, does nothing. UIKit will just keep working. (What else would it do?). If the value is too big, it's pointless.

      注意目前(iOS10), 如果我没弄错的话 :如果你用零延迟的技巧尝试这个技巧,它的工作效果最好。 (正如您可能预期的那样。)

      Note that currently (iOS10), if I'm not mistaken: if you try this trick with the trick of a zero delay, it works erratically at best. (As you'd probably expect.)

      view.addItemsA()
      view.setNeedsDisplay()
      view.layoutIfNeeded()
      
      RunLoop.current.run(mode: .defaultRunLoopMode, before: Date())
      
      view.addItemsB()
      view.setNeedsDisplay()
      view.layoutIfNeeded()
      

      合理。 但是我们最近的实际测试表明,在许多情况下这似乎不起作用。

      (即,Apple的UIKit现在已经足够精准,可以涂抹UIKit工作超出了那个技巧。)

      (ie, Apple's UIKit is now sophisticated enough to smear UIKit work beyond that "trick".)

      思考:在UIKit中,或许有一种方法可以获得一个回调,当它基本上已经绘制了所有的您已经堆积了多少人的观点?还有其他解决方案吗?

      Thought: is there perhaps a way, in UIKit, to get a callback when it has, basically, drawn-up all the views you've stacked up? Is there another solution?

      一个解决方案似乎是......将子视图 置于控制器中 ,这样你就得到了一个didAppear回调,并追踪那些。 这似乎是婴儿,但也许这是唯一的模式?无论如何它真的会起作用吗? (仅一个问题:我没有看到任何保证,确保确保所有子视图都已被绘制。)

      One solution seems to be .. put the subviews in controllers, so you get a "didAppear" callback, and track those. That seems infantile, but maybe it's the only pattern? Would it really work anyway? (Merely one issue: I don't see any guarantee that didAppear ensures all subviews have been drawn.)

      日常用例示例:

      •假设有可能七个部分

      •假设每个人通常需要 0.01到0.20 才能构建UIKit(具体取决于具体内容)你正在展示的信息。

      • Say each one typically takes 0.01 to 0.20 for UIKit to construct (depending on what info you're showing).

      •如果你只是让一切都搞砸了它通常会好或可以接受(总时间,比如0.05到0.15)......但是......

      • If you just "let the whole thing go in one whack" it will often be OK or acceptable (total time, say 0.05 to 0.15) ... but ...

      •当新屏幕出现时,用户经常会有一个单调乏味的停顿。 ( .1到.5或更差)。

      • there will often be a tedious pause for the user as the "new screen appears". (.1 to .5 or worse).

      •如果您按照我的要求行事,它将始终顺利进行屏幕,一次一个块,每个块的最小可能时间。

      • Whereas if you do what I am asking about, it will always smooth on to the screen, one chunk at a time, with the minimum possible time for each chunk.

      推荐答案

      窗口服务器最终控制了什么出现在屏幕上。 iOS仅在提交当前 CATransaction 时向窗口服务器发送更新。为了在需要时实现这一点,iOS在主线程的运行循环上为 .beforeWaiting 活动注册 CFRunLoopObserver 。处理完事件后(可能是通过调用代码),运行循环在等待下一个事件到达之前调用观察者。观察者提交当前事务(如果有)。提交事务包括运行布局传递,显示传递(调用 drawRect 方法),并将更新的布局和内容发送到窗口服务器。

      The window server has final control of what appears on screen. iOS only sends updates to the window server when the current CATransaction is committed. To make this happen when it is needed, iOS registers a CFRunLoopObserver for the .beforeWaiting activity on the main thread's run loop. After handling an event (presumably by calling into your code), the run loop calls the observer before it waits for the next event to arrive. The observer commits the current transaction, if there is one. Committing the transaction includes running the layout pass, the display pass (in which your drawRect methods are called), and sending the updated layout and contents to the window server.

      调用 layoutIfNeeded 执行布局(如果需要),但不调用显示传递或向窗口服务器发送任何内容。如果您希望iOS向窗口服务器发送更新,则必须提交当前事务。

      Calling layoutIfNeeded performs layout, if needed, but doesn't invoke the display pass or send anything to the window server. If you want iOS to send updates to the window server, you must commit the current transaction.

      一种方法是调用 CATransaction。齐平()。使用 CATransaction.flush()的合理案例是当你想在屏幕上放一个新的 CALayer 而你希望它立即有动画。在提交事务之前,新的 CALayer 将不会被发送到窗口服务器,并且您无法在屏幕上添加动画。因此,您将图层添加到图层层次结构中,调用 CATransaction.flush(),然后将动画添加到图层。

      One way to do that is to call CATransaction.flush(). A reasonable case to use CATransaction.flush() is when you want to put a new CALayer on the screen and you want it to have an animation immediately. The new CALayer won't be sent to the window server until the transaction is committed, and you can't add animations to it until it's on the screen. So, you add the layer to your layer hierarchy, call CATransaction.flush(), and then add the animation to the layer.

      您可以使用 CATransaction.flush 来获得所需的效果。 我不推荐这个,但这里是代码:

      You can use CATransaction.flush to get the effect you want. I don't recommend this, but here's the code:

      @IBOutlet var stackView: UIStackView!
      
      @IBAction func buttonWasTapped(_ sender: Any) {
          stackView.subviews.forEach { $0.removeFromSuperview() }
          for _ in 0 ..< 3 {
              addSlowSubviewToStack()
              CATransaction.flush()
          }
      }
      
      func addSlowSubviewToStack() {
          let view = UIView()
          // 300 milliseconds of "work":
          let endTime = CFAbsoluteTimeGetCurrent() + 0.3
          while CFAbsoluteTimeGetCurrent() < endTime { }
          view.translatesAutoresizingMaskIntoConstraints = false
          view.heightAnchor.constraint(equalToConstant: 44).isActive = true
          view.backgroundColor = .purple
          view.layer.borderColor = UIColor.yellow.cgColor
          view.layer.borderWidth = 4
          stackView.addArrangedSubview(view)
      }
      

      这是结果:

      上述解决方案的问题在于它通过调用 Thread.sleep 来阻止主线程。如果您的主线程没有响应事件,不仅用户会感到沮丧(因为您的应用没有响应她的触摸),但最终iOS将决定该应用程序挂起并将其终止。

      The problem with the above solution is that it blocks the main thread by calling Thread.sleep. If your main thread doesn't respond to events, not only does the user get frustrated (because your app isn't responding to her touches), but eventually iOS will decide that the app is hung and kill it.

      更好的方法是在希望它出现时安排添加每个视图。你声称它不是工程,但你错了,你的理由没有意义。 iOS通常每16⅔毫秒更新一次屏幕(除非您的应用程序需要的时间比处理事件的时间长)。只要您想要的延迟至少是那么长,您就可以在延迟之后安排一个块来添加下一个视图。如果你想要一个小于16⅔毫秒的延迟,你通常不会有它。

      The better way is simply to schedule the addition of each view when you want it to appear. You claim "it's not engineering", but you are wrong, and your given reasons make no sense. iOS generally updates the screen every 16⅔ milliseconds (unless your app takes longer than that to handle events). As long as the delay you want is at least that long, you can just schedule a block to be run after the delay to add the next view. If you want a delay of less than 16⅔ milliseconds, you cannot in general have it.

      所以这是添加子视图的更好的推荐方法:

      So here's the better, recommended way to add the subviews:

      @IBOutlet var betterButton: UIButton!
      
      @IBAction func betterButtonWasTapped(_ sender: Any) {
          betterButton.isEnabled = false
          stackView.subviews.forEach { $0.removeFromSuperview() }
          addViewsIfNeededWithoutBlocking()
      }
      
      private func addViewsIfNeededWithoutBlocking() {
          guard stackView.arrangedSubviews.count < 3 else {
              betterButton.isEnabled = true
              return
          }
          self.addSubviewToStack()
          DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300)) {
              self.addViewsIfNeededWithoutBlocking()
          }
      }
      
      func addSubviewToStack() {
          let view = UIView()
          view.translatesAutoresizingMaskIntoConstraints = false
          view.heightAnchor.constraint(equalToConstant: 44).isActive = true
          view.backgroundColor = .purple
          view.layer.borderColor = UIColor.yellow.cgColor
          view.layer.borderWidth = 4
          stackView.addArrangedSubview(view)
      }
      

      这是(完全相同的)结果:

      And here's the (identical) result:

      这篇关于如何,简单地等待iOS中的任何布局?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆