121

I need to create three dynamic columns, each with a fixed percentage of the total width. Not thirds, but different values. For example, the following illustration shows three columns: the first being 42% wide, the second being 25% wide, and the third being 33% wide.

For a 600 pixel across viewcontroller, that would be 252, 150, and 198 pixels respectively.

However, for any subsequent display sizes (i.e. iPhone 4 landscape (960 wide) or iPad 2 portrait (768 wide), I would like the relative percentages to be the same (not the pixel widths quoted above).

Is there a way to do this using Storyboards (i.e. without code)? I can do this easily in code, but my goal is to put as much of this display logic as possible into the Storyboard.

enter image description here

Jeffrey Berthiaume
  • 4,054
  • 3
  • 35
  • 45

3 Answers3

215

If, as you say, you know how to do it in code, then you already know how to do it in the storyboard. It's exactly the same constraints, but you are creating them visually rather than in code.

  1. Select both a view and its superview.

  2. Choose Editor -> Pin -> Widths Equally to constrain the width to be equal to the superview's width (actually the "pin" popup dialog at the bottom of the canvas works best here).

  3. Edit the constraint and set the Multiplier to the desired fraction, e.g. 0.42. And so too for the other views.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • 9
    Be sure that the view you are constraining appears in the "first item" of the Equal Widths Constraint and not the superview's width, otherwise you are making your subview width multiples of the superview's width. When control-dragging from a subview to a superview to apply an equal width constraint these are always reversed, which seems counter-intuitive to me. – memmons Oct 15 '14 at 03:16
  • Ah - by "doing it in code" I meant something more like "manually, without constraints or autolayout". – Jeffrey Berthiaume Oct 22 '14 at 20:52
  • Nice answer. I am surprise IB does not provide a way do this w/o editing the constraint. – wcochran Jan 07 '15 at 20:22
  • I tried to apply this in my UITableViewCell and everything was grayed. I had to embed all my views in a new view and select the Width Equally from my label to this new view. Hope it helps. – nano Apr 15 '15 at 08:33
  • One picture being worth a thousand words - You spent time, created a sample project and solved another dev's problem. Great!. You deserve more up votes @matt. – Ashok Aug 25 '15 at 16:03
  • 6
    New in Xcode7: If you want to equally align views use the new "Stack View". – Bijan Sep 15 '15 at 12:55
  • if grayed, select view together with it's parent view (i.e. both selected). – djdance Jan 17 '16 at 12:24
21

As Apple introduces UIStackView it made job much easy.

Method 1: Using Nib/StoryBoard:

You have to just add three view in interface builder & embed them into stackview

Xcode ► Editor ► Embed in ► StackView

enter image description here

Select stackView & give constraint with leading, trailing, top & equal height with safeArea

Click to Attribute inspector area & Set StackView horizontal & distribution to fill proportionally

[enter image description here3

Give constraint of three view with leading, trailing, top, bottom with respective of sides. enter image description here

Method 2: Programmatically:

import UIKit
class StackViewProgramatically: UIViewController {
    var propotionalStackView: UIStackView!
    ///Initially defining three views
    let redView: UIView = {
        let view = UIView()//taking 42 % initially
        view.frame = CGRect(x: 0, y: 0, width: 42 * UIScreen.main.bounds.width/100, height: UIScreen.main.bounds.height)
        view.backgroundColor = .red
        return view
    }()

    let greenView: UIView = {
        let view = UIView()//taking 42* initially
        view.frame = CGRect(x: 42 * UIScreen.main.bounds.width/100, y: 0, width: 25 * UIScreen.main.bounds.width/100, height: UIScreen.main.bounds.height)
        view.backgroundColor = .green
        return view
    }()
    let blueView: UIView = {
        let view = UIView()//taking 33*initially
        view.frame = CGRect(x: 67 * UIScreen.main.bounds.width/100, y: 0, width: 33 * UIScreen.main.bounds.width/100, height: UIScreen.main.bounds.height)
        view.backgroundColor = .blue
        return view
    }()

    ///Changing UIView frame to supports landscape mode.
    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)
        DispatchQueue.main.async {
            self.redView.frame = CGRect(x: 0, y: 0, width: 42 * self.widthPercent, height: self.screenHeight)
            self.greenView.frame = CGRect(x: 42 * self.widthPercent, y: 0, width: 25 * self.widthPercent, height: self.screenHeight)
            self.blueView.frame = CGRect(x: 67 * self.widthPercent, y: 0, width: 33 * self.widthPercent, height: self.screenHeight)
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        //Adding subViews to the stackView
        propotionalStackView = UIStackView()
        propotionalStackView.addSubview(redView)
        propotionalStackView.addSubview(greenView)
        propotionalStackView.addSubview(blueView)
        propotionalStackView.spacing = 0
        ///setting up stackView
        propotionalStackView.axis = .horizontal
        propotionalStackView.distribution = .fillProportionally
        propotionalStackView.alignment = .fill
        view.addSubview(propotionalStackView)
    }
}
//MARK: UIscreen helper extension
extension NSObject {

    var widthPercent: CGFloat {
        return UIScreen.main.bounds.width/100
    }

    var screenHeight: CGFloat {
        return UIScreen.main.bounds.height
    }
}

Output:

Works with landscape & portrait

enter image description here enter image description here

Demo project - https://github.com/janeshsutharios/UIStackView-with-constraints

https://developer.apple.com/videos/play/wwdc2015/218/

Jack
  • 13,571
  • 6
  • 76
  • 98
4

I think this can be explained in more detail so it can be more easily applied to any number of views requiring fixed percentage layouts within a superview.

Left-most view

  • Anchored to SuperView.Leading
  • Defines its fixed percentage as a multiplier on the SuperView.Height

Intermediate views

  • Defines its fixed percentage as a multiplier on the SuperView.Height
  • Pins its left to its neighbor's right

Right-Most view

  • Does not define a fixed percentage (it is the remainder of the available view)
  • Pins its left to its neighbor's right
  • Pins its right to SuperView.Trailing

All Views

  • Define their non-fixed heights by anchoring to Top Layout Guide.Top and Top Layout Guide.bottom. In the answer above, it is noted that this can also be done by setting equal height to the neighboring view.
JuJoDi
  • 14,627
  • 23
  • 80
  • 126