Issue while using viewDidLayoutSubviews - ios

I have heard that viewDidLayoutSubviews is the best place to alter the layout when we have used constraints.
So I jumped to viewDidLayoutSubviews
I have created three UIViews and SubViewed them on SuperView. I am not mentioning the code for frame calculation.
[self.view addSubview:imgMiddleCircle];
[self.view addSubview:imgFirstCircle];
[self.view addSubview:imgLastCircle];
Using this piece of I am adding these circles.
Now when I run my code In viewDidLayoutSubviews I get following screens:
And when I switch to viewWillLayoutSubviews I am getting this on screen:
Why I am getting extra two circles in viewDidLayoutSubviews even I am creating three circles.
And why in viewWillLayout gives the correct Output.

You should code for the fact that viewDidLayoutSubviews is called multiple types.
Ideally addSubview: should be happening in a place like viewDidLoad where you are sure it is only called once.
You can create a flag to avoid calling addSubview: multiples types (not my personal choice)
Otherwise, try to move your set up code to viewDidLoad and force the view to render itself before doing your calculation:
[yourView setNeedsLayout];
[yourView layoutIfNeeded];

Per Apple Documentation,
When the bounds change for a view controller's view, the view
adjusts the positions of its subviews and then the system calls this
method. However, this method being called does not indicate that the
individual layouts of the view's subviews have been adjusted. Each
subview is responsible for adjusting its own layout.
Your view controller can override this method to make changes after
the view lays out its subviews. The default implementation of this
method does nothing.
So, essentially, viewDidLayoutSubiews gets called multiple times during the creation of your viewController including in cases like rotating the device, scrolling etc. So, you should be really careful with the code you add to this method, because it might be executed multiple times as well. Use this method to reposition your sub-views and not to add/remove them.
Take a look at this blog for more details.

Related

Why viewDidLayoutSubviews is called twice only on initialize?

I am going to use it by adding a child VC. (Coding with code)
ChildVC is using autolayout (snapkit).
At initialization, viewDidLayoutSubviews is called twice.
At the first call, the childVC.View is set to full screen Frame.
In the second call, it is set according to the autolayout setting.
I found out that if the child VC's Frame is not set, it is set to full screen.
(Resize View From NIB)
I want to know how to turn off Resize View From NIB programmatically.
The problem is, I want to know why viewDidLayoutSubviews is called twice.
I'm curious as to why the childVC.View size changes when the second call is made.
I want to know one more thing. Isn't this usually used when setting subView's witdh and Height?
someView.snp.makeConstraints { make in
make.top.left.right.equalToSuperview()
make.width.equalTo(view.bounds.width)
make.height.equalTo(view.bounds.height * 0.58)
}
Based on VC.View bounds ratio?

How would a textView change its frame without calling setFrame

I'm currently debugging an issue in our app with a UITextView getting its last few lines cut off. The UITextView is inside of a collectionView cell that allows itself to be collapsed. I'm trying to catch the execution of the hiding of the text when its frame changes, so I set a symbolic breakpoint on -[UITextView setFrame:], and this will pause a ton of times during setup of the collectionView as expected.
However, when I hit the minimize button, the textView's frame somehow changes it's origin and height, but I never get a call to setFrame: (or at least my breakpoint never gets hit.) I also never get a call to setFrame: when I expand it, when again the origin and height changes.
The only code I call which manipulates the textView directly is hiding and unhiding it. After hiding, I then call the following:
- (void)updateCollection {
[self.collection performBatchUpdates:nil completion:nil];
[self.amountCell updateTable];
[self.collection setNeedsLayout];
[self.collection layoutIfNeeded];
}
The amountCell is unrelated to the cell I'm dealing with, so I don't think it has anything to do with this. Somehow, between hiding the UITextView and then calling a few updating methods on the collectionView, the frame changes without actaully calling setFrame:. How is this possible? How else could the frame be manipulated? Am I missing something obvious here?
One place you might look is in the collection view's -layoutSubviews method, which will be called by the framework whenever it determines that the layout needs to be updated, which you are hinting that it does when you invoke -setNeedsLayout and -layoutIfNeeded. It's an often-overlooked "hidden" influencer, especially with later versions of iOS shifting focus more to view controllers and away from views.

Animating UIView based on as-yet undetermined layout

