ios - Adjust button width and height based on screen size - ios

I am trying to adjust button sizes according to the device they are run on. iPhone SE is small compared to iPhone 8 and as a result the buttons do not fully appear.
I tried using the following code to adjust the size of the buttons according to the screen size but it did not show any changes.
roundedCornerDeliveryButton.layer.cornerRadius = 8
roundedCornerKitHomeButton.layer.cornerRadius = 8
widthMultiplier = Double(self.view.frame.size.width) / 69
heightMultiplier = Double(self.view.frame.size.height) / 321
roundedCornerDeliveryButton.frame.size.width = roundedCornerDeliveryButton.frame.width * CGFloat(widthMultiplier)
roundedCornerDeliveryButton.frame.size.height = roundedCornerDeliveryButton.frame.height * CGFloat(heightMultiplier)
roundedCornerKitHomeButton.frame.size.width = roundedCornerKitHomeButton.frame.width * CGFloat(widthMultiplier)
roundedCornerKitHomeButton.frame.size.height = roundedCornerKitHomeButton.frame.height * CGFloat(heightMultiplier)
roundedCornerDeliveryButton.frame.origin = CGPoint(x: roundedCornerDeliveryButton.frame.origin.x * CGFloat(widthMultiplier), y: roundedCornerDeliveryButton.frame.origin.y * CGFloat(heightMultiplier))
roundedCornerKitHomeButton.frame.origin = CGPoint(x: roundedCornerKitHomeButton.frame.origin.x * CGFloat(widthMultiplier), y: roundedCornerKitHomeButton.frame.origin.y * CGFloat(heightMultiplier))
How would I do this?

There are a few ways to do this, but it comes down to how you declared your buttons in the first place.
If your buttons are declared in Storyboard or Xib file, you probably should be using layout constraints.
For example, if you want a button to take 1/3rd, you start by defining a layout constraint with the top view of the view controller with "Equal Width", then you edit that constraint and change its multiplier to 1:3
The layout system will do its magic to ensure the constraints is respected and the button is always 1/3rd the screen width.
You can declare several constraints like that to automatically respect different constraints, like making sure your button height is always taller than 36pt, width is never wider than 400pt, etc. Just have to define the proper priorities and the constraints.
Defining your sizing constraints this way has the advantage of being inspectable in the Xib as you can quickly change device type & orientation and make sure everything works before even running your code.
Good luck!

To make the button fit its content use
button.sizeToFit()
Also it's better to do it with auto-layout
self.view.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
button.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
button.centerYAnchor.constraint(equalTo: self.view.centerYAnchor)
])
You can add this constraint if you want it proportionally
button.widthAnchor.constraint(equalTo:self.view.widthAnchor,multiplier:0.75)

Related

Increasing height of UIView.frame programmatically works till iPhone 8 plus but doesn't works beyond model iphone x

I am increasing the height for UIView programmatically in runtime on click of button, it works fine on iPhone 6,7,8 and plus models but doesn't works on iPhone X and beyond models. i have initially set constraints programmatically as i views are autoLayout based. I also tried to set constraints at runtime which didn't helped too.
//Tried this and working till iPhone 8+
self.view.frame.size.height += CGFloat(280)
//or
self.view.frame = CGRect(x: 0, y: 0, width: self.view.frame.width,
height: self.view.frame.height + 20.0)
//Tried setting height constraint
self.view.autoSetDimension(.height, toSize: 270)
Debug view hierarchy and look for conflicting constraints that might interfere with your height constraint. Also make sure you set translatesAutoresizingMaskIntoConstraints to false if you set constraints in code.
Also: if you set your constraint like this in code (apparently you are using PureLayout), that method returns the reference to that height constraint. So if you want to modify it, you need to update the constant value of your constraint, instead of setting a new one.
For any further help you certainly need to share more information.

How do I add constraints so that my view's dimensions do not change when the orientation changes?

