I've got a situation where I would like a view to be centered in its superview, remain square, but fill as much height as possible without going off the edge, i.e., it should look at the available vertical and horizontal space, choosing the smallest between the 2.
There are 2 other views, one below and one above, that will both be either a button or label. The bottom/top of these views should be attached to the top/bottom of the central view. I can get this to work, to an extent, but I'll explain my issue below, and what I've got so far:
Top label has:
.Top >= TopLayoutGuide.Bottom
.Top = TopLayoutGuide.Bottom (priority 250)
.Right = CentralView.Right
Central view has:
Center X and Y = Superview Center X and Y
.Height <= Superview.Width * 0.9
.Width = self.Height
.Top = TopLabel.Bottom
Bottom button has:
.Right = CentralView.Right
.Top = CentralView.Bottom
.Bottom <= (BottomLayoutGuide.Top - 16)
Running this seems fine, and produces the desired results:
However, if I make the view an instance of my custom class and add a UIButton subview, it all goes wrong. In this class I perform:
self.topLeftButton = CustomButtonClass()
self.topLeftButton.setTranslatesAutoresizingMaskIntoConstraints(false)
self.addSubview(self.topLeftButton)
self.addConstraints([
NSLayoutConstraint(item: self.topLeftButton, attribute: .Top, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1, constant: 0),
NSLayoutConstraint(item: self.topLeftButton, attribute: .Left, relatedBy: .Equal, toItem: self, attribute: .Left, multiplier: 1, constant: 0),
NSLayoutConstraint(item: self.topLeftButton, attribute: .Height, relatedBy: .Equal, toItem: self, attribute: .Height, multiplier: 0.5, constant: 0),
NSLayoutConstraint(item: self.topLeftButton, attribute: .Width, relatedBy: .Equal, toItem: self.topLeftButton, attribute: .Width, multiplier: 1, constant: 0)
])
Using this code the view collapses down to the following:
I can't figure out why this is. I've made a few small tweaks here and there, but not managed to get it to work as desired. If I add the same button in IB the view wants to collapse again, and it's as if the button will not grow in height.
In real life I wouldn't subclass UIButton, but have done in my answer, as that is what the question indicated. UIButton works best through composition. So maybe better to create a UIButton, then modify its properties.
class FooViewController: UIViewController {
override func viewDidLoad() {
var view = CustomView()
view.backgroundColor = UIColor.darkGrayColor()
var label = UILabel()
label.text = "Label"
var button = UIButton.buttonWithType(.System) as UIButton
button.setTitle("Button", forState: .Normal)
view.setTranslatesAutoresizingMaskIntoConstraints(false)
label.setTranslatesAutoresizingMaskIntoConstraints(false)
button.setTranslatesAutoresizingMaskIntoConstraints(false)
self.view.addSubview(view)
self.view.addSubview(label)
self.view.addSubview(button)
// The width should be as big as possible...
var maxWidthConstraint = NSLayoutConstraint(item: view, attribute: .Width, relatedBy: .Equal, toItem: view.superview, attribute: .Width, multiplier: 1, constant: 0);
// ... but not at the expense of other constraints
maxWidthConstraint.priority = 1
self.view.addConstraints([
// Max width, if possible
maxWidthConstraint,
// Width and height can't be bigger than the container
NSLayoutConstraint(item: view, attribute: .Width, relatedBy: .LessThanOrEqual, toItem: view.superview, attribute: .Width, multiplier: 1, constant: 0),
NSLayoutConstraint(item: view, attribute: .Width, relatedBy: .LessThanOrEqual, toItem: view.superview, attribute: .Height, multiplier: 1, constant: 0),
// Width and height are equal
NSLayoutConstraint(item: view, attribute: .Height, relatedBy: .Equal, toItem: view, attribute: .Width, multiplier: 1, constant: 0),
// View is centered
NSLayoutConstraint(item: view, attribute: .CenterX, relatedBy: .Equal, toItem: view.superview, attribute: .CenterX, multiplier: 1, constant: 0),
NSLayoutConstraint(item: view, attribute: .CenterY, relatedBy: .Equal, toItem: view.superview, attribute: .CenterY, multiplier: 1, constant: 0),
])
// Label above view
self.view.addConstraints([
NSLayoutConstraint(item: label, attribute: .Top, relatedBy: .GreaterThanOrEqual, toItem: label.superview, attribute: .Top, multiplier: 1, constant: 0),
NSLayoutConstraint(item: label, attribute: .Bottom, relatedBy: .Equal, toItem: view, attribute: .Top, multiplier: 1, constant: 0),
NSLayoutConstraint(item: label, attribute: .Right, relatedBy: .LessThanOrEqual, toItem: view, attribute: .Right, multiplier: 1, constant: 0),
])
// Button below view
self.view.addConstraints([
NSLayoutConstraint(item: button, attribute: .Bottom, relatedBy: .LessThanOrEqual, toItem: button.superview, attribute: .Bottom, multiplier: 1, constant: 0),
NSLayoutConstraint(item: button, attribute: .Top, relatedBy: .Equal, toItem: view, attribute: .Bottom, multiplier: 1, constant: 0),
NSLayoutConstraint(item: button, attribute: .Right, relatedBy: .LessThanOrEqual, toItem: view, attribute: .Right, multiplier: 1, constant: 0),
])
}
}
class CustomView: UIView {
required init(coder: NSCoder) {
super.init(coder: coder)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
override init() {
super.init()
var button = CustomButton()
button.setTitle("Custom Button", forState: UIControlState.Normal)
button.setTranslatesAutoresizingMaskIntoConstraints(false)
self.addSubview(button)
// Custom button in the top left
self.addConstraints([
NSLayoutConstraint(item: button, attribute: .Top, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1, constant: 0),
NSLayoutConstraint(item: button, attribute: .Left, relatedBy: .Equal, toItem: self, attribute: .Left, multiplier: 1, constant: 0),
])
}
}
class CustomButton: UIButton {
required init(coder: NSCoder) {
super.init(coder: coder)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
override init() {
super.init()
self.backgroundColor = UIColor.greenColor()
}
}
Related
I have an app which is downloading image from server by clicking the button. After image have downloaded i create a new imageView and add it to the my contentView(UIView). I need to create the constraints - every new imageview need top constraint from previous one
func addNewImageToTheScrollView(img: UIImage?) {
if let imageResponse = img {
let imageView = UIImageView(image: imageResponse.crop(rect: CGRect(x: 0, y: imageResponse.size.height/2, width: self.contentView.frame.width, height: 200)))
self.contentView.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
let x = NSLayoutConstraint(item: imageView, attribute: .centerX, relatedBy: .equal, toItem: self.contentView, attribute: .centerX, multiplier: 1.0, constant: 0)
let y = NSLayoutConstraint(item: imageView, attribute: .top, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 30)
let width = NSLayoutConstraint(item: imageView, attribute: .width, relatedBy: .equal, toItem: self.contentView, attribute: .width, multiplier: 1.0, constant: 0)
let height = NSLayoutConstraint(item: imageView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 150)
self.contentView.addConstraints([x, y])
imageView.addConstraints([width, height])
}
}
If i comment the constraint code, it will be work fine unless every new imageView will be on the same place, on the top of the View. Now whit this constraint code i have such code issue after downloading
2017-07-02 14:50:01.018 ImageFromServerTest[11516:1080948] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'NSLayoutConstraint for >: A multiplier of 0 or a nil second item together with a location for the first attribute creates an illegal constraint of a location equal to a constant. Location attributes must be specified in pairs.'
Whenever you are working with scrollViews, there are 2 thumb rules for it:-
Give the scrollView leading, trailing, bottom, and top constraint with respect to the superview, that is self.view
#IbOutlet weak var scrollView: UIScrollView!
let leading = NSLayoutConstraint(item: scrollView, attribute: .leading, relatedBy: .equal, toItem: self.view, attribute: .leadingMargin, multiplier: 1.0, constant: 0)
let trailing = NSLayoutConstraint(item: scrollView, attribute: .trailing, relatedBy: .equal, toItem: self.view, attribute: .trailingMargin, multiplier: 1.0, constant: 0)
let top = NSLayoutConstraint(item: scrollView, attribute: .top, relatedBy: .equal, toItem: self.view, attribute: .top, multiplier: 1.0, constant: 0)
let bottom = NSLayoutConstraint(item: scrollView, attribute: .bottom, relatedBy: .equal, toItem: self.view, attribute: .bottom, multiplier: 1.0, constant: 0)
Add constraints to the contentView with respect to the scrollView
#IbOutlet weak var contentView: UIView!
let leading = NSLayoutConstraint(item: contentView, attribute: .leading, relatedBy: .equal, toItem: scrollView, attribute: .leading, multiplier: 1.0, constant: 0)
let trailing = NSLayoutConstraint(item: contentView, attribute: .trailing, relatedBy: .equal, toItem: scrollView, attribute: .trailing, multiplier: 1.0, constant: 0)
let top = NSLayoutConstraint(item: contentView, attribute: .top, relatedBy: .equal, toItem: scrollView, attribute: .top, multiplier: 1.0, constant: 0)
//increase the constant according to how much long you need the scrollview to be
let bottom = NSLayoutConstraint(item: contentView, attribute: .bottom, relatedBy: .equal, toItem: scrollView, attribute: .bottom, multiplier: 1.0, constant: 0)
Now add your subviews constraints (labels, images) with respect to the contentView
For example- You received your first image, so we will maintain an array of UIImageViews outside your function.
var imageViews = [UIImageViews]() //declared outside the function
//received an image here
var imageView = UIImageView() // define the frame according to yourself using frame init method
imageView.image = image
if imageViews.isEmpty { // first image view
//add constraints to first image view
let x = NSLayoutConstraint(item: imageView, attribute: .centerX, relatedBy: .equal, toItem: contentView, attribute: .centerX, multiplier: 1.0, constant: 0)
let y = NSLayoutConstraint(item: imageView, attribute: .top, relatedBy: .equal, toItem: contentView, attribute: .top, multiplier: 1.0, constant: 30)
let width = NSLayoutConstraint(item: imageView, attribute: .width, relatedBy: .equal, toItem: self.contentView, attribute: .width, multiplier: 1.0, constant: 0)
let height = NSLayoutConstraint(item: imageView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 150)
}
else { //second, third, fourth image view... so on
let x = NSLayoutConstraint(item: imageView, attribute: .centerX, relatedBy: .equal, toItem: contentView, attribute: .centerX, multiplier: 1.0, constant: 0)
let y = NSLayoutConstraint(item: imageView, attribute: .top, relatedBy: .equal, toItem: imageViews[imageViews.count - 1], attribute: .bottom, multiplier: 1.0, constant: 30)
let width = NSLayoutConstraint(item: imageView, attribute: .width, relatedBy: .equal, toItem: self.contentView, attribute: .width, multiplier: 1.0, constant: 0)
let height = NSLayoutConstraint(item: imageView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 150)
}
imageViews.append(imageView)
}
Hope you got an idea now, how to proceed with this problem. If having more than 4 or 5 imageviews, you'll probably want to check the count of the array and increase the contentView of the scrollView accordingly. you can do so by using
self.scrollView.contentSize = CGSize(width, height)
I believe you have a problem here:
let y = NSLayoutConstraint(item: imageView, attribute: .top, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 30)
"toItem" parameter should have some value. Also first parameter should be imageView.topAnchor. Probably it should look something like this:
let y = NSLayoutConstraint(item: imageView.topAnchor, attribute: .top, relatedBy: .equal, toItem: self.contentView.topAnchor, attribute: .notAnAttribute, multiplier: 1.0, constant: 30)
I'm building a UICollectionViewCell interface by code. Everything works fine, except the right constraint. When I run the app the title label has a zero margin from the right. Here is my code
let titleLabel: UILabel = {
let label = UILabel()
label.backgroundColor = UIColor.purple
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
func setupViews() {
addSubview(titleLabel)
// titleLabel
// top constraint
addConstraint(NSLayoutConstraint(item: titleLabel, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: 16))
// left constraint
addConstraint(NSLayoutConstraint(item: titleLabel, attribute: .left, relatedBy: .equal, toItem: self, attribute: .left, multiplier: 1, constant: 8))
// right constraint
addConstraint(NSLayoutConstraint(item: titleLabel, attribute: .right, relatedBy: .equal, toItem: self, attribute: .right, multiplier: 1, constant: 16))
// height constraint
addConstraint(NSLayoutConstraint(item: titleLabel, attribute: .height, relatedBy: .equal, toItem: self, attribute: .height, multiplier: 0, constant: 20))
}
I'm thinking it has something to do with toItem: self, because self is the uilabel and I want to related it the UICollectionViewCell
The problem is the order of your items in your constraint. You are currently saying the label is 16 past the right edge of its superview. You can either switch the item and toItem in your right constraint, or use -16 as your constant.
So either this:
addConstraint(NSLayoutConstraint(item: self, attribute: .right, relatedBy: .equal, toItem: titleLabel, attribute: .right, multiplier: 1, constant: 16))
or this:
addConstraint(NSLayoutConstraint(item: titleLabel, attribute: .right, relatedBy: .equal, toItem: self, attribute: .right, multiplier: 1, constant: -16))
will work.
I know this question has been asked numerous times, but I can't quite seem to get to the bottom of this problem.
Using Auto Layout, I would like to automatically set the height of my container UIView based on its subviews. I have looked at using sizeToFit and other various methods of summing up the height of my subviews, however from what I've read the height of my container height should be automatic when using Auto Layout because of the subviews "intrinsic" content size.
Below is a reduced case of what I'm experiencing. I would really appreciate any guidance!
Overview:
Create container UIView, pin to left and right sides of superview, no explicit height, align its centerY with its superview centerY
Create a 300 width by 100 height UIView, add it as a subview to container view, align its centerX with container view's centerX, pin to container view's top edge
Repeat step #2, except this time pin its top to #2's bottom edge
The expected height of the container view is 200, except its height is actually still 0 (therefor centerY alignment is off)
Code:
class ViewController: UIViewController {
let redView = RedView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(redView)
view.setNeedsUpdateConstraints()
}
}
class RedView: UIView {
let greenView = GreenView()
let blueView = BlueView()
init() {
super.init(frame: CGRect.zero)
translatesAutoresizingMaskIntoConstraints = false
backgroundColor = UIColor.red()
addSubview(greenView)
addSubview(blueView)
setNeedsUpdateConstraints()
}
override func updateConstraints() {
super.updateConstraints()
NSLayoutConstraint(item: greenView, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: blueView, attribute: .top, relatedBy: .equal, toItem: greenView, attribute: .bottom, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: self, attribute: .left, relatedBy: .equal, toItem: superview, attribute: .left, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: self, attribute: .right, relatedBy: .equal, toItem: superview, attribute: .right, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: self, attribute: .centerY, relatedBy: .equal, toItem: superview, attribute: .centerY, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 200).isActive = true
}
}
class GreenView: UIView {
init() {
super.init(frame: CGRect.zero)
translatesAutoresizingMaskIntoConstraints = false
backgroundColor = UIColor.green()
}
override func updateConstraints() {
super.updateConstraints()
NSLayoutConstraint(item: self, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 300).isActive = true
NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 100).isActive = true
NSLayoutConstraint(item: self, attribute: .centerX, relatedBy: .equal, toItem: superview, attribute: .centerX, multiplier: 1, constant: 0).isActive = true
}
}
class BlueView: UIView {
init() {
super.init(frame: CGRect.zero)
translatesAutoresizingMaskIntoConstraints = false
backgroundColor = UIColor.blue()
}
override func updateConstraints() {
super.updateConstraints()
NSLayoutConstraint(item: self, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 300).isActive = true
NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 100).isActive = true
NSLayoutConstraint(item: self, attribute: .centerX, relatedBy: .equal, toItem: superview, attribute: .centerX, multiplier: 1, constant: 0).isActive = true
}
}
You need to pin blueView's bottom to redView's bottom, just add this line to redView's updateConstraints:
NSLayoutConstraint(item: blueView, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1, constant: 0).active = true
I'm trying to programmatically create a UIView with a UILabel as a subview using autolayout.
Constraints
I used the following constraints on view:
CenterX of view to fastAttacksContainerView (superview).
Top constraint of constant 8 to superview.
Then created a label which is a subview of view and added constraints of constant 8 for Top, Bottom, Left, and Right to view.
Problem
The view only resizes to the frame of the label and does not account for the 4 constraints of constant 8 on all 4 sides. Which causes the label to be displayed partially outside the view.
let view = UIView()
view.backgroundColor = pokemon.secondaryColor
let label = UILabel()
fastAttacksContainerView.addSubview(view)
view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
label.text = fa
let gest = UITapGestureRecognizer(target: self, action: #selector(self.selectFastAttack))
view.addGestureRecognizer(gest)
fastAttackButtons.append(view)
fastAttackLables.append(label)
let top = NSLayoutConstraint(item: view, attribute: .Top, relatedBy: .Equal, toItem: fastAttacksContainerView, attribute: .Top, multiplier: 1, constant: 8)
let centerX = NSLayoutConstraint(item: view, attribute: .CenterX, relatedBy: .Equal, toItem: fastAttacksContainerView, attribute: .CenterX, multiplier: 1, constant: 0)
let labLeft = NSLayoutConstraint(item: label, attribute: .Left, relatedBy: .Equal, toItem: view, attribute: .Left, multiplier: 1, constant: 8)
let labTop = NSLayoutConstraint(item: label, attribute: .Top, relatedBy: .Equal, toItem: view, attribute: .Top, multiplier: 1, constant: 8)
let labRigth = NSLayoutConstraint(item: label, attribute: .Right, relatedBy: .Equal, toItem: view, attribute: .Right, multiplier: 1, constant: 8)
let labBottom = NSLayoutConstraint(item: label, attribute: .Bottom, relatedBy: .Equal, toItem: view, attribute: .Bottom, multiplier: 1, constant: 8)
view.addConstraints([labLeft, labTop, labRigth, labBottom])
fastAttacksContainerView.addConstraints([top, centerX])
Output
view has ambiguous height.
You should add constraint for height of view or distance from it to bottom of fastAttacksContainerView.
I managed to fix this problem by making a similar setup on storyboard and copying the constraints in.
Things that I changed:
Used Leading and Trailing instead of Left and Right layout attributes.
For the labLeft and labRight constraints, I interchanged item and toItem. (This seems like a bug to me. Can anyone verify, please?)
Code change:
let labLeft = NSLayoutConstraint(item: label, attribute: .Leading, relatedBy: .Equal, toItem: view, attribute: .Leading, multiplier: 1, constant: 8)
let labTop = NSLayoutConstraint(item: label, attribute: .Top, relatedBy: .Equal, toItem: view, attribute: .Top, multiplier: 1, constant: 8)
let labRigth = NSLayoutConstraint(item: view, attribute: .Trailing, relatedBy: .Equal, toItem: label, attribute: .Trailing, multiplier: 1, constant: 8)
let labBottom = NSLayoutConstraint(item: view, attribute: .Bottom, relatedBy: .Equal, toItem: label, attribute: .Bottom, multiplier: 1, constant: 8)
I am trying to add a overlay over for all my viewcontrollers by adding this code to my "BaseViewController". However it result in all ViewControllers turning black and behaving oddly.
override public func viewDidLoad()
{
super.viewDidLoad()
overlayView = UIView()
overlayView.backgroundColor = UIColor.redColor() //For testing
view.addSubviewWithMatchingConstraints(overlayView)
...
}
And in UIView extension:
func addSubviewWithMatchingConstraints(subView: UIView)
{
translatesAutoresizingMaskIntoConstraints = false
addSubview(subView)
addConstraint(NSLayoutConstraint(item: subView, attribute: .Width, relatedBy: .Equal, toItem: self, attribute: .Width, multiplier: 1, constant: 0))
addConstraint(NSLayoutConstraint(item: subView, attribute: .Height, relatedBy: .Equal, toItem: self, attribute: .Height, multiplier: 1, constant: 0))
addConstraint(NSLayoutConstraint(item: subView, attribute: .CenterX, relatedBy: .Equal, toItem: self, attribute: .CenterX, multiplier: 1.0, constant: 0))
addConstraint(NSLayoutConstraint(item: subView, attribute: .CenterY, relatedBy: .Equal, toItem: self, attribute: .CenterY, multiplier: 1.0, constant: 0))
}
The issue was that I set translatesAutoresizingMaskIntoConstraints on the parent rather than the child.
childView.translatesAutoresizingMaskIntoConstraints = false
fixed it