Swift - TextView layoutSubviews() not called during viewWillTransition() - ios

Does anyone know why UITextView.layoutSubviews() is not called when rotating a device to portrait mode?
When rotating to landscape mode, these are called:
UIViewController.viewWillTransition
UIViewController.viewDidLayoutSubviews
UITextView.layoutSubviews
UILabel.layoutSubviews
But when rotating back to portrait, the UILabel.layoutSubviews() is called, but not the UITextView.layoutSubviews. This is in an empty project with no other code apart from traces in these methods.

layoutSubviews is usually called when setNeedsLayout() is invoked already in the previous invocation of run loop.
If the layout system does not think it needs to be called, it will not be called.
Ideally you should not override this function. You should just call setNeedsLayout() after making superview changes, and let the layout system call the default implementation of this function. Morever, you should define your subview layout needs inside auto-layout so it is able to get correct values from there.
If you want immediate update, you should call layoutIfNeeded().
This is because this is one of those methods that are called arbitrarily by UIKit framework and it may not be ideal for your layout needs.

There are 2 separate things here.
Understanding layoutSubviews(). I.e. when and where to use it.
How to achieve what you want to do the right way. I.e. doing something with the UITextView at device rotation.
About the layoutSubviews(), you should not put any logic here as your view is not having any sub views.
You may say that we expect iOS to call it, so we can put some implementation here, but again, that is not the right way. layoutSubviews() is not meant to alter the view itself, but just laying out sub views.
I would recommend reading more on layoutSubviews(). I learnt from here, when I started learning iOS.
Now to achieve what you want to do, i.e. do something at the time of device rotation, you proper way is to use viewWillTransition(to:with:) method of UIViewController.
In this method, you can put logic to do something just before the transition will happen.
And you can also put logic which will execute during the transition OR after the transition completes, using the UIViewControllerTransitionCoordinator parameter passed to viewWillTransition(to:with:)
Hope this helps!

Related

Calling layoutIfNeeded() right after setNeedsLayout()

I had a conversation with my colleagues about setNeedsLayout() and layoutIfNeeded().
Starting from the conclusion, my colleague says
setNeedsLayout() 'may' or 'should' be called before layoutIfNeeded(), because layoutIfNeeded() may not conduct layout if we don't set the 'layout flag' to true by calling setNeedsLayout().
Whereas, my thought is, calling both of them in the same place will lead to the same result as we call layoutIfNeeded() only. Because...
What I know about them is, as described here and here,
setNeedsLayout() is just invalidating the current layout and then the coming update cycle would take care of the rest.
layoutIfNeeed() is updating the layout immediately, so don't want to the coming update cycle.
Simply saying, async call and sync call. And that's it.
If there is no animation, I don't recall I've seen a lot of cases I had to use layoutIfNeeded(Perhaps one or two). Most of the cases, it worked quite well only with setNeedsLayout(). Moreover, if there is a case that needs to call both of them in the same place, I would rather say it's a bug from UIKit.
Can someone explain me which one is correct, and why?
First, a little discussion what these methods do, the answer to your question is below that.
From the documentation of setNeedsLayout():
This method makes a note of the request and returns immediately. Because this method does not force an immediate update, but instead waits for the next update cycle, you can use it to invalidate the layout of multiple views before any of those views are updated.
Usually, you need to call this if there's some external factor affecting the layout that UIKit doesn't know about (e.g. old fashioned manual layouting code by overriding layoutSubviews()). It doesn't trigger an immediate re-layout, that usually only happens on the next main runloop iteration. This allows you to mark several views as needing layouts, you can call it on the same view multiple times within the same runloop iteration, but UIKit only does the actual layouting once (as I just noted, usually on the next runloop iteration).
But if you need the layout to get settled right now (e.g. because you need to measure its result), you need to call layoutIfNeeded().
If no layout updates are pending, this method exits without modifying the layout or calling any layout-related callbacks.
This is a crucial point about layoutIfNeeded(): if the layout system doesn't know there are pending changes, it simply won't do any. Calling setNeedsLayout() is the way we tell the layout system that there are changes that require a layout run.
If you only use auto-layout and manipulate just the constraints of the view you are about to re-layout, you can skip setNeedsLayout() since that has already been called indirectly; but setNeedsLayout() is very cheap, so there's no harm in calling it if you're unsure.
So, after calling setNeedsLayout(), do you need to call layoutIfNeeded()? To simply update the view on screen, no, you do not need to call layoutIfNeeded() since UIKit will do so on its own on the next main runloop iteration. In fact, you might negatively impact render performance if you always call layoutIfNeeded() even though your code doesn't need the updated layout right away since this can result in multiple layouting runs within the same runloop iteration.
But if you rely on the layout information immediately (for example, you need to measure the height of a view), you do need to call it. Just beware to have your design not do this too often.
Your comparison with "async" and "sync" is indeed somewhat fitting: in a way, you can see setNeedsLayout() as triggering an asynchronous operation and layoutIfNeeded() "waits" until it has happened (that's not what's actually going on, but from a certain point of view that is the observable effect).

