How to just wait for some layout in iOS?

Before starting, note that this has nothing to do with background processing. There is no “calculation” that could be used.

Only UIKit .

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

Let's say on the iPhone 6s

Each of them takes one second to create a UIKit.

It will happen:

one big step

THEY CURE EVERYTHING. To repeat, the screen just freezes for 3 seconds, while UIKit does a huge job. Then they all appear at once.

But let me say that I want this to happen:

enter image description here

THEY CONTINUE. The screen just hangs for 1 second, while UIKit builds one. It seems. He hangs again when he builds the next one. It seems. And so on.

(The “one second” note is just a simple example for clarity. See the end of this post for a more complete example.)

How do you do it in iOS?

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

 view.addItemsA() view.setNeedsDisplay() view.layoutIfNeeded() view.addItemsB() 

You can try the following:

  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() }... 
  • Note that if the value is too small - this approach is simple and obviously does nothing . UIKit will just work. (What else could he do?). If the value is too great, it is meaningless.

  • Please note that at present (iOS10), if I am not mistaken : if you try this trick with a zero delay trick, it works randomly at best. (As you probably expect.)

Disable startup cycle ...

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

Reasonable. But our recent testing in real life shows that in many cases this does NOT work.

(i.e. Apple UIKit is now complex enough to lubricate UIKit beyond this "trick".)

Thought: is there a way, in UIKit, to get a callback when it basically drew all the views you looked at? Is there any other solution?

It seems like one of the solutions ... will put subviews in the controllers , so you get the "didAppear" callback and keep track of them. It seems infantile , but maybe this is the only template? Will this really work? (Only one problem: I see no guarantee that didAppear guarantees that all the subheadings will be drawn.)




If this is not clear ...

An example of everyday use:

• Say seven sections are possible.

• Say, each of them usually takes from 0.01 to 0.20 to build UIKit (depending on what information you show).

• If you just “let everything go in one stroke” , it will be normal or acceptable (total time, say from 0.05 to 0.15) ... but ...

• Often there is a tedious pause for the user, as a "new screen" appears. ( .1 to .5 or worse ).

• If you do what I ask, it will always smooth the screen, one fragment at a time, with the minimum possible time for each fragment.

+23
performance ios runloop
Feb 23 '17 at 15:41
source share
5 answers

The server window has ultimate control over what appears on the screen. iOS sends updates only to the window server when performing the current CATransaction . To make this happen when necessary, iOS registers CFRunLoopObserver for .beforeWaiting activity in the main thread execution loop. After processing the event (presumably by calling it in your code), the execution loop calls the observer before it waits for the next event. The observer captures the current transaction, if any. Completion of the transaction includes starting the layout, skipping the display (in which you call the drawRect methods), and sending the updated layout and contents to the window server.

A call to layoutIfNeeded executes the layout, if necessary, but does not invoke a display session or sends nothing to the window server. If you want iOS to send updates to the window server, you must commit the current transaction.

One way to do this 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 right away. The new CALayer will not be sent to the window server until the transaction is completed, and you cannot add animation to it until it appears on the screen. So, you add a layer to the layer hierarchy, call CATransaction.flush() , and then add the animation to the level.

You can use CATransaction.flush to get the desired effect. I do not recommend this , but here is 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) } 

And here is the result:

CATransaction.flush demo

The problem with the above solution is that it blocks the main thread by calling Thread.sleep . If your main thread does not respond to events, the user will not only be disappointed (because your application does not respond to its touch), but ultimately iOS will decide that the application freezes and kills it.

It’s best to just plan on adding each view whenever you want it to appear. You claim that "this is not engineering", but you are mistaken, and your data reasons do not make sense. iOS typically refreshes the screen every 16 milliseconds (unless your application takes longer than to process events). As long as the required delay is at least so long, you can simply schedule the execution of the block after the delay to add the next view. If you want the delay to be less than 16 milliseconds, you cannot have it at all.

So here is the best, recommended way to add 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 is the result (identical):

DispatchQueue asyncAfter demo