I want my view to have the following properties (the numbers are arbitrarily chosen):
width is equal to height divided by 1.2
stays at the bottom right of the screen
height is 1/7 of the screen's height when in portrait
width and height does not change when the orientation changes
The first three requirements can be easily translated into UILayoutConstraints. I have done them with SnapKit just because it reads more clearly. You should see what I mean even if you have never used SnapKit before.
let myView = UIView(frame: .zero)
myView.backgroundColor = .green
view.addSubview(myView)
myView.snp.makeConstraints { (make) in
make.right.equalToSuperview().offset(-8)
make.bottom.equalToSuperview().offset(-8)
make.width.equalTo(myView.snp.height).dividedBy(1.2)
make.height.equalTo(view.snp.height).dividedBy(7) // *
}
The problem is the last bullet point. When I rotate the device from portrait to landscape, what was originally the width before the rotation, becomes the height after the rotation. This causes my view to become smaller as a result.
Basically, I want to replace the constraint marked with * with something like this:
make.height.equalTo(max(view.snp.height, view.snp.width)).dividedBy(7)
but I don't think max(a, b) is a thing in SnapKit or the UILayoutConstraint API.
Surely there is some other way of expressing "equal to whichever length is longer", right?
P.S. I didn't tag this with snapkit because I would also accept an answer that uses the UILayoutConstraint API.
Looks like you have 2 options:
Hardcode the height value.
Try to use nativeBounds:
This rectangle is based on the device in a portrait-up orientation. This value does not change as the device rotates.
In this case the height is always be for portrait mode.
myView.snp.makeConstraints { make in
make.right.bottom.equalToSuperview().offset(-8)
let screenHeight = UIScreen.main.nativeBounds.height / UIScreen.main.nativeScale
let height = screenHeight / 7
make.width.equalTo(height).dividedBy(1.2)
make.height.equalTo(height)
}

Set UIView to change its height according to Subviews

I'm creating a popup and I have set the popup view height but I want the popup height to adjust automatically to its subviews
What it looks like on Iphone 8: screenshot 1
and what it look like on Iphone 5s: screenshot 2
i want the popup view to resize automatically to fit subview correctly
I'm using this:
let popUpViewWidth = (view.frame.width / 1.4) + 10
let popUpViewHeight = view.frame.height / 1.8
popUpView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
popUpView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
popUpView.widthAnchor.constraint(equalToConstant: popUpViewWidth).isActive = true
popUpView.heightAnchor.constraint(equalToConstant: popUpViewHeight).isActive = true
I know that I have set the fixed size to popUpView, But how can I make changes to make it to change height dynamically, Thanks.
Note: I'm not using any storyboard so plz do not suggest answers related to storyboard and sorry for my bad english
Easy way is to embed all your sub-views in a vertical stack-view.
StackView Automatically sets it width and height with respect to its subViews.

UIStackView proportional layout with only intrinsicContentSize