The occasion to call setNeedsLayout or setNeedDisplay

I have read some articles that what's the difference between setNeedsLayout and layoutIfNeeded, while what I am focusing is:
1. do I need to call these two method together if I want layout immediately, because I saw this kind of combination so many times
2. when do I need to call setNeedsLayout? upon my understanding, If I change view's frame, it will update layout during next cycle, that I don't have to call setNeedsLayout explictly
How these things work is through invalidation to remove redundancy. A view will contain information if needs to layout or not.
So calling setNeedsLayout will just set some internal boolean value needsLayout to true. Once layoutIfNeeded is called it will check this boolean value
if needsLayout {
needsLayout = false
doMagic() // Calls layoutSubviews at some point
}
Why this is designed this way is because multiple calls may invalidate layout but we want to layout it only once or as fewer times as possible.
In most cases you will not need to call setNeedsLayout because most changes already do that for you. For instance you may change a constraint value and invalidation is done for you. All you need is to call layoutIfNeeded and your views will update. To be more correct you don't even need to call layoutIfNeeded as it will do that for you in the next cycle. But you will need to call it if you want the change animated for instance and you need to do that in animation block.
myViewConstraint.constant = 40.0 // Will already call setNeedsLayout
UIView.animate(withDuration: 0.3, animations: {
myView.layoutIfNeeded()
})
So changing constraints does nothing but change the information on how the view(s) should be layout. Only the call to layoutIfNeeded will actually use those values and change layout. That is why you only need to put that in the animation block (it is not wrong to put it all in the block though).
To be fair there have been some changes where now by default (you may disable it) animate methods already layout your views by themselves so you can do with even less code but that is not the point at the moment.
So:
You do not need to call the 2 methods together to layout immediately. If the view layout is already invalidated (which is in most cases) then layoutIfNeeded is enough. But note that setNeedsLayout is as trivial as setting a boolean internally to true so there is no harm in calling it, just a precaution. So calling both is safer. Calling setNeedsLayout alone will do nothing "immediately" though.
Hopefully you never need to call setNeedsLayout. There are some complex situations where you need to explicitly invalidate layout and there are a few possible UI bugs. In all other cases this will be done for your. But note that if you come to a situation where you need to call this "it will update layout during next cycle" will not be true. Until the view layout is invalidated it will not layout at all.
I am not sure where setNeedDisplay fits in your question (it is only in your title) but this one works the same way but is a bit more complicated. It will invalidate its content and force it to redraw, call drawRect. This must occur during its drawing pipeline, not just anytime so you may not explicitly call it to redraw. And if you do nothing will happen (maybe crash) since it will have no context to draw on. If you override drawRect and you resize your view it will try to cache your drawn content and use contentMode to resize the drawing. By default it is set to scaleToFill which means your content will be stretched as view size changes. You will need to call setNeedDisplay in order for your drawRect to be called again and you may redraw content accordingly.
I found what I want to ask, when should I call setNeedLayout, when should not. since the aim to call setNeedLayout is to call layoutSubviews, so for situations as follows, I don't have to do call setNeedLayout
Resizing a view
Adding a subview User
scrolling a UIScrollView
(layoutSubviews is called on the UIScrollView and its superview)
User rotating their device
Updating a view’s constraints

Autolayout constraints update with size classes

I know, how to create autolayout constraints with size classes perfectly.
But I am not getting when to call layOutIfNeeded(), setNeedsDisplay(), layOutSubViews(), setUpdateConstraints().
Can someone tell how to properly call this function to update UI after constraints changed.
Another my concern is, when to call only single function out of above and call with other functions.
It must be really clear that your layout is calculated by a routine that is called at specific times at runtime.
It could happen that you need to modify the current layout, for instance changing the constant of a specific constraint. If you just do that you will notice no changes in the UI, this is because the routine is still not called.
What you can do is force the layout routine to be called, and you do that by these two methods:
setNeedsLayout : You are telling that the view needs a layout. The next time the routine is called knows that this view need to have a layout refresh
layOutIfNeeded(): You don't want to wait the next call and you are telling the system to force layout calculation ASAP
Same thing happen with setNeedsDisplay() and displayIfNeeded(), with the first you tell that a view needs to be rendered again, and with the second you tell do ASAP.
If you are asking yourself why, the reason is performance. Is useless to re-render everything each time, this lazy approach will save system resources.
The methods - setNeedsUpdateConstraints and -updateConstraintsIfNeeded are basically the same concept applied to constraints, the difference is that you will not see any changes in UI until you force a layout, why this methods are useful? because sometimes you need to check after a change in constraint if the layout is still valid without changing the aspect of your UI.

Does willMoveToSuperview will also deallocate the UIView on which its got called?

