Programmatic Autolayout changes behaviour of a UIView - ios

I have a controller where I add a subview programmatically. With the configuration of the subview I add autolayout constraints programmatically. Everthing is working except that the view doesn't react on touches if I add the constraints and even the set backgroundcolor is not displayed.
The buttonView should be displayed in the lower right corner of my parent view.
Any ideas what could be wrong?
Here is how I add my constraints:
private func configureAutolayoutConstraints(buttonView: UIView, parentView: UIView){
buttonView.translatesAutoresizingMaskIntoConstraints = false
let bottomConstraint = NSLayoutConstraint(item: buttonView, attribute:
.Bottom, relatedBy: .Equal, toItem: parentView, attribute: .Bottom, multiplier: 1.0, constant: -130)
parentView.addConstraint(bottomConstraint)
let trailingConstraint = NSLayoutConstraint(item: buttonView, attribute:
.Trailing, relatedBy: .Equal, toItem: parentView, attribute: .Trailing, multiplier: 1.0, constant: -90)
parentView.addConstraint(trailingConstraint)
}

Autolayout engine needs at least 4 constraints to determine the frame of view. You have applied bottom and trailing constraints only. You need either width+height OR leading+top constraint to make it work.

Related

Wrong NSLayoutConstraint Causes Black Screen

I want to place header view on top of screen with NSLayoutConstraint (I must use NSLayoutConstraint). When I do it like in below code, view places corruptly in somewhere else and also controllers background color turns black and nothing works. Where am I doing wrong?
I searched below posts for not opening a duplicate post but nothing fixed it:
Programmatically creating constraints bound to view controller margins
Programmatically Add CenterX/CenterY Constraints
EDIT: This controller is inside navigation controller but I'm not sure If It is related.
override func viewDidLoad(){
self.view.backgroundColor = UIColor.white
boxView.backgroundColor = Color.Common.welcomeScreenBackgroundColor.withAlphaComponent(0.5)
boxView.translatesAutoresizingMaskIntoConstraints = false
self.view.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubView(boxView)
}
override func viewDidLayoutSubviews() {
//Header = 20 from left edge of screen
let cn1 = NSLayoutConstraint(item: boxView, attribute: .leading, relatedBy: .equal, toItem: self.view, attribute: .leading, multiplier: 1.0, constant: 0)
//Header view trailing end is 20 px from right edge of the screen
let cn2 = NSLayoutConstraint(item: boxView, attribute: .trailing, relatedBy: .equal, toItem: self.view, attribute: .trailing, multiplier: 1.0, constant: 0)
//Header view height = constant 240
let cn3 = NSLayoutConstraint(item: boxView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant:240)
//Header view vertical padding from the top edge of the screen = 20
let cn5 = NSLayoutConstraint(item: boxView, attribute: .top, relatedBy: .equal, toItem: self.topLayoutGuide, attribute: .bottom, multiplier: 1.0, constant: 0)
self.view.addConstraints([cn1,cn2,cn3,cn5])
}
The problem was setting translatesAutoresizingMaskIntoConstraints to false on Superview. So I deleted the;
self.view.translatesAutoresizingMaskIntoConstraints = false
and this solves the problem. I think this causes app creates constraint for superview.

UIScrollView dynamic height content (autolayout) not filling whole space

I am creating a small chat app in which I use a custom subclass of UIScrollView to present the messages. This app is just for practicing so I don't want to use a third party library. I am implementing this UIScrollView via autolayout, after reading the technical note 2154 by Apple and several tutorials explaining this, and my implementation is almost working but the content view of my UIScrollView doesn't seem to fill all the space available.
The code which presents the ScrollView is:
public class ChatView: UIScrollView {
private var contentView: UIView
...
// This get called by all the init methods. contentView is already created ( contentView = UIView() )
private func setupViews() {
self.translatesAutoresizingMaskIntoConstraints = false
contentView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(contentView)
let constraint1 = NSLayoutConstraint(item: self, attribute: .Top, relatedBy: .Equal, toItem: contentView, attribute: .Top, multiplier: 1.0, constant: 0.0)
let constraint2 = NSLayoutConstraint(item: self, attribute: .Bottom, relatedBy: .Equal, toItem: contentView, attribute: .Bottom, multiplier: 1.0, constant: 0.0)
let constraint3 = NSLayoutConstraint(item: self, attribute: .Width, relatedBy: .Equal, toItem: contentView, attribute: .Width, multiplier: 1.0, constant: 0.0)
let constraint4 = NSLayoutConstraint(item: self, attribute: .CenterX, relatedBy: .Equal, toItem: contentView, attribute: .CenterX, multiplier: 1.0, constant: 0.0)
self.addConstraints([constraint1, constraint2, constraint3, constraint4])
self.layoutIfNeeded()
// Later, the messages are added to the contentView. I don't think is relevant to see the exact code (but I can post it if needed)
// Each message is added using autolayout and the constraints only reference the messages themselves and contentView
}
}
When I add a ChatView to my view controller (using storyboards), with its four sides pinned to views which are not in his hierarchy, the following problem happens:
In the image, the scrollView cannot be scrolled upwards any more. There seem to be a space which should be filled and isn't. If I scroll down, I have the exact same problem but the empty space is below the content. In the following images you can see that the contentView is smallest than the ChatView itself:
And the same view hierarchy but with the constraints shown:
In both images the view in the background is the ChatView and the selected one is the contentView. I haven't been able to figure why the content view doesn't cover the full ChatView space.
Thanks in advance!
I finally stumbled upon the answer while searching a different problem, in another stackoverflow question. The key is to open the storyboard and set in the container view controller "AdjustsScrollViewInsets" to "NO".
In code it's simply (inside the view controller):
self.automaticallyAdjustsScrollViewInsets = NO;