I'm experiencing problems with layout of arranged subviews in UIStackView and was wondering if someone could help me understand what's going on.
So I have UIStackView with some spacing (for example 1, but this does not matter) and .fillProportionally distribution. I'm adding arranged subviews with only intrinsicContentSize of 1x1 (could be anything, just square views) and I need them to be stretched proportionally within stackView.
The problem is that if I add views without actual frame, only with intrinsic sizes, then I get this wrong layout
Otherwise, if I add views with frames of the same size, everything works as expected,
but I really prefer not to set view's frame at all.
I'm pretty sure that this is all about hugging and compression resistance priority, but can't figure out what right answer is.
Here is an Playground example:
import UIKit
import PlaygroundSupport
class LView: UIView {
// If comment this and leave only intrinsicContentSize - result is wrong
convenience init() {
self.init(frame: CGRect(x: 0, y: 0, width: 1, height: 1))
}
// If comment this and leave only convenience init(), then everything works as expected
public override var intrinsicContentSize: CGSize {
return CGSize(width: 1, height: 1)
}
}
let container = UIView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
container.backgroundColor = UIColor.white
let sv = UIStackView()
container.addSubview(sv)
sv.leftAnchor.constraint(equalTo: container.leftAnchor).isActive = true
sv.rightAnchor.constraint(equalTo: container.rightAnchor).isActive = true
sv.topAnchor.constraint(equalTo: container.topAnchor).isActive = true
sv.bottomAnchor.constraint(equalTo: container.bottomAnchor).isActive = true
sv.translatesAutoresizingMaskIntoConstraints = false
sv.spacing = 1
sv.distribution = .fillProportionally
// Adding arranged subviews to stackView, 24 elements with intrinsic size 1x1
for i in 0..<24 {
let a = LView()
a.backgroundColor = (i%2 == 0 ? UIColor.red : UIColor.blue)
sv.addArrangedSubview(a)
}
sv.layoutIfNeeded()
PlaygroundPage.current.liveView = container
This is obviously a bug in the implementation of UIStackView (i.e. a system bug).
DonMag already gave a hint pointing in the right direction in his comment:
When you set the stack view's spacing to 0, everything works as expected. But when you set it to any other value, the layout breaks.
Here's the explanation why:
ℹ️ For the sake of simplicity I will assume that the stack view has
a horizontal axis and
10 arranged subviews
With the .fillProportionally distribution UIStackView creates system-constraints as follows:
For each arranged subview, it adds an equal width constraint (UISV-fill-proportionally) that relates to the stack view itself with a multiplier:
arrangedSubview[i].width = multiplier[i] * stackView.width
If you have n arranged subviews in the stack view, you get n of these constraints. Let's call them proportionalConstraint[i] (where i denotes the position of the respective view in the arrangedSubviews array).
These constraints are not required (i.e. their priority is not 1000). Instead, the constraint for the first element in the arrangedSubviews array is assigned a priority of 999, the second is assigned a priority of 998 etc.:
proportionalConstraint[0].priority = 999
proportionalConstraint[1].priority = 998
proportionalConstraint[2].priority = 997
proportionalConstraint[3].priority = 996
...
proportionalConstraint[n–1].priority = 1000 – n
This means that required constraints will always win over these proportional constraints!
For connecting the arranged subviews (possibly with a spacing) the system also creates n–1 constraints called UISV-spacing:
arrangedSubview[i].trailing + spacing = arrangedSubview[i+1].leading
These constraints are required (i.e. priority = 1000).
(The system will also create some other constraints (e.g. for the vertical axis and for pinning the first and last arranged subview to the edge of the stack view) but I won't go into detail here because they're not relevant for understanding what's going wrong.)
Apple's documentation on the .fillProportionally distribution states:
A layout where the stack view resizes its arranged views so that they fill the available space along the stack view’s axis. Views are resized proportionally based on their intrinsic content size along the stack view’s axis.
So according to this the multiplier for the proportionalConstraints should be computed as follows for spacing = 0:
totalIntrinsicWidth = ∑i intrinsicWidth[i]
multiplier[i] = intrinsicWidth[i] / totalIntrinsicWidth
If our 10 arranged subviews all have the same intrinsic width, this works as expected:
multiplier[i] = 0.1
for all proportionalConstraints. However, as soon as we change the spacing to a non-zero value, the calculation of the multiplier becomes a lot more complex because the widths of the spacings have to be taken into account. I've done the maths and the formula for multiplier[i] is:
Example:
For a stack view configured as follows:
stackView.width = 400
stackView.spacing = 2
the above equation would yield:
multiplier[i] = 0.0955
You can prove this correct by adding it up:
(10 * width) + (9 * spacing)
= (10 * multiplier * stackViewWidth) + (9 * spacing)
= (10 * 0.0955 * 400) + (9 * 2)
= (0.955 * 400) + 18
= 382 + 18
= 400
= stackViewWidth
However, the system assigns a different value:
multiplier[i] = 0.0917431
which adds up to a total width of
(10 * width) + (9 * spacing)
= (10 * 0.0917431 * 400) + (9 * 2)
= 384,97
< stackViewWidth
Obviously, this value is wrong.
As a consequence the system has to break a constraint. And of course, it breaks the constraint with the lowest priority which is the proportionalConstraint of the last arranged subview item.
That's the reason why the last arranged subview in your screenshot is stretched.
If you try out different spacings and stack view widths you'll end up with all sorts of weird-looking layouts. But they all have one thing in common:
The spacings always take precedence. (If you set the spacing to a greater value like 30 or 40 you'll only see the first two or three arranged subviews because the rest of the space is fully occupied by the required spacings.)
To sum things up:
The .fillProportionally distribution only works properly with spacing = 0.
For other spacings the system creates constraints with an incorrect multiplier.
This breaks the layout as
either one of the arranged subviews (the last) has to be stretched if the multiplier is smaller than it should be
multiple arranged subviews have to be compressed if the multiplier is greater than it should be.
The only way out of this is to "misuse" plain UIViews with a required fixed-width constraint as spacings between the views. (Normally, UILayoutGuides were introduced for this purpose but you cannot even use those either because you cannot add layout guides to a stack view.)
I'm afraid that due to this bug, there is no clean solution to do this.
As of Xcode 9.2 at least, the playground provided works as intended provided the initializer and the intrinsic content size are both commented out. In that case, the proportional filling works as expected, even with spacing > 0
This is an example with spacing = 5
That seems to make sense because the arranged subviews have no intrinsic content size and the StackView determines their widths to proportionally fill the designated axis.
If only the initializer is enabled (and not the intrinsic content size override), then I get this, which doesn't match the comment in the playground, so I guess this behaviour must have changed since the question was posted:
I don't understand that behaviour, because it would seem to me that setting the frame manually should be ignored when using Auto Layout.
If only the intrinsic content size override is enabled (and not the initializer) then I get the problematic image that originated this post (here with spacing = 5):
Essentially, the design now is conflicting and can't be realized, because views want to be 1 point wide, due to specified intrinsic content size. The total space here should be
24 views * 1 point/view + 23 spaces * 5 points/space = 139 < 300 = sv.bounds.width
with the last arranged view's constraint broken due to lowest priority as pointed out by Mischa.
Upon pixel-per-pixel inspection the first 23 views above are wider than 1 pixel though, 2 or 3 pixels actually, except for the last one, thus the math doesn't quite match, I don't know why, possibly rounding up of decimal numbers?
For reference, this is what it looks like in that case with intrinsic content size of width 5, still failing to satisfy the constraints.

iPhone auto layout images arrangement issue

I want to arrange 3 Images on iPhone & iPad using auto layout.
3 Images should rezise preserving aspect ratio, and width of all 3 images should be same.
Same space from left and right sides for all 3 images.
See Example figure (Figure shows landscape and portrait mode) on this link:
Portrait: http://i.imgur.com/9KVXATE.png
Landscape: http://i.imgur.com/tDjj9K6.png
It is possible programatically getting width and height of screen/view but I want to do it using auto-layouts
Programatically:
//Inside this method
//- (void) didRotateFromInterfaceOrientation:
(UIInterfaceOrientation)fromInterfaceOrientation
//Main_View is the view in which the 3 images are kept
//ImageView1, ImageView2, ImageView3 are 3 image views
ImageView1.frame = CGRectMake(Main_View.frame.origin.x + 3, Main_View.frame.origin.y + 30, ((Main_View.frame.size.width / 3)-4), ((Main_View.frame.size.width / 3) - 4) * (82.0/75.0));
ImageView2.frame = CGRectMake(ImageView1.frame.size.width + 6, Main_View.frame.origin.y + 30, ((Main_View.frame.size.width / 3)-4), ((Main_View.frame.size.width / 3) - 4) * (82.0/75.0));
ImageView3.frame = CGRectMake(ImageView2.frame.size.width + ImageView1.frame.size.width + 9, Main_View.frame.origin.y + 30, ((Main_View.frame.size.width / 3)-4), ((Main_View.frame.size.width / 3) - 4) * (82.0/75.0));
Main_View changes its size(width,height). Then (ImageView1 width) is (Main_View width / 3), It Preserves aspect ratio too.
Programatically it works perfect
This is quite easy to do it with autolayout in interfacebuilder as well. But, there are few things which you might have to sacrifice when using autolayout in interface builder. You would want to arrange items the items in storyboard. The steps to do it are as follows:
Add three views to interface builder, like so,
Select all three pictures and add new constraint in interface builder with the button like this |---|, and select equal width and equal height.
Then, again select all three views and then from the menu select Editor -> Align -> Top Edges
Select the left most view and give it some offset to the left,
Select the right most view and give it some offset to the right,
Select one of the view and give some offset at the top and bottom.
Select second view control + drag it to first and select the horizontal separation and give some constant value and do the same for the second and third view.
Now, the constraints is complete. It could happen that when you select the multiple views at the same time and add some constraint, it might not have been added to one of the views. So might have to resolve this looking error and seeing the constraints for the view, if all those constraints we added is there.
You could keep the constraint that separate the view from the top margin or bottom margin and create outlet for it and then change to suit your need or you could set some parameter in storyboard itself.
Here is the screenshot from my ipad simulator for the horizontal orientation.
And here is for vertical orientation,
I hope I have answered your question. Do let me know if you have something with this.
(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{
// i assume here that you have three imageviews img1, img2, img3
float viewWidth = [UIScreen mainScreen].bounds.size.width;
float viewHeight = [UIScreen mainScreen].bounds.size.height;
if (toInterfaceOrientation == UIInterfaceOrientationPortrait){
// your portrait mode code here
}
else{
NSLog(#"bounds are (%f,%f)", viewHeight,viewWidth);
img1.width = viewHeight/3; //i used "viewHeight" because of landscape mode
img2.width = viewHeight/3;
img3.width = viewHeight/3;
// and then set centre of imageview according to your requirement
}
}

Resources