iOS custom UIView design: init vs layoutSubviews - ios

I have a number of custom UIViews.
I constantly find myself initializing properties in the init of my custom view, but I also set the frame there too.
I usually leave my layoutSubviews empty. If I don't expect my view bounds to change, is it ok to have my various subview frames set in the init itself, or should I move that to layoutSubviews?
I wanted to mention that one of the reasons, I do it this way is because often I find myself having to calculate the frame (size) of my custom view based on how my subviews get laid out.
I usually resize my custom view's frame after all of my subview frame sizes have been set.

You should avoid allocating/creating your views in layoutSubviews method, because it will be called a lot of times. You can allocate your views in your initializer methods and layout them in layoutSubviews method. But if your views' frames are non-relative to your view's bounds, then there is nothing to worry about setting their frames in your initializer methods.

Related

iOS incorrect frame size at runtime

I've got an UIImageView inside a root view of a controller, and I've set it to be 90% of the screen width and given it an aspect ratio as constraints to set the dimensions.
In the code I'm trying to do something with respect to the size of the UIImageView at runtime, however when I get the frame.size.height or frame.size.width of the UIImageView they are clearly wrong and way too small.
At first I was accessing the size in viewDidLoad(), after which I found quite a few posts suggesting to do it either in viewWillLayoutSubviews(), viewDidLayoutSubviews(), or viewWillAppear(). Unfortunately I've tried all of those and none of these contexts seem to provide the right value. I suspect it may have something to do with auto layout but I'm not quite sure how to get around this. Any insight as to why this may be would be appreciated
viewDidLoad is too early. At this time, the views have the frames they were given in the storyboard. Ditto for viewWillAppear.
In viewWillLayoutSubviews, the view controller's top-level view has its correct frame, but its descendants do not.
In viewDidLayoutSubviews, the view controller's immediate subviews have their correct frames, but more distant descendants (“grandchildren” and so forth) don't.
If the image view is a direct subview of the view controller's view, then its frame is up to date in viewDidLayoutSubviews.
If the image view is a more distant descendant, then there is no method you can override in the view controller that will be called after the image view's frame has been updated but before the image view is visible on screen. Here are two options in this case:
Create a custom subclass of UIView to be the superview of the image view. When the superview's layoutSubviews runs, after it calls super.layoutSubviews, the image view's frame is up to date.
Create a hidden UIView that is a direct subview of the view controller's top-level view. Use constraints to make this hidden view's frame exactly match the image view's frame. Hidden views participate in layout, so when viewDidLayoutSubviews is called, this hidden view's frame is up to date, and is the same as the image view's frame will eventually be (except that the hidden view's frame is in the top-level view's geometry, which might be different than the geometry of the image view's superview).

iOS: Adding new constraints in a custom view the rely on the view's frame

I'm trying to understand when to override layoutSubviews versus updateConstraints.
I have created a custom view, and that view contains other views. I can't set the constraints for the view's subviews in the custom initializer I have as I don't know the custom view's frame yet.
Currently I have this:
-(void)layoutSubviews
{
[super layoutSubviews];
// Add new constraints
}
Then whenever I add or remove any of the subviews, I call [self setNeedsLayout];
Is this the correct way to do this? I am currently recreating the constraints whenever layoutSubviews is called. I've heard that updateConstraints might be what I want? But I'm not sure as the number of subviews does not remain constant, and thus, the number of constraints will not be constant either.
You should add constraint when you add the subviews. You may do this for each subview individually, or in a custom method if that can be generalized.
The methods layoutSubvews and updateConstraints are callbacks, and they are called at specific points in the layout cycle. Especially, they are called multiple times, and that automatically prohibits you from using constraints, because there is a danger of creating constraints more than once.
As the documentation stated, layoutSubviews should only be used when
if the autoresizing and constraint-based behaviors of the subviews do not offer the behavior you want.
If layoutSubviews is called, the current automatic layout is done, and adding, removing or modifying constraints might re-trigger the layout process. This might slow-down your app extremely.
The documentation for updateConstraints includes also a clear statement when you should overwrite it:
Override this method to optimize changes to your constraints.
Changes to constraints doesn't mean adding constraints. If you need different constraints for your views for different times, you may deactivate unused and activate needed constraints at runtime. This could be done in updateConstraints.
BTW: The (explicit) call of setNeedsLayout is usually unnecessary, especially if you have changed the view hierarchy or the layout constraints.