Horizontal UIScrollView autolayout constraint localization

I have multiple buttons separated by some 10px padding (leadingPadding), in case of English the items must start from left to right, and in case of Arabic from right to left and centralized.
This is the code used to draw the scrollView:
var previousView: UIView = self.itemsScrollView!
for i in 0..<self.items.count {
let sectionButton = UIButton(frame: CGRectZero)
sectionButton.titleLabel.text = "i = " + i
sectionButton.translatesAutoresizingMaskIntoConstraints = false
self.itemsScrollView!.addSubview(sectionButton)
// Left Constraint
let leftConstraint = NSLayoutConstraint(
item: sectionButton,
attribute: .Leading,
relatedBy: .Equal,
toItem: toView,
attribute: (toView === self.itemsScrollView!) ? .Leading : .Trailing,
multiplier: 1.0,
constant: leadingPadding);
self.itemsScrollView! .addConstraint(leftConstraint)
// Top Constraint
let topConstraint = NSLayoutConstraint(
item: sectionButton,
attribute: .Top,
relatedBy: .Equal,
toItem: self.itemsScrollView!,
attribute: .Top,
multiplier: 1.0,
constant: self.itemPaddingTopBottom);
self.itemsScrollView! .addConstraint(topConstraint)
// Width Constraint
let widthConstraint = NSLayoutConstraint(
item: sectionButton,
attribute: .Width,
relatedBy: .Equal,
toItem: nil,
attribute: .NotAnAttribute,
multiplier: 1.0,
constant: buttonWidth);
self.itemsScrollView! .addConstraint(widthConstraint)
// Height Constraint
let heightConstraint = NSLayoutConstraint(
item: sectionButton,
attribute: .Height,
relatedBy: .Equal,
toItem: nil,
attribute: .NotAnAttribute,
multiplier: 1.0,
constant: itemHeight);
self.itemsScrollView! .addConstraint(heightConstraint)
previousView = sectionButton
}
// add Trailing constraint on scroll
if (previousView !== self.itemsScrollView!) {
let leftConstraint = NSLayoutConstraint(
item: previousView,
attribute: .Trailing,
relatedBy: .Equal,
toItem: self.itemsScrollView!,
attribute: .Trailing,
multiplier: 1.0,
constant: 0.0);
self.itemsScrollView! .addConstraint(leftConstraint)
}
It works great when the language orientation is LTR.
And a weird behavior when the language orientation is RTL (They need to be centralized like the picture above)
I'm assuming this is something related to the Trailing constraint with the scrollView but I'm not sure. If yes, is there any way to fix that?
Thanks
You should not add multiple views directly in scrollview. Instead you should add a single container view ,horizontally centered in scrollview, which should include all the necessary subview. The width of the container view must be determined by its subviews, so the first item leading is aligned with container leading and the last item trailing is aligned with container trailing in LTR mode or first item trailing is aligned with container trailing and last item leading is aligned with container leading in RTL mode

How to add a constraint programmatically so that the height of a view just fits the bottom of its last subview?