I was wondering if I can call willMoveToSuperview on UIView and after that retain that view to reuse later for one ? something like following
if (!CGRectIntersectsRect(cell.frame, visibleRegion)) {
[cell willMoveToSuperview:nil];
[self.resuableCells addObject:cell];
}
I am not sure about your intent here...
But WillMoveToSuperview - According to doc:
The default implementation of this method does nothing. Subclasses can override it to perform additional actions whenever the superview changes.
So your code,
[cell willMoveToSuperview:nil];
Has no effect unless you override this method in a cell subclass and implement your own logic there.
Coming to your question -
Does willMoveToSuperview will also deallocate the UIView on which its got called?
Answer is obvious - NO.
willMoveToSuperview is an observer method that the system calls as a courtesy to you in order to give you a chance to handle special cases before it completes some other hidden tasks.
It's default behavior is to do nothing, but you might want to tidy up something in your code prior to a move by overriding this method.
A proper use case might be if you had a view playing a video clip or an animation, and something else in your code is about to rip the view out of it's current hierarchy and place it in some other un-related view hierarchy. You might want the chance to pause the clip or suspend the animation before the move took place.
I doubt it's the right method to handle what you are attempting, and I definitely know you should not be calling it directly.
Feel free to post some more code to show us what you're trying to accomplish and where it's going wrong.

iOS - Where to initialize views

If I want to initialize views programmatically, where in the viewcontroller lifecycle should this happen?
The initial intuition is loadView. However, here, we don't yet have the frame of the view itself (necessary for calculating the sizes/positions of the views). Ditto for viewDidLoad.
Next intuition is viewWillAppear- here we DO (finally) have a guarantee of the frame of the view. However, this has potential to be called many times throughout the vc lifecycle. Ditto for viewDidAppear, etc...
Finally, I found viewWillLayoutSubviews. This works for the initialization of most static layouts- however, whenever any view moves this gets called again (same problem as viewWillAppear).
I've seen recommendations to init the views in loadView and set their frames in viewWillLayoutSubviews (since setting frames should be idempotent, who cares if it gets called a couple times). But then why does apple so strongly encourage initWithFrame: as the standard initialization method of UIViews (https://developer.apple.com/library/ios/documentation/windowsviews/conceptual/viewpg_iphoneos/CreatingViews/CreatingViews.html)?
Would it be crazy to subclass all my UIViewControllers to have an initWithViewFrame: method? That way I can pass in a frame, manually set it immediately in loadView and be done with it? Or is it better to have a viewHasBeenFormatted flag in viewWillAppear that, if not set, calls the formatting of views and then sets it?
Or is this just apple's way of saying "use interface builder or you're screwed"?
Any help is appreciated!
edit- accidentally wrote loadView where I meant viewWillAppear (in final paragraph)
update- I guess I've come to terms with the fact that there is no place where
The frame is confidently known
The code will only be run once (on setup)
Looks like you're expected to initWithFrame: all your views in viewDidLoad (but then I guess the contents of that view shouldn't treat that frame as even remotely final? because how could it be when it was derived on an assumption? ugh...). Then re-set their frames in layoutSubviews. And make sure to manually handle the differences between initial layout and layout as a result of a moved view there... Man I feel like I've GOT to be missing something... (lol denial...)
I guess that, OR submit and use IB.
update2- viewWillLayoutSubviews WILL get called when one of its subviews is resized. So it is still disqualified as it fails property 2 of the required characteristics that I'm looking for. :(
If you're doing layout with IB, it's fine to do additional view initialization in viewDidLoad (for example, if you need to do stuff that IB doesn't handle well, or if you have UIView subclasses with properties not supported by IB). Alternatively, if you're not using IB, the documentation says you should use loadView to manually initialize your view hierarchy.
You're right, though, that you can't rely on the frame being accurate at that point. So you can accomplish layout via each view's autoResizingMask property, layout constraints (if you're iOS 6 and later), and/or overriding layoutSubviews.
My usual approach is to do layout to some degree in IB, then do anything else I need to (nontrivial layout, custom classes, etc) in viewDidLoad. Then, if I have layout to figure out that autoResizingMask doesn't cover (I'm supporting down to iOS 5), I override viewWillAppear (or layoutSubviews if I'm subclassing UIView) and do some pixel math. I've got a category on UIView to help with this that has things like:
-(void)centerSubviewHorizontally:(UIView *)view pixelsFromTop:(float)pixels;
-(void)centerSubviewHorizontally:(UIView *)view pixelsBelow:(float)pixels siblingView:(UIView *)sibling;
View controllers should not have initWithFrame: methods. What I do in all of my code (I never use IB) is to let the default loadView do its own thing. I create and setup all subviews in viewDidLoad. At this point the view controller's frame has at least a sane value. All subviews can be created with their own sane frames based on the initial size of the view controller's view. With proper autoresizingMask values this may be all you need.
If you need more specific subview layout, put the appropriate layout code in the viewWillLayoutSubviews method. This will deal with any view controller view frame changes including rotation, in-call status bars, etc.
If you don't use interface builder you should override loadView and initialize the views there. If you use autolayout you can also add your constraints there. If you don't use autolayout you can override the layoutSubviews method of your views to adjust the frames.

Resources