addSubview breaking internal contraints - ios

I'm having some trouble with dynamically adding subviews to each other. Namely, when I have a view with constraints built in the interface builder and then use [view addSubview:subview], it destroys all of the constraints that I have set up.
I've tried adding [subview setAutoresizingMask:NO]; as well as subview.translatesAutoresizingMaskIntoConstraints = false;
I expected that the subview's constraints to its original parent would be broken, and added them back in programmatically after my call to addSubview, but I didn't expect that the constraints of its children would also be broken.
Unfortunately, adding back in child constraints is not feasible. The architecture we're using is a plugin model, and while it's simple enough to resize the plugin's container from the main UIViewController, the plugin needs to be responsible for positioning its internal elements.
Here's a mock up of how things should look:
And here's what it currently looks like:
How can I get addSubview to maintain those internal constraints? The red, green, and blue boxes are only constrained to each other and their parent (purple), which are all brought through with addSubview.

I tried too many ways includes sizeToFit()and setting the translatesAutoresizingMaskIntoConstraints but it did not work. You should set width of the custom view which you want to add as subview and you should set the view's sizeToFit(). it worked for me:
bigView.sizeToFit()
subView.sizeToFit()
subView.width = screenWidth
bigView.addSubview(subView)

Related

How do I prepare a UIViewController's view for being added to a UIStackView?

This is what I'm trying to do...
I have one view controller that needs to dynamically display different subviews based on the presence of some data.
Here is a simple mockup. Each colored block represents a unique subview.
Sometimes the green block needs to be at the top, sometimes the green block won't display at all, sometimes the light blue block will be something different, etc.
Each subview has interactive elements, so I've been creating and adding them like so:
Defining a new view controller
Defining its view
Calling addChildViewController and didMoveToParentViewController
Calling addSubview on myNewViewController.view
Using SnapKit to make auto layout constraints to position the view
I want to transition to UIStackView because it seems a good support system for this view because all I need to do is stack its subviews. I'm seeing many conflicting constraint errors and unexpected view frames when trying to add subviews with their own inner auto layout constraints.
Question
Am I setting myself up for failure here by embedding the views of 4-6 view controllers in the view of one view controller?
Also, how do I give the added views properties like minimum heights or content sizes without seeing many breaking constraints with UIStackView? (So they can stack, but one of them is say, 400 tall, and the other is 200 tall)
Answer
You can absolutely do this using UIContainerViews combined with UIStackViews and UIScrollViews, it's a complicated setup so here's a starter project to get you started:
https://github.com/Rnorback/ScrollingStackView
Here's what that project looks like:
You want the containers to have different sizes. In order to do that, simply add height constraints to the containers with Autolayout. If you want to change the height of the scrolling. Then you'll need to change the height constraint of the UIStackView.
Brief Explanation
When creating this setup, you have to make sure the UIStackView distribution setting stays in fill. That way you can set height constraints on UIContainerViews.
When setting up anything in a UIScrollView you have to make sure that the object is pinned to the edges of the scroll view and has a defined width and height, otherwise the scrollview will throw a constriant error. I think of it like the scrollview tries to press in on all sides of your content view and if any side gives, the scrollview won't be able to present it properly.
Hope this helps.

UIView added with autolayout does not receive taps/events

When I add a subview with autolayout by settings its left, right, top, and height constraints, I cannot add a tap gesture and my buttons do not receive taps.
When I manually set the frame to the position I need and do not use autolayout, I can add gesture recognizers and my buttons are interactive.
Why would a view added with autolayout not respond to events?
I would like to use autolayout since I would like to respond to different orientations and sizes that are more complex than I would like to deal with with manual frame adjustment.
This can happen if you have restricted your view too much using autolayout. For example, you may have accidentally set the height of the view to zero or positioned it outside the area where you put your buttons. Note that the buttons themselves can be positioned in a place that appears correct when you look at them, but they are actually outside the area that the autolayout engine has calculated for the parent view.
Another thing that can hit you, is that if you added all your autolayout contraints programmatically, some contraints in the Interface Builder may still be enabled. You can make sure that IB contraints are ignored by setting view.translatesAutoresizingMaskIntoConstraints = NO;
Also, pay attention to any runtime autolayout warnings in the console window, they will often tell you if some contraints don't play well together.

ScrollView With AutoLayout