I have constraint code that lays out a number of UILabels (4+) vertically from top to bottom inside a container view (a regular UIView). I now want my container view to be sized so that its height matches the bottom of that last label that I've added.
I have tried this:
let constraintPanelHeight = NSLayoutConstraint(item: cell.panelOptions,
attribute: .Height, relatedBy: .Equal, toItem: priorLabel!,
attribute: .Bottom, multiplier: 1, constant: 0)
cell.contentView.addConstraint(constraintPanelHeight)
but this generates an Invalid pairing of layout attributes error since I'm matching .Height of one view with .Bottom of a subview (I'm guess that's why).
How can I auto-size my containing view like this?
I don't know what your trying to achieve there exactly. If you want to pin the one cell to the bottom of the other, you'll have to use attribute .Top and another constraint for the height. For example: you have 10 labels in your view, then set your height to one tenth of the superview:
let heightConstraint = NSLayoutConstraint(item: cell.panelOptions, attribute: .Height, relatedBy: .Equal, toItem: superview, attribute: .Height, multiplier: 0.1, constant: 0)
But since iOS9 there's also the UIStackView which makes laying out subviews in a view (vertically or horizontally) very easy. Have a look at that if you want to spread your labels evenly in your superview.
If you are fixed number of views the easiest and most readable way is the use the visual layout format and constraints like this:
NSLayoutConstraint.constraints
WithVisualFormat("V:|[view1][view2][view3]|",
options: NSLayoutFormatOptions.AlignAllCenterX,
metrics: nil,
views: ["view1": view1, "view2": view2,"view3": view3])
If you want to add a variable number the principle is the same but achieved with a loop. You shouldn't set the height of anything. Let the intrinsicContentSize of each label size the container from the inside. You might need to set the priority of contentCompressionResistance of each label to 1000 just to be sure the labels don't get squashed. Keep in mind the you will need horizontal constraints as well but they should be simpler to work out.
And here's the version for a variable number of subviews:
var prevView : UIView?
for view in views{
container.addSubview(view)
//
// Add horizontal constraints for each view to fit the container
// exclude for simplicity
// Add vertical constraints
if prevView == nil{
container.addConstraint(NSLayoutConstraint(item: container,
attribute: .Top,
relatedBy: .Equal,
toItem: view,
attribute: .Top,
multiplier: 1, constant: 0)
)
} else if view == views.last!{
container.addConstraint(NSLayoutConstraint(item: container,
attribute: .Bottom,
relatedBy: .Equal,
toItem: view,
attribute: .Bottom,
multiplier: 1,
constant: 0))
} else if let prev = prevView {
container.addConstraint(NSLayoutConstraint(item: prev,
attribute: .Bottom,
relatedBy: .Equal,
toItem: view,
attribute: .Top,
multiplier: 1,
constant: 0))
}
prevView = view
}

How to programatically add constraints on uinavigationbar subview

I'm adding a subview to navigationbar , problem is that im unable to add constraints to it .Im getting crash like this
terminating app due to uncaught exception 'NSGenericException', reason: 'Unable to activate constraint with items ; value: 0.000000> and > because they have no common ancestor. Does the constraint reference items in different view hierarchies? That's illegal.'
The code used is below
//create a slider and add it to the view
let slider = UISlider()
self.navigationController?.navigationBar.addSubview(slider)
//pin the slider 20 points from the left edge of the the superview
//from the left edge of the slider to the left edge of the superview
//superview X coord is at 0 therefore 0 + 20 = 20 position
let horizonalContraints = NSLayoutConstraint(item: slider, attribute:
.LeadingMargin, relatedBy: .Equal, toItem: view,
attribute: .LeadingMargin, multiplier: 1.0,
constant: 20)
//pin the slider 20 points from the right edge of the super view
//negative because we want to pin -20 points from the end of the superview.
//ex. if with of super view is 300, 300-20 = 280 position
let horizonal2Contraints = NSLayoutConstraint(item: slider, attribute:
.TrailingMargin, relatedBy: .Equal, toItem: view,
attribute: .TrailingMargin, multiplier: 1.0, constant: -20)
//pin 100 points from the top of the super
let pinTop = NSLayoutConstraint(item: slider, attribute: .Top, relatedBy: .Equal,
toItem: view, attribute: .Top, multiplier: 1.0, constant: 100)
//when using autolayout we an a view, MUST ALWAYS SET setTranslatesAutoresizingMaskIntoConstraints
//to false.
slider.translatesAutoresizingMaskIntoConstraints = false
slider.backgroundColor = UIColor.redColor()
//IOS 8
//activate the constrains.
//we pass an array of all the contraints
NSLayoutConstraint.activateConstraints([horizonalContraints, horizonal2Contraints,pinTop])
The above code works fine if i use the line view.addSubview(slider)
instead of
self.navigationController?.navigationBar.addSubview(slider)
But the idea is to add constraints on a subview on navigation bar .
Any thoughts are welcome
As the exception already stated, the navigationBar is not a subview of 'view'. It belongs to the navigationcontroller.
What you could do is to use the navbar's superview:
let slider = UISlider()
self.navigationController?.navigationBar.addSubview(slider)
let targetView = self.navigationController?.navigationBar.superview
let horizonalContraints = NSLayoutConstraint(item: slider, attribute:
.LeadingMargin, relatedBy: .Equal, toItem: targetView,
attribute: .LeadingMargin, multiplier: 1.0,
constant: 20)
let horizonal2Contraints = NSLayoutConstraint(item: slider, attribute:
.TrailingMargin, relatedBy: .Equal, toItem: targetView,
attribute: .TrailingMargin, multiplier: 1.0, constant: -20)
let pinTop = NSLayoutConstraint(item: slider, attribute: .Top, relatedBy: .Equal,
toItem: targetView, attribute: .Top, multiplier: 1.0, constant: 10)
slider.translatesAutoresizingMaskIntoConstraints = false
slider.backgroundColor = UIColor.redColor()
NSLayoutConstraint.activateConstraints([horizonalContraints, horizonal2Contraints,pinTop])
That removes the exception and might look like it does what you want, but it is definitely not a good solution. If you want the slider inside the navbar, add it to the navigationitem instead. If you want it bellow the navbar, add it to your View and set a constraint to the top layout guide.

Resources