+5
Feb 26 '17 at 18:35
source share

TL; DR

Enable pending user interface changes on the render server using CATransaction.flush() or split the work into several frames using CADisplayLink (sample code below).




Summary

Is there a way, in UIKit, to get a callback when it has drawn all the views you viewed?

No

iOS acts like changing the rendering of a game (no matter how much you make) no more than once per frame. The only way to guarantee the passage of the code after your changes have been displayed on the screen is to wait for the next frame.

Is there any other solution?

Yes, iOS can only process changes once per frame, but your application is not what this rendering does. The window server process.
Your application does its layout and rendering, and then commits its changes to its layerTree on the render server. It will do this automatically at the end of runloop, or you can force pending transactions to the visualization server CATransaction.flush() .

However, blocking the main thread is generally bad (not only because it blocks user interface updates). Therefore, if you can avoid it,

Possible solutions

This is the part that interests you.

1: Make as many background queues as you can and increase productivity.
Seriously, the iPhone 7 is the third most powerful computer (not a phone) in my house, just beaten up by my gaming PC and Macbook Pro. It is faster than every other computer in my house. A pause of 3 seconds is not required to display the application user interface.

2: latent waiting for CAT transactions
EDIT: As rob mayoff pointed out , you can force CoreAnimation to send pending changes to the render server by calling CATransaction.flush()

 addItems1() CATransaction.flush() addItems2() CATransaction.flush() addItems3() 

This does not actually make a difference, but sends the pending user interface updates to the window server, ensuring that they are included in the next screen update.
This will work, but with this warning in the documentation for Apple.

However, you should try to avoid calling flush explicitly. By providing a flash to execute during runloop ...... both transactions and animations that work from transaction to transaction will continue to function.

However, the CATransaction header file includes this quote, which seems to imply that even if they don't like it, it is officially supported.

In some cases (i.e., no loop cycle or loop cycle is blocked), explicit transactions may be required to receive timely updates to the rendering tree.

Apple Documentation - "Best Documentation for + [CATransaction flush] . "

3: dispatch_after()
Just delay the code until the next runloop. dispatch_async(main_queue) will not work, but you can use dispatch_after() without delay.

 addItems1() DispatchQueue.main.asyncAfter(deadline: .now() + 0.0) { addItems2() DispatchQueue.main.asyncAfter(deadline: .now() + 0.0) { addItems3() } } 

You mentioned in your answer that this no longer works for you. However, it works great in the test Swift Playground and the iOS app example that I included with this answer.

4: Use CADisplayLink
CADisplayLink is called once for each frame and allows you to ensure that only one operation is performed for each frame, ensuring that the screen can be updated between operations.

 DisplayQueue.sharedInstance.addItem { addItems1() } DisplayQueue.sharedInstance.addItem { addItems2() } DisplayQueue.sharedInstance.addItem { addItems3() } 

This helper class is required to work (or similar).

 // A queue of item that you want to run one per frame (to allow the display to update in between) class DisplayQueue { static let sharedInstance = DisplayQueue() init() { displayLink = CADisplayLink(target: self, selector: #selector(displayLinkTick)) displayLink.add(to: RunLoop.current, forMode: RunLoopMode.commonModes) } private var displayLink:CADisplayLink! @objc func displayLinkTick(){ if let _ = itemQueue.first { itemQueue.remove(at: 0)() // Remove it from the queue and run it // Stop the display link if it not needed displayLink.isPaused = (itemQueue.count == 0) } } private var itemQueue:[()->()] = [] func addItem(block:@escaping ()->()) { displayLink.isPaused = false // It needed again itemQueue.append(block) // Add the closure to the queue } } 

5: call runloop directly.
I don’t like it because of the possibility of an infinite loop. But I admit, this is unlikely. I'm also not sure if this is officially supported, or an Apple engineer is going to read this code and look horrified.

 // Runloop (seems to work ok, might lead to infitie recursion if used too frequently in the codebase) addItems1() RunLoop.current.run(mode: .defaultRunLoopMode, before: Date()) addItems2() RunLoop.current.run(mode: .defaultRunLoopMode, before: Date()) addItems3() 

