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
Related
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.
I see many solution and applied auto layout constrains but still i am not getting desired solution.
Current Result:
Constrains given to all button:
View Hierarchy:
Assuming that what do you want is letting the 3 buttons equal each others and filling the width of screen using the Interface Builder (with no code, i.e: not programmatically), this is a solution:
you don't need to add them in views. just follow these steps:
let's start with this:
note that the buttons don't have any constraints -yet-, just add them to the to the bottom of the view (or where ever you want to display them, for this solution, I will display them in bottom of the screen). Make sure that the 3 of them have the same size.
Adding constraints to the orange button:
add the following constraints:
leading, bottom space, and equal height.
Adding constraints to the blue button:
add the following constraints:
trailing, bottom space and equal height.
Adding constraints to the cyan button:
ctrl + drag from the cyan button to the orange button and add the following constraints: horizontal spacing and center vertically.
ctrl + drag from the cyan button to the blue button and add the following constraints: horizontal spacing.
bottom space and equal height.
So far so good, we are almost done!
Now, select the 3 buttons and add the following constraint: equal widths.
Your buttons should look like this:
Now, all you have to do is select each of the two horizontal spacing constraints and set their constants to 0 -from the size inspector-:
AND there you go:
I hope this helped, Cheers Up.
Sorry for solution in code.. .Swift 3.0:
let button1 = UIButton()
let button2 = UIButton()
let button3 = UIButton()
let buttons = [button1, button2, button3]
button1.backgroundColor = .red
button2.backgroundColor = .blue
button3.backgroundColor = .green
buttons.forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
view.addSubview($0)
}
buttons.forEach {
let cnt1 = NSLayoutConstraint(item: $0, attribute: .centerY, relatedBy: .equal, toItem: view, attribute: .centerY, multiplier: 1, constant: 0)
let cnt2 = NSLayoutConstraint(item: $0, attribute: .height, relatedBy: .equal, toItem: view, attribute: .height, multiplier: 0.2, constant: 0)
view.addConstraints([cnt1, cnt2])
}
let cnt1 = NSLayoutConstraint(item: button1, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1, constant: 0)
let cnt2 = NSLayoutConstraint(item: view, attribute: .trailing, relatedBy: .equal, toItem: button3, attribute: .trailing, multiplier: 1, constant: 0)
let cnt3 = NSLayoutConstraint(item: button2, attribute: .width, relatedBy: .equal, toItem: button1, attribute: .width, multiplier: 1, constant: 0)
let cnt4 = NSLayoutConstraint(item: button3, attribute: .width, relatedBy: .equal, toItem: button2, attribute: .width, multiplier: 1, constant: 0)
let cnt5 = NSLayoutConstraint(item: button2, attribute: .leading, relatedBy: .equal, toItem: button1, attribute: .trailing, multiplier: 1, constant: 0)
let cnt6 = NSLayoutConstraint(item: button3, attribute: .leading, relatedBy: .equal, toItem: button2, attribute: .trailing, multiplier: 1, constant: 0)
view.addConstraints([cnt1, cnt2, cnt3, cnt4, cnt5, cnt6])
Are you trying to lay out three buttons horizontally that have equal height and width? I think you can achieve this without using UIViews. I set constraints like as follows.
This is a simulator screenshot.
I'm struggling to implement to make my uilabel to stretch from the left side to right side (Full Width), I can do it easily in storyboard but I have a part on my app that needs to display a uilabel progmatically.
Here's my code:
textLabel.backgroundColor = UIColor.grayColor()
textLabel.text = StringsLabel.NOINTERNET
textLabel.textAlignment = .Center
textLabel.font = UIFont.boldSystemFontOfSize(24.0)
textLabel.translatesAutoresizingMaskIntoConstraints = false
centerView.addSubview(textLabel)
let centreXTopLabel:NSLayoutConstraint = NSLayoutConstraint(item: textLabel, attribute: NSLayoutAttribute.CenterX, relatedBy: NSLayoutRelation.Equal, toItem: centerView, attribute: NSLayoutAttribute.CenterX, multiplier: 1, constant: 0);
centerView.addConstraint(centreXTopLabel)
Here's the screenshot of what i've got:
Pin it to the leading and trailing of the view at 0 points:
let leading = NSLayoutConstraint(item: textLabel, attribute: .Leading, relatedBy: .Equal, toItem: centerView, attribute: .Leading, multiplier: 1, constant: 0)
let trailing = NSLayoutConstraint(item: textLabel, attribute: .Trailing, relatedBy: .Equal, toItem: centerView, attribute: .Trailing, multiplier: 1, constant: 0)
NSLayoutConstraint.activateConstraints([leading, trailing])
Note that this will result in an ambiguous layout — you need to add a constraint that positions the label vertically.
Try this..
let views = ["textLabel" : textLabel]
let formatString = "|-[textLabel]-|"
let constraints = NSLayoutConstraint.constraintsWithVisualFormat(formatString, options:.AlignAllTop , metrics: nil, views: views)
NSLayoutConstraint.activateConstraints(constraints)
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.
I want to add a leading, trailing, bottom and width constraint programmatically to a UISearchController. This is my code:
#IBOutlet weak var navigationBar: UIView!
// create search bar
searchBar = UISearchController(searchResultsController: nil)
navigationBar.addSubview(searchBar.searchBar)
searchBar.searchBar.translatesAutoresizingMaskIntoConstraints = false
let leftConstraint = NSLayoutConstraint(item: searchBar.searchBar, attribute: .Leading, relatedBy: .Equal, toItem: navigationBar, attribute: .Leading, multiplier: 1, constant: 0)
let rightConstraint = NSLayoutConstraint(item: searchBar.searchBar, attribute: .Trailing, relatedBy: .Equal, toItem: navigationBar, attribute: .Trailing, multiplier: 1, constant: 0)
let bottomConstraint = NSLayoutConstraint(item: searchBar.searchBar, attribute: .Bottom, relatedBy: .Equal, toItem: navigationBar, attribute: .Bottom, multiplier: 1, constant: 0)
let heightConstraint = NSLayoutConstraint(item: searchBar.searchBar, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 44)
navigationBar.addConstraints([leftConstraint, rightConstraint, bottomConstraint, widthConstraint])
When running the app, the search bar appears correctly, but when I press on the search bar, it shrinks, and if I press another time the app crashes. Here is the output:
2015-08-12 20:20:37.696 Contacts++[96997:8547485] The view hierarchy is not prepared for the constraint: <NSLayoutConstraint:0x7fb22580c7a0 UIView:0x7fb225817b20.leading == UIView:0x7fb223449860.leading>
When added to a view, the constraint's items must be descendants of that view (or the view itself). This will crash if the constraint needs to be resolved before the view hierarchy is assembled. Break on -[UIView(UIConstraintBasedLayout) _viewHierarchyUnpreparedForConstraint:] to debug.
2015-08-12 20:20:37.697 Contacts++[96997:8547485] *** Assertion failure in -[UIView _layoutEngine_didAddLayoutConstraint:roundingAdjustment:mutuallyExclusiveConstraints:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3491.2.5/NSLayoutConstraint_UIKitAdditions.m:590
Why do you need to create an instance of 'UISearchController' and get its searchBar?
why not just make the searchBar from UISearchBar?
// create a searchBar from UISearchBar
let searchBar = UISearchBar(frame: CGRectZero)
// add searchBar to navigationBar
navigationController?.navigationBar.addSubview(searchBar)
// call sizeToFit.. this will set the frame of the searchBar to exactly the same as the size of the allowable frame of the navigationBar
searchBar.sizeToFit()
// now reframe the searchBar to add some margins
var frame = searchBar.frame
frame.origin.x = 20
frame.size.width -= 40
searchBar.frame = frame // set new frame with margins
note: you won't need any of those constraint to achieve this.
But if you really prefer the constraint, here's the constraint code without a crash.
let searchBar = UISearchBar(frame: CGRectZero)
navigationController?.navigationBar.addSubview(searchBar)
searchBar.setTranslatesAutoresizingMaskIntoConstraints(false)
let leftConstraint = NSLayoutConstraint(item: searchBar, attribute: .Leading, relatedBy: .Equal, toItem: navigationController?.navigationBar, attribute: .Leading, multiplier: 1, constant: 20) // add margin
let bottomConstraint = NSLayoutConstraint(item: searchBar, attribute: .Bottom, relatedBy: .Equal, toItem: navigationController?.navigationBar, attribute: .Bottom, multiplier: 1, constant: 1)
let topConstraint = NSLayoutConstraint(item: searchBar, attribute: .Top, relatedBy: .Equal, toItem: navigationController?.navigationBar, attribute: .Top, multiplier: 1, constant: 1)
let widthConstraint = NSLayoutConstraint(item: searchBar, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: self.view.frame.size.width - 40) // - margins from both sides
navigationController?.navigationBar.addConstraints([leftConstraint, bottomConstraint, topConstraint, widthConstraint])
Do you really want to set the width of the navigatinBar to 44 points? Width is horizontal. You already have a width constraint by adding trailing and leading constraints.