Set UIImageView AspectRatio constraint programmatically in swift 3 - ios

I have an UIImageView in storyboard which AspectRatio is 1:1, that I want to change to 2:1 programmatically in ViewController in some cases. I create reference of that constraint in ViewController but unable to set the constraint.

You can change constraint programmatically in swift 3
let aspectRatioConstraint = NSLayoutConstraint(item: self.YourImageObj,attribute: .height,relatedBy: .equal,toItem: self.YourImageObj,attribute: .width,multiplier: (2.0 / 1.0),constant: 0)
self.YourImageObj.addConstraint(aspectRatioConstraint)

As it's stated in Apple's guide, there're three ways to set constraints programmatically:
You can use layout anchors
You can use the NSLayoutConstraint class
You can use the Visual Format Language
The most convenient and fluent way to set constraints is using Layout Anchors.
It's just one line of code to change aspect ratio for your ImageView
imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor, multiplier: 1.0/2.0).isActive = true
To avoid "[LayoutConstraints] Unable to simultaneously satisfy constraints." you should add reference to your ImageView's height constraint then deactivate it:
heightConstraint.isActive = false

Set the multiplier of the constraint to 0.5 or 2 depending on your constraint condition, It'll become 2:1

Related

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)

Can not find constraints created for UIView in constraints property

According to docs the constraints instance property is
The constraints held by the view.
var constraints: [NSLayoutConstraint] { get }
but when I created them with
SampleView.rightAnchor.constraint(equalTo: right ,constant: rightConstant).isActive = true
so it prints me a constraint
<NSLayoutConstraint:0x1c4281540 UIButton:0x103d1d030'Next'.right == UIView:0x103d04740.right (active)>
but constraints property shows me nothing. empty array [], but at the same time
when i define constraints like heightAnchor and widthAnchor
sampleView.heightAnchor.constraint(equalToConstant: 50).isActive = true
sampleview.widthAnchor.constraint(equalToConstant: 60).isActive = true, and constraints array is not empty
[<NSLayoutConstraint:0x1c028bd10 UIButton:0x103d1c5e0'Skip'.height == 50 (active)>,
<NSLayoutConstraint:0x1c028bf40 UIButton:0x103d1c5e0'Skip'.width == 60 (active)>]
If someone knows , why so?
Width and height constraints are added to the view itself , but constraints with otherViews are added to the other view if it's the parent if not to the shared parent between the two subviews , see this
so before you ask any view for it's constraints you should keep in mind the above photo
The constraint will probably be in the constraints array of the UIView right. The height and width constraints are held by the view itself because they are constraints that only are in relation to the SampleView itself while the right anchor constraint is in relation to the UIView right.
The hierarchy is visually clearer when dealing with constraints in the Interface Builder.

Programmed Slider Constraint is Not Updating