Need assistance regarding viewDidLayoutSubviews

Apple doc regarding viewDidLayoutSubviews says:
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.
I have view1 which contains view2 means view2 is a subview of view1. I am creating a CALayer for my view2 which I want to be exact size of view2. I am using auto layouts so I want to create the CALayer when auto layout finish its work so I can have correct values to set for CALayer frame.
But in viewDidLayoutSubviews method I can't detect when exactly view2 frame is set by auto layout because Apple doc is also saying that
this method being called does not indicate that the individual
layouts of the view's subviews have been adjusted.
after that Apple doc saying
Your view controller can override this method to make changes after
the view lays out its subviews.
I am so confused at what point auto layout will set view2 frame so I can set my CALayer frame accordingly.
As I mentioned in the comments, you don't have to do anything special if your view is aligned with auto layout. If you want a single layer to adjust its bounds to the view's bounds, the most simple and correct way will be overriding layerClass method of the view - in that case layer bounds will be adjusted within a view automatically. If you need one more layer in addition to the standard one, the best place to update layer's bounds may be setFrame: method override.
As of viewDidLayoutSubviews, it is just an event to inform you, that all subviews of viewcontroller's root view are positioned at the desired places. You are not guaranteed that all the rest subviews of those subviews will also be aligned, since it is a responsibility of the parent view itself. they can be both auto layout positioned and manual layout positioned. This event also does not guarantee that all the the views are displayed as desired, especially if their frame change is animated.

how to make a programmatically generated subview conform to the bounds of the superview (iOS)?

I have a containing view called containerView which is a UIView. When the app starts, it's just that view. During the course of the app's execution, I want to swap two "full-size" sub views in and out of the main containerView.
My question is, how do I make sure that the sub views fill up the entire containerView regardless of the orientation of the iPad. The containerView is 300 wide, but the height varies based on orientation.
I've tried:
setting the frame of the subview's from viewDidLoad, viewWillAppear, viewDidAppear directly equal to the side of the containerView frame,
creating LayoutConstraints that force the subview to conform to the containerView's proportions from viewDidLoad, viewWillAppear, and viewDidAppear. The question here is when to apply these constraints, and since the view is removed periodically, when to reapply the constraints.
I don't know what the idiomatic approach is and I want my code to be maintainable and reusable. Am i approaching this the wrong way? Is there some other way to make sure a subview fills up its containing view?
Example Code:
https://gist.github.com/Sahasrara/6817105
You should set the frame size in viewDidLayoutSubviews. By then all the autolayout nonsense has occurred.

Don't know the final frame size of UIView until after drawRect?

I'm drawing a grid of data in a UIView with drawRect, of which I won't know the final size when the UIView is created because the number of columns and rows is dynamic. Sure I could do the calculations before creating the UIView, but that doesn't really make sense, because I'll also be doing those calculations in the UIView subclass, and would rather not have to extract that.
So how do I handle this? Do I init with a very large frame and adjust it after drawRect is done?
I will also be setting this view as the content of a UIScrollView in case its too large to be viewed in the area allotted for it.
The view I'm going to be drawing looks something like this:
Perhaps others have another solution but I think that in your case I would override setFrame: and call setNeedsDisplay so that drawRect will be called after your frame changes. Have you tried this approach already?
Add a method in your view that determines how large it should be, based on its data. This needs to be separate from drawRect:.
In your view controller, when you're setting up the view, call that method to get that size.
Then set your view's bounds and/or frame to that size.
And also set the scroll view's contentSize to that size.
The key concept is: Separate the ideas of "determine how large my view's stuff is" and "draw my view's stuff". Right now, you are doing the size computation while you draw, but you need to be able to do it earlier.
If you want to get fancy, you could look into overriding layoutSubviews in a superview of your view -- that would be a good place to check if the view's desired size has changed, and then update the view's size and the scroll view's contentSize. But it isn't necessary to do that to start.

Resources