I have a weird output with autoLayout used for following scenario.
I have 4 sub pages to scroll. (subPage or scrollPage has been designed separately with autoLayout).
MainView has a scrollView component which loads the sub pages.
Everything is fine except the starting. After first load, the sub page components are not arranged properly. As soon as it receives a first tap/touch, automatically re-scrolls/re-arranges to proper places. which looks like a bug.
The loading creates problem. I have attached 2 images for reference for the above scenarios.
FirstOne at the first loading
Second one just after tapping on scroll area
Second one is the proper one. I need to show this instead of the first.
Need help to fix this.
Thanks,
Satyaranjan
One more thing to note,
[myScroll scrollRectToVisible:CGRectMake(320*pageNumber, 0, 320 , 240) animated:NO];
this is not working As I am using dynamic width for the subPage. Because it will vary for iPhon5 and iPhone6
First, you generally don't want to set the scrollView's contentSize when using autolayout -- if your sub views are laid out correctly, it will do that automatically.
Try this:
constrain all four of scrollView's edges to its parent
make sure translatesAutoresizingMaskIntoConstraints is NO for all views
Create a view called "contentView" and parent it to the scrollView.
pin all four of contentViews's edges to scrollView
parent your subviews to contentView and arrange them with autolayout
If for some reason you need to change the size of a subview programmaticly, you need to override its intrinsicContentSize method to return the correct size after the change. After the change, you might need to call the view's sizeToFit method from its parent view (not sure about that -- it may happen automatically).
In general, when using autolayout, you should almost never explicitly set the size of anything. If it can't be avoided, you should do it by creating height/width constraints and modifying them at runtime in the updateConstraints method.
EDIT:
I made an example project which demonstrates how to set up a scrollView and a couple other things.
Take a look!
https://github.com/annabd351/AutolayoutTemplate

Temporarily remove NSLayoutConstraint?

I have a UITableViewCell subclass laid out using AutoLayout, and I’d like a small animation in which the image view transforms, grows a bit and shrinks back.
At present, due to the constraints, the labels on the right side of the image are moving too (as they should be).
I’d like a quick and easy means to temporarily say “leave those labels where they currently are while this animation is running”. Is that something I can do without needing to remove and re-add those constraints, which is a lot of hassle?
Starting in iOS 8 NSLayoutConstraints now have an active property that you can change myConstraint.active = NO;. This removes the constraint, while setting this value to YES will [re]add it:
Activating or deactivating the constraint calls addConstraint: and
removeConstraint: on the view that is the closest common ancestor of
the items managed by this constraint. Use this property instead of
calling addConstraint: or removeConstraint: directly.
source
Additionally, there are two new class methods activateConstraints: and deactivateConstraints: that take an array of constraints (NSArray<NSLayoutConstraint>), so that can be helpful, especially if you have an IBOutletCollection with constraints.
[NSLayoutConstraint deactivateConstraints:myConstraints];
There's no API to say "leave these views here", temporarily disabling some parts of the autolayout. There are several possible solutions:
remove your constraints during animation
arrange your constraints differently (making them to a superview of the image view, for example, or having them only depend on the center of the image view)
override layoutSubviews of a container view to stick your labels back where you want them after autolayout runs in [super layoutSubviews] (or remove the transform of your image view, run autolayout and then put it back)
subclass your image view and override frameForAlignmentRect: and alignmentRectForFrame: to get autolayout to use the untransformed frame for alignment purposes.
More details and suggestions are available in the answers here, which is a similar question of how to make transforms and autolayout play together: How do I adjust the anchor point of a CALayer, when Auto Layout is being used?

iOS AutoLayout - when does it Run?

I have a XIB file where I specify constraints at design time.
In addition to controls with constraints, I also have controls that have no constraints in the XIB that I want to position programmatically at run time.
I do this by repositioning views manually in didRotateFromInterfaceOrientation.
When I rotate the device, my code places the controls that are manually controlled but then later it appears that auto layout messes up the placement of manual controls that have no constraints on them in the Interface builder.
Question - When exactly does Auto layout run if I rotate the device.
Auto Layout runs at the end of the run loop after setNeedsLayout is called on a view, or immediately when setNeedsLayout is followed by layoutIfNeeded. Note that there are many methods that may call setNeedsLayout in their implementation, such as addSubview:, removeFromSuperview, etc.
A couple suggestions:
Instead of updating your constraints in didRotateFromInterfaceOrientation:, try doing so in updateViewConstraints.
You may also want to add placeholder constraints in Interface Builder to the views you will position programmatically. I think IB will automatically constrain views to their x/y position if a placeholder constraint is not set, but I'm not 100% sure on that.
The important thing to know is that the results of constraint calculations are applied in layoutSubviews. So if you want to set frames, you do so after calling super in layoutSubviews or in your view controller's viewDidLayoutSubviews.
Having said that, I've only experimented with this. I've not done it in production code and can't say that you won't run into issues.
I'm actually not clear on whether it's OK to set frames manually like this. The only relevant bit of information I've come across is the following from Apple's Auto Layout documentation:
You cannot set a constraint to cross the view hierarchy if the
hierarchy includes a view that sets the frames of subviews manually in
a custom implementation for the layoutSubviews method on UIView (or
the layout method on NSView).
Here, the phrase "view that sets the frames of subviews manually" implies to me that manually setting frames is OK. I'd be interested to know if anyone has a see a more explicit discussion on this.
you must not change the frame you must connect the constraint with iboulet and in the didRotateFromInterfaceOrientation you can change the constraint like this:
(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation{
if (UIInterfaceOrientationIsLandscape(fromInterfaceOrientation)) {
self.constraintTop.constant = 50;
}else{
self.constraintTop.constant = 100;
}
}

Resources