I have am attempting to learn how to populate a view in my storyboard with sliders and buttons, programmatically. I am trying, currently, to get one programmed slider to adhere to a programmed NSLayoutConstraint
Here is my code:
let centerXConstraint = NSLayoutConstraint(item: self.volumeSliderP, attribute: NSLayoutAttribute.centerX, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.centerX, multiplier: 1.0, constant: 10.0)
self.view.addConstraint(centerXConstraint)
I should mention, that when I substitute the first item for a slider which already exists on the view (which was placed via Storyboard, with it's own constraints also placed with IB/Storyboard), it does updated correctly with the above NSLayoutConstraint code. Also, I have been able to update my programmed volumeSliderP with custom code to change it's handle and rotate it to vertical successfully.
What step am I missing to allow this NSLayoutConstraint code to work upon my programmed slider?
Thank you for any help!
When working with constraints in code, you need to do two (maybe three) things, regardless of control type:
Set the translatesAutoresizingMaskIntoConstraints to false.
Failure to do so will set off constraint conflicts, which will appear in the console log. I usually create an extension to UIView for this:
public func turnOffAutoResizing() {
self.translatesAutoresizingMaskIntoConstraints = false
for view in self.subviews as [UIView] {
view.translatesAutoresizingMaskIntoConstraints = false
}
}
Then in viewDidLoad (after adding my subviews) I simply add a line:
view.turnOffAutoResizing()
Consider if any subviews have intrinsic content size.
As explained in the linked Apple doc, if you have label and a text field, the text field will expand to fit the label without the need for setting widths. A UISlider does not have an intrinsic width but it does have an intrinsic height.
So in your case you need to not only set position, it needs to define the width.
A combination of top and leading will yield enough for the layout engine to know "where" and "height", but not "width". Same would go if you defined "centerX" and something - you didn't list any code - for the Y factor (top, bottom, centerY).
If I'm stating this clearly, you should be able to see that the engine will know enough to say (in frame coordinates) "start the slider at X/Y, height is XX points (it has intrinsic height), but how long should it be?"
I typically set either top, leading, and trailing... or top, centerX, and width. But it varies with the need.

How to add leading padding to view added inside an UIStackView

This is my setup: I have an UIScrollView with leading,top, trialing edge set to 0. Inside this I add an UIStackView with this constraints:
stackView.centerYAnchor.constraintEqualToAnchor(selectedContactsScrollView.centerYAnchor).active = true
stackView.leadingAnchor.constraintEqualToAnchor(selectedContactsScrollView.leadingAnchor).active = true
Inside the stack view I add some views.
My issue is that because of the constraints the first view added to stack view will also have leading edge = 0.
What are the ways that I could add some padding to the first view ? Without adjusting the scroll view constraints.
When isLayoutMarginsRelativeArrangement property is true, the stack view will layout its arranged views relative to its layout margins.
stackView.layoutMargins = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20)
stackView.isLayoutMarginsRelativeArrangement = true
But it affects all arranged views inside to the stack view. If you want this padding for only one arranged view, you need to use nested UIStackView
I have found that constraints don't work inside a Stack View, or they seem somewhat strange.
(Such as if I add leading/trailing constraint to selected on image stackview, that adds leading to collectionview too, but doesn't add trailing; and it be conflict for sure).
To set layout margins for all views inside the stackview, select:
Stack View > Size Inspector > Layout Margins > Fixed
Note: "Fixed" option was formerly called "Explicit", as seen in the screenshots.
Then add your padding:
The solution you have provided doesn't add a padding for your views inside your UIStackView (as you wanted in the question), but it adds a leading for the UIStackView.
A solution could be to add another UIStackView inside your original UIStackView and give the leading to this new UIStackVIew. Then, add your views to this new UIStackView.
Hint, you can do that completely using Interface Builder. In other words, no need to write code for it.
What worked for me is to add to stack view another UIView that's just a spacer (works at least with stackView.distribution = .Fill):
let spacerView = UIView(frame: CGRect(x: 0, y: 0, width: 10, height: 10))
stackView.addArrangedSubview(spacerView)
stackView.addArrangedSubview(viewThatNeedsSpaceBeforeIt)
stackView.addArrangedSubview(NextView)...
If you only need leading padding, then you can set the stack view's Alignment to "Trailing" and then you will be free to specify unique Leading constraints on each of its contained subviews.
As a bonus, you can also set the stack view's alignment to "Center" and then you can use Leading and/or Trailing constraints to give each item its own padding on both sides.
Set your stackview alignment to "center". After that you can give every subview different leading and trailing.
swift 3:
You just need set offset by:
firstView.leadingAnchor.constraint(equalTo: parentView.leadingAnchor, constant: 200).isActive = true
Be sure this constraint set after you parentView.addArrangdSubView(firstView)
The solution would be to have a regular view in the stack view to hold whatever views you are wanting to add constraints to, and then you can add constraints for your items that are relative to the views in the stack view. That way, your original views can have leading and trailing constraints within the stack view.
This can be done in the interface builder and programatically.
This question already has good answers,
One suggestion though, use spacing property to set the spacing between the views. For first and last views there can be two options, either set insets as #tolpp suggested or add constraint attaching to parent (stackview) with constant to add padding.
What we did was add transparent components (e.g., UIButton/UIView) as the first and last children of the UIStackView. Then set constrain the width of these invisible children to adjust the padding.
It seems that the solution was pretty simple. Instead of:
stackView.leadingAnchor.constraintEqualToAnchor(selectedContactsScrollView.leadingAnchor).active = true
I just wrote:
stackView.leadingAnchor.constraintEqualToAnchor(selectedContactsScrollView.leadingAnchor, constant: 15).active = true
Just add an empty view at the beginning of the stack view (also constraining its width and/or height):
stackView.insertArrangedSubview(UIView().constrain(width: 0, height: 0), at: 0)
and/or at the end:
stackView.addArrangedSubview(UIView().constrain(width: 0, height: 0))
I created this simple UIView extension to add the constraints:
extension UIView {
func constrain(width: CGFloat, height: CGFloat) -> Self {
NSLayoutConstraint.activate([
widthAnchor.constraint(equalToConstant: width),
heightAnchor.constraint(equalToConstant: height)
])
return self
}
}

UIImageView with Aspect Fill inside custom UITableViewCell using AutoLayout

I've been struggling with fitting an UIImageView which shows images of variable widths and heights with Aspect Fill. The cell height is not adapting to the new height of the UIImageView and persist it's height.
The hierarchy of the views is this
UITableViewCell
UITableViewCell.ContentView
UIImageView
I tried these scenarios in XCode Auto Layout :
set the UIImageView => Height to remove at build time
set the Intrinsic Value of the UIImageView to placeholder
set the Intrinsic Value for each of the UIImageView, ContentView and UITableViewCell to placeholder
With any of these combinations I get this view:
http://i.stack.imgur.com/Cw7hS.png
The blue lines represent the cell borders (boundaries) and the green ones represent the UIImageView border (boundaries). There are four cells in this example, the 1st and the 3rd ones have no images and the 2nd and the 4th ones have the same image (overflowing over the ones which have none).
I cobbled together a solution based on two previous answers:
https://stackoverflow.com/a/26056737/3163338 (See point 1)
https://stackoverflow.com/a/25795758/3163338 (See point 2)
I wanted to keep the AspectRatio of the image regardless of its Height while fixing up the Width according to that of the UIImageView, the container of the image.
The solution comprises of :
Adding a new AspectRatio constraint
let image = UIImage(ContentFromFile: "path/to/image")
let aspect = image.size.width / image.size.height
aspectConstraint = NSLayoutConstraint(item: cardMedia, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: cardMedia, attribute: NSLayoutAttribute.Height, multiplier: aspect, constant: 0.0)
when adding this constraint, xCode will complain about the new "redundant" constraint and attempt to break it, rendering it useless, yet displaying the image exactly like I want. This leads me to the second solution
Lowering the priority of the new constrain to '999' seems to stop xcode from breaking it, and it stopped showing warning message about the new constraint
aspectConstraint?.priority = 999
Not sure why xCode automatically adds UIView-Encapsulated-Layout-Height and UIView-Encapsulated-Layout-Height at build/run time; however, I learned how to respect that and live with it :)
Just leaving the solution here for anyone to check. This is working on iOS 8. I tried with iOS7 but it doesn't work the same as you need to implement tableView:heightForRowAtIndexPath calculating the height of the cell based on all the items contained within it and disable setting up:
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44.0
Suppose you have an UIImage with dimensions 768 x 592 of width and height respectively, the height always remains equals, where the device it's rotated for example in the dimensions above (iPad), the width of the image change to 1024 and the height remains equal.
What you can do to maintain the aspect of the image is scale it in the dimensions you want, for example if you know that the images coming always have the same dimensions, we say for example 1280x740 , you can set the UIImage to .ScaleToFill and calculate in the following way:
(widthOfTheImage / heightOfTheImage) * heightWhereYouWanToSet = widthYouHaveToSet
For example :
(1280 / 740) * 592 = 1024
And it's the width I have to set in my UIImage to maintain the proportions of the image when it's change it's width.
I hope you understand where I try to say to you.

Resources