What is the proper way to make a fluid layout in iOS, in the sense that hidden elements do not take up space anymore?
I have a table view with in each cell a customized detail-type of view with title, subtitle and a row with some extra information:
The extra information can be up to three pairs of an icon and a label with a value. The layout of all views inside the cell is done using AutoLayout with no missing or ambiguous constraints.
What I would like to achieve is that when the value is 0, the icon and the label are not displayed and the views on the right are shifted to the left.
If I just use the setHidden: method, the width of the hidden parts are not changed, so that there is just whitespace, but no views are moved. Example:
It should look like this:
The following questions are related but do not seem to fit my case:
Fluid UI layout on iPhone
AutoLayout with hidden UIViews?
I have tried to follow the approach with creating layout constraints for the four frames that need to be set to zero: the width of the heart-shaped icon, the width of the label containing the value, the whitespace in between those and the whitespace between the label and the next icon. This did not work because I could not bind the layout constraints to the outlet in the code, and besides it seems a cumbersome method for something that should be a common scenario.
EDIT: I fixed the problem with the outlets to constraints: to do this it is necessary to create a subclass for the table cell and creating outlets for the constraints there.
With "common scenario" I refer to doing something similar in web design, where setting the display style to none is simple and has the desired effect. I expect that there is something similarly simple for this in iOS.
I have been thinking of using a collection view with reusable cells, but then I need to set up a delegate and a datasource and everything, and before I would go this way I wanted to make sure that that is the way to do it.
There is no need to remove a hidden view. Connect the constraint to an outlet in the code, and when you determine a view is hidden, subtract from the constraint's constant. Then, in the cell's prepareForReuse, remember to return the constraint's constant to the correct value.
Hidden views maintain their frame, so auto layout will have no reason to adjust the view. The correct way to do this would be to remove the views from the superview. The last thing you must do is double check the constraints. Since you will be removing views, you cannot use those views for auto layout. This will require quite a bit of constraint setting on your UI.
Related
I've been trying for days to make one layout of my app to work well, and after days of learning and mistakes I still can't get the table cell layout to look how I want it to be.
This is how my cell .xib looks like in the editor:
http://i.stack.imgur.com/VEr3r.png
And this is how my app looks like when running with suggested constraints:
http://i.stack.imgur.com/wiK1f.png
Why is that? How I can find my mistake and make the layout like it supposed to be, in the view?
Suggested constraints are rarely what I actually wanted to see.
For each label with fixed text, lock the horizontal and vertical positions either to the view or to the next adjacent item.
For imageViews, choose the size you want and lock the height and width.
For labels that you will be dynamically changing the text on, pick a size that will hold the longest string and lock the width. You'll need a vertical position constraint.
You have many options to simplify your layout.
you can use the stakview or create a static UITableView and insert your component inside the cells and enter the Constraint. excellent tutorial http://www.runtimecrash.com/2015/09/17/exploring-uistackview/
I have a group of text labels that are vertically spaced to each other 15 pixels.
The problem is that when one of the labels is empty, then there is extra space between the other two labels above and below the empty one (30 pixels).
I know that one solution would be to constraint all the text labels to the top and to the labels above and then I can just delete it and everything should look aligned but the problem is that I reuse the view and sometimes all the labels have text and sometimes some are empty. So if I delete the label, I would have to recreate it and readjust the constraints manually.
Is there a way to delete the extra vertical spacing when one of the labels is empty without deleting it?
Edit 1: The labels don't have any Height constraint so the empty one will be 0 pixels high
Edit 2: I need to support iOS7+
Possible options:
do not use multiple labels, but a single one, possibly using an attributed string if you need different formatting for the different parts
add an outlet to each of your constraints, and adjust the constant based on the label having text or not
There are quite a few others, including the use of table views, stack views (iOS 9+), and probably more...
If you are ok with supporting iOS 9 and above then you can use stack view and set the constraints for the labels within the stack view. But instead of emptying the label you should hide the label. When the label is hidden the stack view automatically brings up all the labels below it. You should get the desired behaviour using this method.
My rule of thumb is if layouts get overly complicated to do in Interface Builder, then it's better to just write code to do it. In this case I'm not even sure it's possible to define in IB. But even if it was, I'd do it in code. It's not a complicated layout, it will be more reusable, it will be cleaner in code.
A few options:
A - You could modify constraints in viewWillLayoutSubviews - it's kinda messy to hold on to so many top constraints. Somewhat less messy if you add them all to an outlet collection (array of outlets, basically).
B - You could manually adjust frames in viewDidLayoutSubviews - although it then begs the question, why even use AutoLayout at all if you're doing almost all the work manually anyway.
C - Use SnapKit and generate those constraints in viewDidLoad and / or update them whenever the text changes. I highly recommend SnapKit. Think of it as a sane way of programmatically creating AutoLayout constraints. It's very clean, and very simple.
See http://snapkit.io
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.
I am making a form based UIScrollView, which will contain some labels and text fields.
My ScrollView Height will increase as per the iOS device height.
PS: I do not want to add constraint to each and every element of the Scrollview, because in my case there could be 100 form fields.
What I want is, the inner content to fully occupy my scrollView like this:
Till now there a are no special constraints, the button is tagged with the bottom edge and the scroll view is pinned from the top edge. Also, the vertical spacing between scrollview and button is defined.
This is the autolayout constraint screenshot.
If the number of labels is variable, I recommend doing them in code, rather than in Interface Builder.
In code, you can use a loop to set every label to have the same width/height as the one above it. You may want to set their height to be >= a minimum value. Be sure to anchor the first label with the top, and the last label with the bottom.
But this can be cumbersome, why not just use a UITableView? you may modify the row height to let the cell fully occupy the view.
- tableView:heightForRowAtIndexPath:
- tableView:estimatedHeightForRowAtIndexPath:
I just face the same issue and already write a pod called TLFormView that do exactly that: a form based on UIScrollView.
It also has some nice features like:
declarative form taxonomy with TLFormModel Just extend it, add the properties you want and that's it. No delegate, no event handling, no boilerplate.
a nice way to handle different layouts for iPhone and iPad
conditional visibility with an NSPredicate that can access all the values in the other fields (e.g: show field A when field B has certain value)
in place help with a popover
all the fields are TLFormFields that extend UIView so you can place whatever you need.
You can try it right from the command line with pod try TLFormView.
If you want to know more I wrote some blog post about it here.
Please let me know your thoughts about it here or as a comment in the blog posts. Also any contribution is extremely welcome in the GitHub repo
I have been struggling with IB and auto layout and figure there must be an easier way to create a simple scrollable form with multiple fields on it. The width can adjust to the device screen width (or in the case of the iPad to the details view of a split view.
The basic layout is simple a label and underneath a field (in some cases the field must have a minimum height) repeated for each attribute.
I think I must be doing something completely stupid or IB's autolayout is hopeless, I suspect the former. In any event whatever I do in IB fields just adopt whatever constraints they feel like it seems. Even when I set a constrain it seem IB just replaces it sometimes.
Currently I am using a view with a fixed height and width that fits the screen and laying out fields on this form, however its a complete nightmare having to pin every field in almost every direction. And then IB complains about a million constraints that it can't satisfy.
Hopefully someone has a better way of doing this they are willing to share - perhaps it might be best just to write some code to set up the constraints rather than relying on IB ?
Thanks
You are right UIScrollView and autolayout is not clear at first look.
You have to understand that when you are adding layout constraints for a view inside scroll view, the superview is content view of scroll (not the view of the UIScrollView) that can be more or less then current view of the ScrollView in IB.