Autolayout - equal distribution of 6 views - xcode

I want to have 6 objects (buttons) laid out inside one view. They should, however, follow some constraints:
Two top buttons should have the same vertical distance from superview (A)
Two bottom - the same (C)
Two in the middle should have their centers at the superview's center line
The vertical distances between all buttons (E) should be the same
and last but not least - the buttons should be square (so the width and height should be the same)
A = C
B = D
Is it possible to have this effect just in the IB, or should I use some additional code for the constraints?

This is a logical request, but constraints are defined using the attributes of views, but cannot not be defined in relation to other constraints. That having been said, there are a number of approaches:
Layout guides: An approach which doesn't require predetermining the any spacing is to have UILayoutGuide objects or, if using iOS versions before 9, just use hidden views, i.e. views with clear background or alpha of zero, in between the buttons.
The idea is to add these layout guides with addLayoutGuide (or add invisible views with addSubview if supporting iOS versions predating iOS 9) in between your six buttons as "spacers", and define the spacers to be the same size as each other, and with constraints between the spacers, the superview, and the buttons that will go in between the spacer. Once you lay that out (showing the horizontal spacer views in blue, vertical ones in red, just so you can see them):
The equivalent VFL for the constraints for those red UIView objects, called vspacerX, would be:
H:|[vspacer1][button1(100)][vspacer2(==vspacer1)][button2(==button1)][vspacer3(==vspacer1)]|
H:|[vspacer1][button3(==button1)][vspacer2][button4(==button1)][vspacer3]|
H:|[vspacer1][button5(==button1)][vspacer2][button6(==button1)][vspacer3]|
And constraints on the blue UIView objects, called hspacerX, like:
V:|[hspacer1][button1(100)][hspacer2(==hspacer1)][button3(==button1)][hspacer3(==hspacer1)][button5(==button1)][hspacer4(==hspacer1)]|
V:|[hspacer1][button2(==button1)][hspacer2][button4(==button1)][hspacer3][button6(==button1)][hspacer4]|
You don't have to use VFL to define these constraints, as any way you define these constraints will work, but it's just a concise format for describing the collection of constraints that I employed.
Anyway, when the view is rendered with those layout guides (or invisible views), it yields evenly spaced buttons like so:
Another approach is to have six "container" views, that would look like:
The equivalent VFL for these six container UIView objects might look like:
H:|[container1][container2(==container1)]|
H:|[container3(==container1)][container4(==container1)]|
H:|[container5(==container1)][container6(==container1)]|
V:|[container1][container3(==container1)][container5(==container1)]|
V:|[container2(==container1)][container4(==container1)][container6(==container1)]|
You can then add your buttons to that, centering one on each of the six little containers and then make your containers clear:
This works, too, but just a slightly different spacing (where the margins are half of the spacing between the views, whereas the other approach keeps the margins the same as the spacing between them.
Stack view: In a permutation of the prior point, in iOS 9, you can also use UIStackView, designed precisely for evenly spacing views. In this case, put two buttons each in three horizontal stack views, and then place those stack views within a vertical stack view. This achieves six evenly sized container views.
See WWDC 2015 video What's New in Cocoa Touch.
The problem with stack views is that they can be used to ensure even spacing between the arranged subviews, they don't ensure spacing before the first arranged view nor after the last arranged view. So, the kludge to get around that is to, for horizontal stack view, include two more zero width views (or zero height for vertical stack views). Then when you use even spacing on the stack view, it also give you what will appear to be spacing before and after all of the arranged subviews.
NSLayoutAttributeCenterX with multiple: Another technique involves defining the attribute:NSLayoutAttributeCenterX and attribute:NSLayoutAttributeCenterY attributes for your six buttons, but rather than using the constant values, use the multiplier field. This technique enjoys a little simplicity, but doesn't always render the desired effect, so I won't describe it unless it's something you definitely want to pursue. I've already entered tl:dr territory here.
Collection view: Another approach is to use a UICollectionView, which handles this scenario gracefully. It's well designed to let you layout cells in a grid.
Hardcoding values: For the sake of completeness, I'll note that you could simply specify specific values for A, B, C, and D (as well as the width and height constraints). You don't even have to worry about setting the E constraints, but rather just set the vertical center constraint of the middle two to their superview, and you're effectively done (because the spacing represented by E should be a natural result of the previous steps, assuming A=C and B=D). If you want to adjust these values on the basis of device size and/or orientation, you can then implement a viewWillLayoutSubviews to adjust the constants for these constraints according to the size of the view.

Update: I have a better solution that does not use spacers. Check it out here.
Ok, this can be achieved very quickly in IB. It's so so simple. Here's a diagram that will help illustrate.
Assume v1-6 are your buttons, and s1-5 are your spacers.
1) in IB control drag out all of the connections shown by the red lines.
2) shift click v1-6 and pin icon (looks like |-I-| ) set the width and height to a definite value. also, set the height and width to be equal.
3) shift select s1-4 (not 5) and set the height to equal. do not give it a definite height, since this should be calculated by the system. you might also need to set the widths of s1-4 to be equal, but don't give them a definite width.
4) control drag from the centre views to the leading and trailing edge and set the centre constraint.
So, you might think, ok, this should work now. It doesn't. Here's my app running in portrait with slightly different colors. Looks good. (Notice, you would make the spacers invisible once you get it setup).
But when I rotate, oops!
What's happening here? The problem is incredibly easy to solve once we understand what's gone wrong. What we want is for IB to not shrink our views. We want IB to make the spacers and the spaces to shrink and grow as necessary, but to leave our views alone. Basically, IB has shrunk the spacers down as far as it can in portrait and to attempt to make everything fit IB has shrunk our views. But we wanted IB to shrink the vertical spaces between views and spacers, not our views. The solution is so easy. All we have to do is adjust the priority of the vertical spaces and all is well. So, select the vertical spaces in IB and adjust the priority to 750. The vertical spacing lines will show as dashed. Done.
Ok, so here's everything as we expect it.
And with the spacers made clear:

Related

Fast layout algorithms for UI

I have a number of UI elements like panels, edit fields, buttons, labels etc. so panels contain other panels which contain input fields, editors and so on. Most of the elements are editable and/or resizable which means whenever I change anything, a lot of adjacent UI elements should change their width, height and x/y position on the pane. It works fine with a small number of elements but incredibly slow when the number of elements is thousands.
Is there a fast layout algorithm which can be used in this case? Note that I cannot use any existing layout managers and should come up with my own implementation.
I'd suggest taking a leaf out of the Android playbook and have a larger 'grid' and keep everything sized in modular multiples - this avoids you needing to solve the knapsack problem everytime!
For example, instead of having a button with an width of 80 and a height of 40 you store this as metadata as {2:1} (assuming your layout grid is 40^40 squares).
This way if you have a work panel with space of, say, {2:12} this could be filled with two objects of size {2:6} or maybe 3 of size {2:4}.
It's pretty simple to fit-to-max too as any available space can just be scaled up (say you delete a {1:1} item you can just expand the one next to it to take the space etc - you can of course create your own rules around whether objects can scale in single directions etc.
The other advantage of this approach is that you can easily manage different screen sizes and resolutions too while still keeping the same framework and look and feel.

How to make NSStackView truncate right-most NSTextField

I have one line in my UI, that consists of fixed-sized string (FS), fixed-sized image and variable-sized string (VS). I need this line to be centered in its superview, possibly truncating the VS if it no longer fits in. Just like simple centered NSTextField with truncate enabled would do. But NSTextField can't hold an NSImage AFAIK.
I suppose NSStackView should be able to do this. I have set it up to be centered in its superview and have an NSTextField, NSImage and NSTextField inside. But when I set VS to something very long, both FS and VS are pushed out of superview bounds and clipped.
Is there a way to tell NSStackView to never move FS out of superview bounds and rather truncate VS? Or is there any other technique to achieve my goal?
What I have tried so far:
Use NSStackView and set compressionResistance to 499 on the VS - no effect
Use NSTextView - could not make it truncate text and fit on single line. Also seems like an overkill
Use NSView - it requires me to set x-position so it is not properly
centered.
I could use NSView and calculate sizes of the strings and modify constraints to make this work. But I think there must be a better way.
Thanks for any ideas,
J.
It sounds like you haven't constrained the stack view to fit within its container. In addition to the centering constraint, you need to set its leading to be greater than or equal to the leading of the superview and its trailing to be less than or equal to the trailing of its superview.
The compression resistance of FS and the image view should be higher than the compression resistance of VS, and could even be required to prevent those two from ever being compressed.
The horizontal clipping resistance of the stack view should be higher than the compression resistance of VS. It's fine to leave it at required. You want VS to be compressed rather than clipped.
What the compression resistance of VS should actually be depends on the rest of your view hierarchy and constraints. It should presumably be less than 490 (NSLayoutPriorityDragThatCannotResizeWindow) so it doesn't force the window to resize.

