1

I have a UIButton that I have set to sit 8 points from the left and 8 points from the bottom of my view. I currently have the width and height set to >= 32 points.

But, I want the button to be larger on larger displays. I worked out how to set constraints so that the button is always at the same aspect ratio (square in my case) and always 10% of the width of its parent view, but that means when a device is rotated the button gets bigger and smaller as the view gets wider and narrower and I don't want that. I want it to be 10% of the shortest side.

It's easy in the enclosing ViewController code to get my desired width:

let buttonWidth = 0.1 * (self.view.bounds.width < self.view.bounds.height ? self.view.bounds.width : self.view.bounds.height)

But how do I correctly apply this to the button and what constraints should be in place from the storyboard? The entire interface is currently defined in the storyboard.

Do I update the bounds directly? A constraint? Multiple constraints? In the case of constraints I am completely unfamiliar with how deal with them in code.

zkarj
  • 657
  • 9
  • 23

5 Answers5

6

Achieving what you want, can't be done using only storyboard because you want to choose the smallest side of the screen and get 10% of it. In code you can easily do that.

  1. First you need to make IBOutlet of your width constraint. See this answer for detailed explanation
  2. Then change the constant of your IBOutlet width constraint depending on you calculated size. As far as you have aspect ratio 1:1 it will automatically change height equal width.


let buttonWidth = 0.1 * min(self.view.bounds.width, self.view.bounds.height)

self.yourWidthConstraint.constant = buttonWidth

self.view.layoutIfNeeded()
Community
  • 1
  • 1
  • Aha! Didn't realise you could make the *constraint* an IBOutlet. I will give that a go. – zkarj Oct 26 '15 at 01:28
  • Perfect! Simple to implement and addresses exactly what I want. – zkarj Oct 26 '15 at 01:53
  • @zkarj I am glad that it worked out for you, enjoy :) – Bagrat Kirakosian Oct 26 '15 at 10:41
  • 1
    First line can be: `let buttonWidth = 0.1 * min(self.view.bounds.width, self.view.bounds.height)` – kientux Oct 26 '15 at 16:04
  • Yes @kientux shorter version. But I just chose to show how to modify constraints by code. I didn't optimize existing code. Will update the answer though. – Bagrat Kirakosian Oct 26 '15 at 16:12
  • Purely as a thought exercise, because it really doesn't matter in my case, would the use of the native ternary operator not be more efficient than calling the library function min( )? I'd imagine that function is doing essentially the same thing but at the expense of the library call. But yeah, shorter and more obvious for the human. :-) – zkarj Oct 29 '15 at 08:34
  • @zkarj definitely agree with you because obviously min() function is doing exactly the same thing as you wrote in inline statement. The min() is just a shortcut but still you don't know what the function is doin? Probably it's faster but there is a chance that it's slower? Who cares tho? – Bagrat Kirakosian Oct 29 '15 at 09:45
1

Try this:

your_button.frame.size.width = buttonWidth

Or you can add/change constraint:

let widthConstraint = NSLayoutConstraint (item: your_button, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: buttonWidth)
your_button.view.addConstraint(widthConstraint)
Orkhan Alizade
  • 7,379
  • 14
  • 40
  • 79
1

I think you should explore the size classes APIs by Apple. You can set the constraints for your button for all specific cases, i.e. smaller and larger devices, portrait and landscape, right in Interface Builder.

More details here: About Designing for Multiple Size Classes

Mundi
  • 79,884
  • 17
  • 117
  • 140
  • I agree that size classes are the de facto approach, however I cannot conceive of how they would work in this case. I want the button to be large on large screens, small on small screens. If I tie size constraints to size classes, the class could change with simple device rotation. Granted right now if I tied it to width compact versus width regular, it would not change on any existing device. But going forward it doesn't seem like a deterministic approach – although perhaps my requirement is where it falls down. EDIT: Actually, the 6 Plus and 6S Plus *do* change. – zkarj Oct 26 '15 at 01:36
  • Your comment is not clear. You **can** specify the same button size for both orientations on a specific device with size classes. "Deterministic"? How are you going to determine the screen sizes of hardware to be released in the future? – Mundi Oct 26 '15 at 10:56
  • Ignore my use of the word 'deterministic' – I think I overuse it. :-) – zkarj Oct 29 '15 at 08:21
  • I can specify an *absolute* button size for a given size class, but I want it relative to the size of the view and therefore I must tie it in one dimension of the view which will change its absolute size when rotated. Compare a landscape iPhone 6 Plus versus a landscape iPad. Both are Regular width but the dimensions (in points) are vastly different. – zkarj Oct 29 '15 at 08:30
0

Edit: If you want to keep the same widht/height when device is rotated, forget about width constraint in storyboard (but keep the aspect ratio one). Create width constraint programmatically like this:

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.

    // Equivalent: Width of self.button equals 0.1 * width of self.view
    let widthConstraint = NSLayoutConstraint(item: self.button,
                                        attribute: NSLayoutAttribute.Width,
                                        relatedBy: NSLayoutRelation.Equal,
                                           toItem: nil,
                                        attribute: NSLayoutAttribute.NotAnAttribute,
                                       multiplier: 1.0,
                                         constant: 0.1 * CGRectGetWidth(self.view.bounds))

    self.view.addConstraint(widthConstraint)
    self.view.layoutIfNeeded()
}

It won't get wider and narrower if you create a width constrant and a aspect ratio constraint.

Button constraints

Ctrl + drag your button to content view, then choose Equals Width. Then edit that constraint with multiplier = 0.1. So your button width is always equals 10% of device width.

Then create a Aspect Ratio constraint and the button won't get narrower.

kientux
  • 1,782
  • 1
  • 17
  • 32
  • Surely it will? The aspect ratio will control the shape of the button regardless of the overall size. The width constraint will always apply to the width of the containing view, which changes size on rotation. Or am I missing something? – zkarj Oct 26 '15 at 01:25
  • Yes it will change the size but it won't be narrower. Is that what you want? If you want to keep the same size, you will need to create the width constraint programmatically (but aspect ratio can be create in storyboard). I'll update my answer. – kientux Oct 26 '15 at 01:50
  • My goal was to keep the size the same on rotation. Your updated answer will do the job, but I like the approach of adding the constraint in the storyboard to keep it happy and then modifying in code. Thanks for the full example though – may well come in useful for more complex situations later, or for others. – zkarj Oct 29 '15 at 08:37
0

if your button have the constraints, you need to find it on this way:

button.superview?.constraints.forEach { constraint in
  if constraint.firstItem === button &&
     constraint.firstAttribute == .width {
     constraint.constant = 20
     return
  } 
}       
self.view.layoutIfNeeded() 

Or you can make an IBOutlet for your constraint and change the constant of it.

Illya Krit
  • 925
  • 1
  • 8
  • 9