This should work if only (although it doesn’t respond to runloop events) you do something else to block the runloop call from completing, since CATransaction is sent to the window server at the end of runloop.




Code example

Xcode Demo and Xcode Playground (Xcode 8.2, Swift 3)




Which option to use?

I like DispatchQueue.main.asyncAfter(deadline: .now() + 0.0) and CADisplayLink best. However, DispatchQueue.main.asyncAfter does not guarantee that it will work at the next runloop mark so that you would not want to trust it?

CATransaction.flush() will force you to change the user interface settings on the render server, and this use seems to follow Apple's comments on the class, but it contains some warnings.

In some cases (i.e., no loop cycle or loop cycle is blocked), explicit transactions may be required to receive timely updates to the rendering tree.




Detailed explanation

The rest of this answer is a background to what goes on inside UIKit, and explains why the original answers to attempts to use view.setNeedsDisplay() and view.layoutIfNeeded() did nothing.




UIKit Layout and Render Overview

CADisplayLink is not fully affiliated with UIKit and runloop.

Not really. iOS UI is a graphics processor created as a 3D game. And trying to do as little as possible. Therefore, many things, such as layout and rendering, do not happen when something changes, but when it is needed. This is why we call setNeedsLayout not mock views. Each frame can change several times. However, iOS will only attempt to call layoutSubviews once per frame, and not 10 times that setNeedsLayout can be called.

However, a lot happens on the processor (layout, -drawRect: etc.), since all this is combined.

Please note that this is all simplified and misses a lot of things, such as CALayer, which is actually a real viewer that does not display a UIView, etc.

Each UIView can be thought of as a bitmap, texture image / GPU. When the screen is displayed, the GPU combines the presentation hierarchy into the resulting frame that we see. It forms representations, turning subviews textures on top of previous views into a ready-made render that we see on the screen (similar to a game).

This is what enabled iOS to have such a smooth and easily animated interface. To animate a view across the screen, it does not need to rewrite anything. In the next frame, which views the texture, it is simply composed in a slightly different place on the screen than before. Neither he nor the opinion that he was in excess so that their contents were overwritten.

In the past, general performance advice has usually been to reduce the number of views in the view hierarchy by fully displaying view cells in drawRect: This tip was to make the GPU composting step faster on early iOS devices. However, the GPU runs so fast on modern iOS devices that it no longer bothers.

LayoutSubviews and DrawRect

-setNeedsLayout nullifies the current layout of the view and marks it as a necessary layout.
-layoutIfNeeded will pass the view if it does not have a valid layout

-setNeedsDisplay will mark the views that need to be redrawn. We said earlier that each view is rendered into a texture / image view that can be moved and manipulated using the GPU without the need to redraw. This will cause it to be redrawn. The drawing is done by calling -drawRect: on the CPU and therefore slower than being able to rely on the GPU, which can handle most frames.

And it is important to note that these methods do not. Layout methods do nothing visually. Although if the contentMode set to redraw , changing the view frame may invalidate the rendering of the views (trigger -setNeedsDisplay ).

You can try the following all day long. This does not seem to work:

 view.addItemsA() view.setNeedsDisplay() view.layoutIfNeeded() view.addItemsB() view.setNeedsDisplay() view.layoutIfNeeded() view.addItemsC() view.setNeedsDisplay() view.layoutIfNeeded() 

From what we learned, the answer should be obvious why this is not working now. view.layoutIfNeeded() does nothing but recalculate the frames of its subzones.
view.setNeedsDisplay() just marks the view as it needs to be redrawn the next time. UIKit scans the view hierarchy, updating the view textures to send to the GPU. However, this does not affect the subviews you tried to add.

