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

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)
}

Related

Rounded UIImageView

I'm trying to get a rounded UIImageView but it seems to render differently on different devices;
Looks like this on an iPhone Xr:
Looks like this on an iPhone 7:
I have a height constraint of 60 and the following code:
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
self.userAvatar.layer.cornerRadius = self.userAvatar.frame.height / 2
self.userAvatar.layer.masksToBounds = false
self.userAvatar.clipsToBounds = true
self.userAvatar.layer.borderWidth = 0
}
Any ideas?
It seems to me that you have given the image leading and trailing constraints instead of a fixed width.
To achieve a circle give image view width equal to height.
This happens due to different widths of devices.
If you're managing this view using Interface Builder (i.e. Storyboard or XIB), you can enforce a square shape (which becomes a circle when combined with the rounded corners you already have) for the view directly from there by defining a constraint for its Aspect Ratio. No need to code anything.
Control-drag (like you do to create Outlets, Actions, etc.) from the image view to itself, and the following popup will appear.
Select Aspect Ratio, which will create a constraint matching whatever the view's current ratio is (in this example, it's 15:8). If the view was already square, the constraint created should already be correct.
If not, you can find that constraint by clicking the following icon (for the Size inspector):
From there, you can double-click on that constraint to edit it, and change the Multiplier to 1:1:
In fact, an even easier option is, once you've Control-dragged from the view to itself, hold down Alt/Opt and the option displayed in the popup will change to Aspect Ratio (1:1), meaning you can set it directly from there without even having to edit the constraint.
Constrain the height equal to the width.
And, create a simple UIImageView subclass:
class RoundedImageView: UIImageView {
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = bounds.height / 2
}
}
The frame can (and will) change based on view lifecycle. By updating the cornerRadius in layoutSubviews() it will keep it "round".

Swift, how to add X number of views with constraints dynamically

I am developing a swift application which will fit 10 items on the screen. If I wanted to do this on a screen that would not change size i.e. the user doesn't change orientation or an iPad user does not use split screen, I would be able to detect the width by doing let size = bounds.width/19.
The problem is as the screen size is dynamic so therefor I need to do it with constraints. I would not like to use UICollectionView as that is too heavy and would also not like to use UIStackView if possible as I don't think it supports aspect ratio which I need for circles. I am trying to use UIViews.
Edit:
This is how I want them to look. This will be about 50 high and other information will be underneath.
UIStackView is the right tool for this job. In the future, I recommend more rigorously defining what you want to happen first, then dive into the documentation.
let sv = UIStackView()
sv.spacing = 10
// this means each arranged subview will take up the same amount of space
sv.distribution = .fillEqually
for _ in 0..<50 {
// omitting the rounded corners or any other styling because
// it's not the point of this question
let subview = UIView()
// The stack view will determine the width based on the screen size
// we just need to say that height == width
subview.widthAnchor.constraint(equalTo: subview.heightAnchor).isActive = true
sv.addArrangedSubview(subview)
}
// add your stackview to your view hierarchy and constrain it

ios - Adjust button width and height based on screen size

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)

Full width view does not contain same frame width as parent

I'm having an issue where I have a UIImageView that spans the entire width of the screen using the following constraints.
Align Leading to : Superview (Equals: -20)
Align Trailing to : Superview (Equals: -20)
Top Space to : Top Layout Guide (Equals: 0)
Aspect Ratio : 1:1
The image assumes the width of the screen while gaining aspect ratio (in this case a square). The issue comes with when I started attempting to make a circle out of the image, that I noticed that my UIIMageView's frames do not have proper values.
You'll have to excuse to code below for not being in Objective-C as I'm using Java through RoboVM but with very little effort you can mentally convert the code to Objective-C as it's pretty straightforward.
public class MyViewController extends UIViewController {
#IBOutlet private UIImageView myImageView;
#Override public void viewDidLoad() {
System.out.println("Image view width: " + myImageView.getFrame().getWidth());
System.out.println("Superview width: " + this.getView().getFrame().getWidth());
}
}
The results of running the application with this Controller shows the following:
Image view width: 240.0
Superview width: 320.0
When cutting the frame width of the image in half and applying that to the CALayer#cornerRadius property, the image does not make a complete circle, however when using half of the superviews width (Which is actually the size of the image) the result is a perfect circle.
In viewDidLoad, it has just loaded your view from the XIB or storyboard, not sized it yet. The size should be correct when viewWillAppear is called. So try putting your code in viewWillAppear and see if that helps.
The reason you need the -20 margin is because your superview has a margin of 20, and the constraints are created by default to 'Relative to margin' - if you uncheck that, you can set the 'real' constraint - in this case zero rather than -20
If you select the constraint in the assistant editor, you will see the check box to toggle this value

issues with UIImageView.layer.cornerRadius to create rounded images on different pixel densities ios

I'm simply trying to create a perfectly round image. Here's my swift code:
myImage.layer.cornerRadius = myImage.frame.size.width/2
myImage.layer.masksToBounds = true
This works on a 4s, but is not quite round on a 5s, and appears as a rounded rectangle on a iphone 6.
I'm assuming this has to do with frame.size.width returning values in pixels not points or something like that, but I've been unable to solve this problem.
If you're putting that code in viewDidLoad, try moving it to viewDidLayoutSubviews.
I'm guessing it's an auto layout issue -- although you've used the corner radius property appropriately and are in fact making the image frame circular, after auto-layout, the corner radius stays the same, but the image stretches so that it's no longer circular.
If your code is in viewDidLoad in ViewController, try moving it to viewDidLayoutSubviews.
If your rounded imageView is in tableViewCell, try moving it to draw.
override func draw(_ rect: CGRect) {
avatarView.layer.cornerRadius = avatarView.frame.size.width / 2
avatarView.layer.masksToBounds = true
avatarView.clipsToBounds = true
avatarView.contentMode = .scaleAspectFill
}
The problem is that if you change the cornerRadius of Any view, and expect it to look like a circle, the view has to be a square.
Now, because of different devices and different device size, the size of your image view might change and it may be a rectangle.
For e.g.
If you view is a 50 * 50
myImage.layer.cornerRadius = myImage.frame.size.width/2
This would add corner radios of 25 on both sides to make it a circle.
But because of auto layout of device change, your view might be a 50 * 80
Corner radius would add a 25, but as the height is 80, it will add 25 on both sides, and the remaining 30 won't be a curve and look straight.
What you can do is try viewing the screen in various orientations in the storyboard and change auto layout Constraints (Or structs and springs) to ensure that the image view is a square in all the devices
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
myImage.layer.cornerRadius = myImage.frame.size.width/2
myImage.layer.masksToBounds = true
}
This should work:
myImage.layer.cornerRadius = myImage.**bounds**.size.width/2
myImage.layer.masksToBounds = true

Resources