I have a custom view, CustomView, that can animate itself in response to an animate message:
// in a view controller
[self.customView animate];
Inside -[CustomView animate], the logic is as follows:
- (void)animate {
for each subview {
startFrame = subview.frame;
endFrame = EndFrameFromStartFrame(startFrame);
subview.animation = AnimationLoopBetween(startFrame, endFrame);
}
}
* This block is pseudocode.
That is to say, the animate method examines the current frame of each subview and generates an animation ending frame for each subview which is essentially a frame which is a fixed ratio larger than the current frame. Then, it creates one animation per subview that varies the frame of its subview, alternating between the current (or start) frame and the end frame, calculated earlier.
This method works very well in general. It allows CustomView to animate properly regardless of how its superview positions and sizes it.
However, in a few cases, "users" of CustomView (that is, view controllers) call animate very early in their lifetime – so early, in fact, that Auto Layout has not yet made any constraint or layout passes. This means that animate is called when all of CustomView's subview frames are CGRectZero. As you can probably guess, this causes both the starting and ending values of the animations to be calculated incorrectly.
Even after the layout pass is complete, and the views' model layers have the proper frames, the CustomView instance is still not visible, because its subviews are happily animating from one zero-rect to another. Not good.
How should I restructure this animation behavior to allow users of this class to call animate at any time without negative repercussions?
Here are some possible answers I've considered, and why they don't seem satisfactory to me:
In any view controller that uses CustomView, put off the call to animate until viewDidLayoutSubviews.
This forces an unwieldy restriction on view controllers that use this view.
In CustomView's layoutSubviews method, first call [super layoutSubviews], then restart the animation if it was already running.
An interesting idea, but CustomView has more than one level of subviews. Thus, even after [super layoutSubviews], many of the views further down the view hierarchy will not yet have valid frames, meaning animate will still misbehave.
In CustomView's layoutSubviews method, first call [super layoutSubviews], then manually layout any required views' frames using [<view> layoutIfNeeded], before finally restarting the animation if it was already running.
This might actually work, but doesn't seem like a good idea. As much as possible, I'd like to leave layout work to Auto Layout.
Why not just skip the uninteresting case?
- (void)animate {
if (CGRectEqualToRect(self.frame, CGRectZero) return;
// ...animate.
}

iOS - viewDidLayoutSubviews called before auto-layout completed on iOS7

We're currently having a problem that only seems to affect iOS7 devices.
Within our .xib file we have two views within a container view (i.e.: not at the top level of the view hierarchy) that need to be circular on display. The views have constraints applied to their position and horizontal spacing within the container, and an aspect ratio condition requiring they are square. The views should expand in width/height on larger screen sizes, respecting the constraints described.
In our VC, we have the following in viewDidLayoutSubviews to force these views to appear circular:
- (void)viewDidLayoutSubviews {
self.progressContentContainerView.layer.cornerRadius = self.progressContentContainerView.frame.size.width/2;
}
This seems to work fine on iOS8, however on iOS7 there is a period after the view has been displayed where the constraints have not yet been applied and the size of the view/views is incorrect (see attached screenshots). This resolves itself and correctly renders a circle after half a second. This only appears to happen when the views that we intend to be circular are NOT at the top level of the VC's view hierarchy which seems to imply that viewDidLayoutSubviews is called before the subviews of subviews have also been laid out.
My guess is that we could potentially fix this issue by subclassing UIView for the nested container, adding references to the circular view within this subclass and overriding viewDidLayoutSubviews here to make the cornerRadius adjustment. This seems like a bit of a workaround though and I'm interested to see if there are other options.
Is there a cleaner/more idiomatic solution to this problem?
I know this is an old question but have you tried calling either:
[self.progressContentContainerView setNeedsUpdateConstraints];
or:
[self.progressContentContainerView layoutIfNeeded];

What conditions can prevent layoutSubviews from being called after setNeedsLayout?

The problem
In a custom view of mine (a UIScrollView subclass) I am calling setNeedsLayout in response to a "reload data" event (triggered by an external source). Most of the time this works correctly and layoutSubviews is called when the next view layout cycle occurs. Sometimes, however, layoutSubviews is not called! Up until now I was living with the "certain knowledge" that setNeedsLayout always triggers layoutSubviews. Apparently I was wrong. I even tried calling layoutIfNeeded after setNeedsLayout, but still no success.
The question
Obviously, I would like to solve my particular problem. On the other hand, I would like to improve my understanding of the view layout process on iOS, so I am formulating my question in a general way: Do you know of any conditions that can prevent layoutSubviews from being called after setNeedsLayout has been called? Answers that focus on UIScrollView are quite welcome, since that is where I am having trouble.
Problem context
I am on iOS 7.1, using Xcode 5.1.1. Notes on the implementation of my custom scroll view:
The scroll view has a single container view of type UIView that is always the same size as the scroll view content size
The scroll view's layoutSubviews implementation places custom subviews into the container view by manually calculating the subviews' frames
The custom subviews' implementation uses Auto Layout
Here is how the reloadData implementation looks like:
- (void) reloadData
{
// Iterates through an internal array that holds the subviews,
// then empties the array. Subviews are deallocated at this
// point.
[self removeAllSubviewsFromContainerview];
self.contentOffset = CGPointMake(0, 0);
// I verified that the content size is always greater than
// CGSizeZero (both width and height)
CGFloat contentWidth = [self calculateNewContentWidth];
self.contentSize = CGSizeMake(contentWidth, self.frame.size.height);
// Here is the problem: Sometimes this triggers layoutSubviews,
// sometimes it does not.
[self setNeedsLayout];
// Adding the following line for debugging purposes does not
// help, making it clear that setNeedsLayout has no effect.
// [self layoutIfNeeded];
}
Last but not least, here are some observations I made:
layoutSubviews is called as expected if the content offset or content size change. layoutSubviews is not called in my particular case if these two values don't change. I first assumed that this observation is a general working principle of UIScrollView, i.e. that layoutSubviews is generally not called for UIScrollView unless content offset or content size change. However, when I wrote a couple of minimal projects I failed to reproduce this assumed "general" behaviour - in those projects layoutSubviews was always called as expected, regardless of whether content offset or content size changed or not.
layoutSubviews is always called as expected if the scroll view displays dummy UILabel instances instead of my own custom subviews
layoutSubviews is always called as expected if I use my own custom subviews, but replace Auto Layout in the custom subviews' implementation by manual frame calculations
The last two observations have led me to believe that Auto Layout is somehow involved, but I really have no idea how this could affect how setNeedsLayout works.

Resources