In your view.addItemsA() example, 100 submenus have been added. These are separate unrelated layers / textures on the GPU until the GPU merges them into the next framebuffer. The only exception is if CALayer shouldRasterize set to true. In this case, he creates a separate texture for the presentation, and he looks at it and visualizes (thinks on the GPU) the presentation, and he peeps into one texture, effectively caching the composition that he will have to perform each frame. This has a performance advantage when it is not necessary to compile all its routines in each frame. However, if the view or its views change frequently (for example, during animation), this will be a penalty for performance, as this will invalidate the cached texture, which often requires redrawing (similar to calling -setNeedsDisplay ).




Now any game engineer will just do it ...

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

Now it really works.

But why does this work?

Now, -setNeedsLayout and -setNeedsDisplay do not start relaying or redrawing, but instead simply mark the view as necessary. As UIKit prepares to render the next frame, it invokes views with invalid textures or layouts for redrawing or relaying. After everything is ready, he sends the GPU command to the composition and displays a new frame.

So, the main startup loop in UIKit probably looks something like this.

 -(void)runloop { //... do touch handling and other events, etc... self.windows.recursivelyCheckLayout() // effectively call layoutIfNeeded on everything self.windows.recursivelyDisplay() // call -drawRect: on things that need it GPU.recompositeFrame() // render all the layers into the frame buffer for this frame and displays it on screen } 

So, back to the source code.

 view.addItemsA() // Takes 1 second view.addItemsB() // Takes 1 second view.addItemsC() // Takes 1 second 

So, why do all 3 changes appear immediately after 3 seconds instead of one at a time for 1 second?

Well, if this bit of code is triggered by pressing a button or the like, it synchronously locks the main thread (the UIKit thread requires user interface changes) and therefore blocks the execution cycle in line 1, the even processing part. In fact, you make the first line of the runloop method return in 3 seconds.

However, we determined that the layout will not be updated to line 3, individual views will not be displayed until line 4 and no changes are displayed on the screen until the last line of the runloop method, line 5.

The reason why runloop is manually started is because you basically put in a call to the runloop() method. runloop

 -runloop() - events, touch handling, etc... - addLotsOfViewsPressed(): -addItems1() // blocks for 1 second -runloop() | - events and touch handling | - layout invalid views | - redraw invalid views | - tell GPU to composite and display a new frame -addItem2() // blocks for 1 second -runloop() | - events // hopefully nothing massive like addLotsOfViewsPressed() | - layout | - drawing | - GPU render new frame -addItems3() // blocks for 1 second - relayout invalid views - redraw invalid views - GPU render new frame 

, , . , -runloop , .







.




,




CADisplayLink NSRunLoop

, KH , , " " ( : : RunLoop.current) - CADisplayLink.

Runloop CADisplayLink - . CADisplayLink runloop .
( ), , NSRunLoop CADisplayLink , . , NSRunLoop - while (1), , , .. , Apples .

. , , . , , , while for , . "" , .
- - developer.apple.com

CADisplayLink NSRunLoop , . CADisplayLink:

" , vsync ".
: func add(to runloop: RunLoop, forMode mode: RunLoopMode)

preferredFramesPerSecond .

, , .
...
, 60 , , .

, - CADisplayLink ( ), .

, , UIKit.

Not really. , UIViews , , UIKit . , , UIKit.

UIKit " " {... " , . ! , !" }

, .

" " {... , , , , - , ! - , ! ... "}

, UIKit, , , . ?

" UIKit" , UIKit . -setNeedsLayout -setNeedsDisplay , . , , , . , 10 , UIKit - ( -layoutIfNeeded -setNeedsLayout ).

-setNeedsDisplay . , , , . layoutIfNeeded , displayIfNeeded , , . , UIView UIImage, ( CALayer, , UIImage, ). UIImage. UIImage - , , - .

, UIView ?

UIKits main render runloop. UIKit, . UIKit, , , . / SpringBoard ( ) iOS 6 ( BackBoard FrontBoard SpringBoards , , . / / / / / ..).

UIKits, , . , , UIKits , , ( - , runloop).

 -(void)runloop { //... do touch handling and other events, etc... UIWindow.allWindows.layoutIfNeeded() // effectively call layoutIfNeeded on everything UIWindow.allWindows.recursivelyDisplay() // call -drawRect: on things that need to be rerendered // Sends all the changes to the render server process to actually make these changes appear on screen // CATransaction.flush() which means: CoreAnimation.commit_layers_animations_to_WindowServer() } 

, iOS . iPad , . , .

, , . . "", sleep(1) . . iOS . , , .. .

UIKit " " {... " , . ! , !" }

UIKit stop video frames , . 60FPS, , . - 60FPS, , , .
, CoreAnimation.commitLayersAnimationsToWindowServer() , ( add lots of views ), . , , , , .

- UIKit, , . sleep(1) UIView, , , ( sleep() ). , , , .

 func freezePressed() { var newFrame = animationView.frame newFrame.origin.y = 600 UIView.animate(withDuration: 3, animations: { [weak self] in self?.animationView.frame = newFrame }) // Wait for the animation to have a chance to start, then try to freeze it DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { NSLog("Before freeze"); sleep(2) // block the main thread for 2 seconds NSLog("After freeze"); } } 

This is the result:

.

freezePressed() .

 func freezePressed() { var newFrame = animationView.frame newFrame.origin.y = 600 UIView.animate(withDuration: 4, animations: { [weak self] in self?.animationView.frame = newFrame }) // Wait for the animation to have a chance to start, then try to freeze it DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in // Do a lot of UI changes, these should completely change the view, cancel its animation and move it somewhere else self?.animationView.backgroundColor = .red self?.animationView.layer.removeAllAnimations() newFrame.origin.y = 0 newFrame.origin.x = 200 self?.animationView.frame = newFrame sleep(2) // block the main thread for 2 seconds, this will prevent any of the above changes from actually taking place } } 

sleep(2) 0,2 , , . , 2 , , , .

sleep() .

, , , . UIViews, . , , , , , . , iOS , , SpringBoard, iOS. - , iOS ( , , ).

,

  • UIKit , .
  • UIKit , UIKit , .
  • UIKit ,
  • , arent
+13
23 . '17 16:36
source share

. .

, . , , , , : runloop , , . , , delay . (, delay .)

+1
23 . '17 15:45
source share

3 , . , , subview , . - :) , , layoutSubviews . - , - 1000 subviews . childviewcontroller , viewDidLayoutSubviews() , , . 1000 viewcontroller . 0 , . Here is a working example.

  import UIKit class TrackingViewController: UIViewController { var layoutCount = 0 override func viewDidLoad() { super.viewDidLoad() // Add a bunch of subviews for _ in 0...1000{ let view = UIView(frame: self.view.bounds) view.autoresizingMask = [.flexibleWidth,.flexibleHeight] view.backgroundColor = UIColor.green self.view.addSubview(view) } } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() print("Called \(layoutCount)") if layoutCount == 1{ //finished because first call was an emptyview NotificationCenter.default.post(name: NSNotification.Name(rawValue: "kLayoutFinished"), object: nil) } layoutCount += 1 } } 

, subviews, .

 import UIKit class ViewController: UIViewController { var y :CGFloat = 0 var count = 0 override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. NotificationCenter.default.addObserver(self, selector: #selector(ViewController.finishedLayoutAddAnother), name: NSNotification.Name(rawValue: "kLayoutFinished"), object: nil) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 4, execute: { //add first view self.finishedLayoutAddAnother() }) } deinit { NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: "kLayoutFinished"), object: nil) } func finishedLayoutAddAnother(){ print("We are finished with the layout of last addition and we are displaying") addView() } func addView(){ // we keep adding views just to cause print("Fired \(Date())") if count < 100{ DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.0, execute: { // let test = TestSubView(frame: CGRect(x: self.view.bounds.midX - 50, y: y, width: 50, height: 20)) let trackerVC = TrackingViewController() trackerVC.view.frame = CGRect(x: self.view.bounds.midX - 50, y: self.y, width: 50, height: 20) trackerVC.view.backgroundColor = UIColor.red self.view.addSubview(trackerVC.view) trackerVC.didMove(toParentViewController: self) self.y += 30 self.count += 1 }) } } } 

, , . , , . , .

gif

 import UIKit class CompletionView: UIView { private var lastUpdate : TimeInterval = 0.0 private var checkTimer : Timer! private var milliSecTimer : Timer! var adding = false private var action : (()->Void)? //just for testing private var y : CGFloat = 0 private var x : CGFloat = 0 //just for testing var randomColors = [UIColor.purple,UIColor.gray,UIColor.green,UIColor.green] init(frame: CGRect,targetAction:(()->Void)?) { super.init(frame: frame) action = targetAction adding = true for i in 0...999{ if y > bounds.height - bounds.height/100{ y -= bounds.height/100 } let v = UIView(frame: CGRect(x: x, y: y, width: bounds.width/10, height: bounds.height/100)) x += bounds.width/10 if i % 9 == 0{ x = 0 y += bounds.height/100 } v.backgroundColor = randomColors[Int(arc4random_uniform(4))] self.addSubview(v) } } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func milliSecCounting(){ lastUpdate += 0.001 } func checkDate(){ //length of 1 frame if lastUpdate >= 0.003{ checkTimer.invalidate() checkTimer = nil milliSecTimer.invalidate() milliSecTimer = nil print("notify \(lastUpdate)") adding = false if let _ = action{ self.action!() } } } override func layoutSubviews() { super.layoutSubviews() lastUpdate = 0.0 if checkTimer == nil && adding == true{ checkTimer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(CompletionView.checkDate), userInfo: nil, repeats: true) } if milliSecTimer == nil && adding == true{ milliSecTimer = Timer.scheduledTimer(timeInterval: 0.001, target: self, selector: #selector(CompletionView.milliSecCounting), userInfo: nil, repeats: true) } } } import UIKit class ViewController: UIViewController { var y :CGFloat = 30 override func viewDidLoad() { super.viewDidLoad() // Wait 3 seconds to give the sim time DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3, execute: { [weak self] in self?.addView() }) } var count = 0 func addView(){ print("starting") if count < 20{ let completionView = CompletionView(frame: CGRect(x: 0, y: self.y, width: 100, height: 100), targetAction: { [weak self] in self?.count += 1 self?.addView() print("finished") }) self.y += 105 completionView.backgroundColor = UIColor.blue self.view.addSubview(completionView) } } } 

, , viewDidAppear, , , , viewDidAppear.

 DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.0, execute: { //code}) 
+1
26 . '17 19:56
source share

NSNotification .

.

A, B, C... (, ), , self performSelectorInBackground

- , - performSelectorOnMainThread , subview .
, , UIViewController, . - , , , . UIViewController ( ). View - UIView, . ViewN - . .

 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@"ViewNotification" object:nil]; 

, . ViewN * V1 = [[ViewN alloc] init]; - subviews - .

 - (void) handleNotification: (id) note { ViewN * Vx = (ViewN*) [(NSNotification *) note.userInfo objectForKey: @"ViewArrived"]; [self.View performSelectorOnMainThread: @selector(addSubView) withObject: Vx waitUntilDone: FALSE]; } 

UIView . , , addSubview . performSelectorOnMainThread subview , .

- , subviews .

 -(void) sendToScreen: (id) obj { NSDictionary * mess = [NSDictionary dictionaryWithObjectsAndKeys: obj, @"ViewArrived",nil]; [[NSNotificationCenter defaultCenter] postNotificationName: @"ViewNotification" object: nil userInfo: mess]; } 

, NSDictionary ViewArrived.
, , , ​​3 :

 -(void) initViews { ViewN * V1 = [[ViewN alloc] init]; ViewN * V2 = [[ViewN alloc] init]; ViewN * V3 = [[ViewN alloc] init]; [self performSelector: @selector(sendToScreen:) withObject: V1 afterDelay: 3.0]; [self performSelector: @selector(sendToScreen:) withObject: V2 afterDelay: 6.0]; [self performSelector: @selector(sendToScreen:) withObject: V3 afterDelay: 9.0]; } 

. , NSArray subviews .
initViews , , , , performSelector .

0
26 . '17 2:13
source share



All Articles