Xcode auto alignment and constraints - equal distances between several images

I have nine images ad I want them to display in a single column on the device. The images should retain their original height and width (that I will set) and the only thing that should change according to the screen height should be the distance between the images.
I'm not really sure how to approach this problem - I tried using a container, but it didn't turn out as I expected. Should I set the constraints programmatically? And if so, how? What should the constraints be if I want the images to be from top to bottom of he screen?
Consider using a stack view:
On iOS, UIStackView provides UIStackViewDistributionEqualSpacing, which I believe will arrange images as you want
On Mac, NSStackView can automatically space images as you describe if the individual image views are all in the same gravity and retain their sizes at a high enough priority (using constraints or content hugging priority)

JavaFX's version of WPF's UniformGrid?

I'm starting with JavaFX on a new application and I want to get a layout on screen identical to WPF's UniformGrid. There's TilePane but it's different (similar to WrapPanel I guess).
My approach so far would be (for a collection [size = N] I want to display as "tiles"):
Use a TilePane t.
t.setPrefRows( ROUNDUP( sqrt(N)))
That's probably not the best approach.
Do you know a better solution? Maybe a resizable one?
It sounds like you want a layout which:
Has a fixed number of cell nodes in each row.
Each cell node has the same height and width.
I'm not familiar with WPF or UniformGrid, but I think the above is what you want.
TilePane likely isn't a Good Fit
It seems like TilePane would be a good fit for this, but, as you found out, it doesn't really exhibit this behaviour out of the box. With a TilePane, you set a preferred number of rows, but as you resize the TilePane the number of rows and the number of columns can change as the tiles are rearranged to fit the available area.
Use a GridPane with Binding and Preference Settings or Constraints
To get a fixed number of cells per row, use a GridPane. A GridPane will keep a fixed number of rows and columns as it resizes. To ensure that each cell node has the same height and width, you can listen to the height and width properties of the GridPane and, on change, set the min/max/preferred sizes of the child elements so that they all have the same size. Or set some constraints on the GridPane rows and columns such as setPercentWidth and setPercentHeight.
To demonstrate this approach, the ColorChooser sample provides some code for a re-sizable ColorChooser with a fixed number of color swatches per grid row and as the overall containing grid grows or shrinks, the color swatches are grown and shrunk to fit the available area as appropriate. The color swatches themselves don't necessarily maintain the same height/width ratio, but you could easily achieve that by setting appropriate min/max/preferred sizes on the child elements.
Create Your Own Layout
As an alternative to using change listeners and setting min/max/preferred sizes on children, you could implement your own layout manager by subclassing Pane to create a UniformGrid class and implementing the layoutChildren method. In layoutChildren, arrange the size and position of UniformGrid children as needed. You can refer to the source code of an existing Pane to find an example of how to create your own layouts.
Investigate 3rd party Layouts
The ControlsFX project has a GridView or MigLayout which might provide the functionality of a UniformGrid.

I have a table view and I want to spread table cells with a gap

I want to load a table view with cells. In between cells I should able to see minimum cell gap. How can I increase existing table view cell distance in my navigation controller root view?
You can't increase cell distance, but what you can do is subclass UITableViewCell, override -layoutSubviews, and reposition all the internal views to allow for a bit of padding on one (or both) end(s) of the cell.
That said, I can't think of a single app on the iPhone that has a cell padding like this. You should think very carefully before doing this, as it's quite non-standard.
+1 you cant increase cell distance. However you can make it look that way: custom cells are easy to make in IB, and good custom cells are worth their weight in gold. You could set the cell spacer to none and a desired hight while making two UIImageViews (or a label with a cheeky string of underscores) in the cell to create the illusion of a cell boundary.
Here is a good tutorial on custom cells (I recommend watching the screen-cast):
http://icodeblog.com/2009/05/24/custom-uitableviewcell-using-interface-builder/

Resources