Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Achieving Grid Layout of Buttons with Autolayout

Tags:

ios

autolayout

I am trying to achieve a layout of buttons very similar to Apple's inbuilt Calculator app: Closeup of calc application

I am using autolayout to position the buttons, and drawing a 0.5px border on each button, aiming for a 1px gap between buttons (like the calc app, closeup showing pixels above, from a retina device).

Border applied by :

    btn.layer.borderWidth=0.5f;
    btn.layer.borderColor=[[UIColor blackColor] CGColor];

and the layout is set up using Interface Builder.

app screenshot

It almost works; but there is some variability in the gap between buttons - for example the gap between the "1,2,3,thru" row and the "4,5,6,and" row is 2 pixels, but the gap between the "7,8,9" and "cl,0,@" rows is only one pixel.

Constraints are :

  • Top black area has a fixed height
  • All buttons same height
  • In the numeric rows, numeric buttons are set to same width, THRU, AND, ALL OFF and ENTER buttons are set to same fixed width
  • Top row of buttons pinned to bottom of black area, bottom row pinned to bottom of superview In IB the buttons are all positioned butting up against each other.

In interface builder and at runtime I don't see any autolayout errors; and a debugger dump of the autolayout info gives me:

po [[UIWindow keyWindow] _autolayoutTrace]

*<UIWindow:0x10908a560> - AMBIGUOUS LAYOUT
|   *<UILayoutContainerView:0x109136140>
|   |   *<UINavigationTransitionView:0x10908ef80>
|   |   |   *<UIViewControllerWrapperView:0x109138870>
|   |   |   |   *<UIView:0x1090e6d10>
|   |   |   |   |   *<UILabel:0x1090d5bb0>
|   |   |   |   |   *<UIButton:0x1090d27e0>
|   |   |   |   |   |   <UIButtonLabel:0x1091c9190>
|   |   |   |   |   *<UIButton:0x1090844c0>
|   |   |   |   |   |   <UIButtonLabel:0x1091c7990>
|   |   |   |   |   *<UIButton:0x109088b60>
|   |   |   |   |   |   <UIButtonLabel:0x1091c6190>
|   |   |   |   |   *<UIButton:0x1090e2ce0>
|   |   |   |   |   |   <UIButtonLabel:0x1091c4990>
|   |   |   |   |   *<UIButton:0x1090e4e50>
|   |   |   |   |   |   <UIButtonLabel:0x1091c3190>
|   |   |   |   |   *<UIButton:0x1090db730>
|   |   |   |   |   |   <UIButtonLabel:0x1091c1990>
|   |   |   |   |   *<UIButton:0x109068f50>
|   |   |   |   |   |   <UIButtonLabel:0x1091c0190>
|   |   |   |   |   *<UIButton:0x10906db80>
|   |   |   |   |   |   <UIButtonLabel:0x1091be990>
|   |   |   |   |   *<UIButton:0x1090d23a0>
|   |   |   |   |   |   <UIButtonLabel:0x1091bd190>
|   |   |   |   |   *<UIButton:0x1090c8520>
|   |   |   |   |   |   <UIButtonLabel:0x1091bb990>
|   |   |   |   |   *<UIButton:0x1090c9090>
|   |   |   |   |   |   <UIButtonLabel:0x1091ba190>
|   |   |   |   |   *<UIButton:0x1090c5680>
|   |   |   |   |   |   <UIButtonLabel:0x1091b8990>
|   |   |   |   |   *<UIButton:0x1090d7fa0>
|   |   |   |   |   |   <UIButtonLabel:0x1091b7190>
|   |   |   |   |   *<UIButton:0x1090cc820>
|   |   |   |   |   |   <UIButtonLabel:0x1091b5990>
|   |   |   |   |   *<UIButton:0x10909dda0>
|   |   |   |   |   |   <UIButtonLabel:0x1091b4190>
|   |   |   |   |   *<UIButton:0x1090c8090>
|   |   |   |   |   |   <UIButtonLabel:0x1091b2990>
|   |   |   |   |   *<UIButton:0x1090c8e30>
|   |   |   |   |   |   <UIButtonLabel:0x1091b1190>
|   |   |   |   |   *<UIButton:0x1090d5390>
|   |   |   |   |   |   <UIButtonLabel:0x1091af990>
|   |   |   |   |   *<UIButton:0x10d30d260>
|   |   |   |   |   |   <UIButtonLabel:0x1091ae190>
|   |   |   |   |   *<UIButton:0x10909a6a0>
|   |   |   |   |   |   <UIButtonLabel:0x1091ac990>
|   |   |   |   |   *<UIButton:0x1090cca40>
|   |   |   |   |   |   <UIButtonLabel:0x109161160>
|   |   |   |   |   *<UIButton:0x10d30ea90>
|   |   |   |   |   |   <UIButtonLabel:0x109156460>
|   |   |   |   |   *<UIButton:0x1090da9e0>
|   |   |   |   |   |   <UIButtonLabel:0x109177ad0>
|   |   |   |   |   *<_UILayoutGuide:0x1090e6dd0> - AMBIGUOUS LAYOUT
|   |   |   |   |   *<_UILayoutGuide:0x1090ce080> - AMBIGUOUS LAYOUT
|   |   <UINavigationBar:0x10907da60>
|   |   |   <_UINavigationBarBackground:0x109087240>
|   |   |   |   <_UIBackdropView:0x1090899f0>
|   |   |   |   |   <_UIBackdropEffectView:0x10908b110>
|   |   |   |   |   <UIView:0x10908bbb0>
|   |   |   |   <UIImageView:0x1090877a0>
|   |   |   <UINavigationItemView:0x10916bc30>
|   |   |   |   <UILabel:0x10917f810>
|   |   |   <UINavigationItemButtonView:0x1090ee060>
|   |   |   |   <UILabel:0x1090ee5d0>
|   |   |   <_UINavigationBarBackIndicatorView:0x109098c80>

Can anyone offer any hints on how to achieve a "pixel perfect" layout like this with Autolayout? Or should I be doing this programatically instead?

like image 848
docsteer Avatar asked Feb 16 '14 20:02

docsteer


People also ask

How do I create a grid auto layout in Figma?

Use the keyboard shortcut ⇧ Shift ⇧ Shift A to add auto layout. Figma will create a frame around your selection, and add auto layout.

What is Autolayout in Swift?

Auto Layout constraints allow us to create views that dynamically adjust to different size classes and positions. The constraints will make sure that your views adjust to any size changes without having to manually update frames or positions.

How do I create a constraint in Autolayout?

Control-Dragging ConstraintsIf you drag more or less horizontally, you get options to set the horizontal spacing between the views, and options to vertically align the views. If you drag more or less vertically, you get options to set the vertical spacing, and options to align the views horizontally.


2 Answers

Layer borders are drawn inside the views bounds, so you aren't going to see a "gap" between the buttons, but the effect of two borders abutting each other. This isn't going to work in the non-retina simulator or devices, since it can't draw a half-pixel, and Autolayout doesn't align views on half-pixels.

For a layout like the one above I'd be giving a fixed height to the buttons (or to one button, and making the others equal it) and letting the black view take up the remaining space. It's always best to have at least one (ideally, exactly one) variable element in a full-screen layout basically to cover rounding errors.

It sounds at the moment like you have one fixed element (the black view) and the buttons are basically "take the remaining height and divide it between yourselves", which will only work if the remaining height divides perfectly.

like image 105
jrturton Avatar answered Oct 20 '22 03:10

jrturton


I tried to set horizontal/vertical spaces between views equal to 0.5. The color of parent view acts as border color. It looks ugly in IB, it cannot layout views properly but resulting layout on device looks perfect.

In interface builder:

in interface builder

On simulator:

on device

Constraints are a bit tricky. For each row:

  • set height for the 1st button
  • set equal height for all buttons
  • set equal width for all buttons (a bit different for rows with orange buttons)
  • align top edges of all buttons
  • align bottom edges of all buttons
  • set leading and trailing spaces between all buttons (including spaces to superview) to 0.5
  • set top space (to previous row) of 1st button to 0.5

You can apply constraints to multiple items at once. In order to apply leading/trailing spaces between buttons, place them without intersection in desired order before applying.

like image 30
vokilam Avatar answered Oct 20 '22 04:10

vokilam