I'm allowing the user to create a new text field when they press a button. I want to programmatically copy the leading and trailing constraints from an existing text field. My code:
#IBAction func addAnotherTextField(sender: AnyObject) {
let newTextField = UITextField.init(frame: CGRectMake(20, positionY, self.view.frame.size.width-40, 30))
newTextField.delegate = self
newTextField.tag = fieldCount
newTextField.placeholder = "You created this!"
newTextField.borderStyle = UITextBorderStyle.RoundedRect
newTextField.translatesAutoresizingMaskIntoConstraints = false
let leadingConstraint = NSLayoutConstraint(item: newTextField, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: nameTextField, attribute: NSLayoutAttribute.Leading, multiplier: 1, constant: 0)
let trailingConstraint = NSLayoutConstraint(item: newTextField, attribute: NSLayoutAttribute.Trailing, relatedBy: NSLayoutRelation.Equal, toItem: nameTextField, attribute: NSLayoutAttribute.Leading, multiplier: 1, constant: 0)
newTextField.addConstraint(leadingConstraint)
newTextField.addConstraint(trailingConstraint)
view.addSubview(newTextField)
fieldCount++
positionY = positionY + 15 + newTextField.frame.size.height
}
}
Unfortunately, the above code crashes at run time.
You need first to addSubView, only then to add the constraints.
You can't connect constraints between UIViews that are not related...
From the docs:
Discussion:
The constraint must involve only views that are within scope of the receiving view. Specifically, any views involved must be either the receiving view itself, or a subview of the receiving view. Constraints that are added to a view are said to be held by that view. The coordinate system used when evaluating the constraint is the coordinate system of the view that holds the constraint.
Related
I have two UIViewControllers with Storyboard identifiers "vc0" and "vc1", which I am trying to instantiate objects of within a UIScrollView with paging enabled such that the two view controllers are adjacent to each other. The goal is to have the user swipe left and right to swipe between view controllers, similar to SnapChat.
When running my code, the first page of the scrollview contains the first view controller, while the second page contains nothing at all. I am assuming that this is because the first view controller is overlapping the second. How can I alter my constraints (or anything at all) to fix this issue, so that the right edge of vc0 meets with the left edge of vc1?
The UIScrollView is contained within a UIView, within a view controller. Here is the viewDidLoad() which contains all the relevant code. Please let me know of any additional helpful information I should provide.
override func viewDidLoad()
{
super.viewDidLoad()
let screenWidth = self.view.frame.width
let screenHeight = self.view.frame.height
let vc0 = self.storyboard?.instantiateViewControllerWithIdentifier("vc0")
self.addChildViewController(vc0!)
self.scrollView.addSubview(vc0!.view)
vc0!.didMoveToParentViewController(self)
let vc1 = self.storyboard?.instantiateViewControllerWithIdentifier("vc1")
var frame1 = vc1!.view.frame
frame1.origin.x = self.view.frame.size.width
vc1!.view.frame = frame1
self.addChildViewController(vc1!)
self.scrollView.addSubview(vc1!.view)
vc1!.didMoveToParentViewController(self)
self.scrollView.contentSize = CGSizeMake(self.view.frame.size.width * 2, self.view.frame.size.height);
//ADD CONSTRAINTS TO vc0
vc0!.view.translatesAutoresizingMaskIntoConstraints = false
let horizontalConstraint = NSLayoutConstraint(item: vc0!.view, attribute: NSLayoutAttribute.Left, relatedBy: NSLayoutRelation.Equal, toItem: scrollView, attribute: NSLayoutAttribute.Left, multiplier: 1, constant: 0)
view.addConstraint(horizontalConstraint)
let widthConstraint = NSLayoutConstraint(item: vc0!.view, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: screenWidth)
view.addConstraint(widthConstraint)
let heightConstraint = NSLayoutConstraint(item: vc0!.view, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: screenHeight)
view.addConstraint(heightConstraint)
//ADD CONSTRAINTS TO vc1
vc1!.view.translatesAutoresizingMaskIntoConstraints = false
let horizontalConstraint2 = NSLayoutConstraint(item: vc1!.view, attribute: NSLayoutAttribute.Left, relatedBy: NSLayoutRelation.Equal, toItem: vc0!.view, attribute: NSLayoutAttribute.Right, multiplier: 1, constant: 0)
view.addConstraint(horizontalConstraint2)
let widthConstraint2 = NSLayoutConstraint(item: vc1!.view, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: screenWidth)
view.addConstraint(widthConstraint)
let heightConstraint2 = NSLayoutConstraint(item: vc1!.view, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: screenHeight)
view.addConstraint(heightConstraint)
}
I think there is a problem in constraints!!
You should add one view to scrollview and then add views of both VC to that view not direct to scrollview.
view's size should be same as scrollview's contentsize
Now your constrains should be like :
scrollview : top,bottom,leading,trailing
View in scrollview : top,bottom,leading,trailing, Vertical center in container(center Y) and fixed width (because you want horizontal scrolling), If want vertical scrolling then constraint should be (horizontal center in container (center X) and fixed height).
And you views from both VCs : top,leading,fixed width, fixed height (width and height should be same as screen size)
Hope this will help :)
I have just started exploring the Uber iOS Sdk. I was checking for RequestButton and have noticed that its not taking any frame which we are passing. Is it possible to put button on desired location of a view?
let button = RequestButton(colorStyle:.White)
view.addSubview(button)
button.frame = CGRectMake(100,300, button.frame.size.width, button.frame.size.height)
It is possible to add the RequestButton in a desired location. The RequestButton is positioned with Auto Layout (or Constraint Based Layout) rather than Frame-Based Layout. See Apple's notes on Auto Layout Versus Frame-Based Layout.
Auto Layout guarantees resizing when the frame changes orientation and for different device sizes. In Auto Layout, you need to place UIViews based on their relative position to other views.
Here is an example of how you would center a RequestButton (button) inside a parent UIView (inView)
func centerButton(forButton button: RequestButton, inView: UIView) {
button.translatesAutoresizingMaskIntoConstraints = false
// position constraints
let horizontalConstraint = NSLayoutConstraint(item: button, attribute: NSLayoutAttribute.CenterX, relatedBy: NSLayoutRelation.Equal, toItem: inView, attribute: NSLayoutAttribute.CenterX, multiplier: 1, constant: 0)
let verticalConstraint = NSLayoutConstraint(item: button, attribute: NSLayoutAttribute.CenterY, relatedBy: NSLayoutRelation.Equal, toItem: inView, attribute: NSLayoutAttribute.CenterY, multiplier: 1, constant: 0)
// add constraints to view
inView.addConstraints([horizontalConstraint, verticalConstraint])
}
There might be an easier way but I found this solution for positioning the Uber RequestButton using frame based layouts.
Place the request button inside a wrapper UIView. Set the constraints on the wrapper view via Christine Kim's answer. Then you can position the wrapper's frame and the RequestButton will move with it
// MyView.swift
let uber = RequestButton()
let uberWrapper = UIView()
uberWrapper.addSubview(uber)
self.addSubview(uberWrapper)
//Set constraints
let horizontalConstraint = NSLayoutConstraint(item: uber, attribute: NSLayoutAttribute.CenterX, relatedBy: NSLayoutRelation.Equal, toItem: uberWrapper, attribute: NSLayoutAttribute.CenterX, multiplier: 1, constant: 0)
let verticalConstraint = NSLayoutConstraint(item: uber, attribute: NSLayoutAttribute.CenterY, relatedBy: NSLayoutRelation.Equal, toItem: uberWrapper, attribute: NSLayoutAttribute.CenterY, multiplier: 1, constant: 0)
// add constraints to view
uberWrapper.addConstraints([horizontalConstraint, verticalConstraint])
//layoutSubviews()
//set your origin
uberWrapper.frame = CGRect(origin: CGPointMake(0, 0), size: uber.intrinsicContentSize())
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'm trying to center my subview with a button in its superview. So I want the center of the subview be the center of the superview. I'm trying that with following code:
override func viewDidLoad() {
self.view.backgroundColor = UIColor.redColor()
var menuView = UIView()
var newPlayButton = UIButton()
//var newPlayImage = UIImage(named: "new_game_button_5cs")
var newPlayImageView = UIImageView(image: UIImage(named: "new_game_button_5cs"))
newPlayButton.frame = CGRectMake(0, 0, newPlayImageView.frame.width, newPlayImageView.frame.height)
newPlayButton.setImage(newPlayImage, forState: .Normal)
newPlayButton.backgroundColor = UIColor.whiteColor()
menuView.backgroundColor = UIColor.whiteColor()
menuView.addSubview(newPlayButton)
menuView.addConstraint(
NSLayoutConstraint(item: self.view,
attribute: .CenterX,
relatedBy: .Equal,
toItem: menuView,
attribute: .CenterX,
multiplier: 1, constant: 0)
)
}
Unfortunately the program breaks when I try to run it.
(Thread 1: signal SIGABRT)
Your code triggers an assertion saying:
When added to a view, the constraint's items must be descendants of
that view (or the view itself).
This means you have to add menuView as a subview to self.view before adding constraints. You should also add the constraints to self.view, not the menuView. Last but not least, remove autoresizing masks constraints that were implicitly added to menuView by calling setTranslatesAutoresizingMaskIntoConstraints(false) or autolayout will complain about conflicting constraints.
menuView.addSubview(newPlayButton)
menuView.setTranslatesAutoresizingMaskIntoConstraints(false)
self.view.addSubview(menuView)
self.view.addConstraint(
NSLayoutConstraint(item: self.view,
attribute: .CenterX,
relatedBy: .Equal,
toItem: menuView,
attribute: .CenterX,
multiplier: 1, constant: 0)
)
I'm trying to place my subview with a left margin based on the width of the parent view. This sounds simple but I can't figure out how to do it using autolayout.
Logically, I would only need to set the left margin value at a certain percentage value of the parent's width but at the moment, I fail to translate that logic to autolayout.
This is my code at the moment:
var view = UIView();
view.backgroundColor = UIColor.redColor();
view.frame = CGRectMake(0, 0, 320, 400);
var sview = UIView();
sview.setTranslatesAutoresizingMaskIntoConstraints(false);
sview.backgroundColor = UIColor.yellowColor();
//sview.frame = CGRectMake(0, 0, 50, 50);
view.addSubview(sview);
var dict = Dictionary<String, UIView>()
dict["box"] = sview;
var con1 = NSLayoutConstraint(item: sview, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.Top, multiplier: 1.0, constant: 20.0);
view.addConstraint(con1);
var con2 = NSLayoutConstraint(item: sview, attribute: NSLayoutAttribute.Left, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.Width, multiplier: 0.75, constant: 0.0);
view.addConstraint(con2);
var con3 = NSLayoutConstraint(item: sview, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.Width, multiplier: 1.0, constant: 0.0);
view.addConstraint(con3);
var con4 = NSLayoutConstraint(item: sview, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.Height, multiplier: 1.0, constant: 0.0);
view.addConstraint(con4);
This is the where the code returns an error:
var con2 = NSLayoutConstraint(item: sview, attribute: NSLayoutAttribute.Left, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.Width, multiplier: 0.75, constant: 0.0);
view.addConstraint(con2);
Error:
* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '* +[NSLayoutConstraint
constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:]:
Invalid pairing of layout attributes'
Does anyone have any idea on how to achieve this? I just want the left margin to be 0.75% of the parent view's width.
Thanks.
What you want is the left of sview to be at some point of the left of view and you are writing that you want the left of sview to be at some point of the width of view which is not a correct pairing of layout attributes as your error says.
Here is what you need to do:
NSLayoutConstraint(item: sview,
attribute: NSLayoutAttribute.Left,
relatedBy: NSLayoutRelation.Equal,
toItem: view,
attribute: NSLayoutAttribute.Left,
multiplier: 1,
constant: (CGRectGetWidth(view.bounds) * 0.75));
Hope it helps!
EDIT
I found a great article about Percented based margins: https://web.archive.org/web/20170624134422/http://simblestudios.com/blog/development/percentage-width-in-autolayout.html
Or even simpler:
https://web.archive.org/web/20170704113819/http://simblestudios.com/blog/development/easier-percentage-width-in-autolayout.html
It is possible to create a percentage-based margins with auto layout constraints between a subview and its superview. The margin will change dynamically as the size of the superview changes. For example, this is how to create a trailing 10% margin.
Create a trailing constraint between your view and its superview.
Make sure the first constraint item is the subview and the second item is the superview. This can be done by clicking on the first item drop down and selecting Reverse First and Second Item.
Change the constant value of the constraint to zero in the attributes inspector.
Change the multiplier value to 0.9.
The is one problem with this manual approach though - it does not work in right-to-left language, Arabic, for example. Right-to-left layouts require a bit different settings for the constraint but we can not keep both in one storyboard.
Here is a library that I wrote that lets you create percentage-based constraints. It does handle the right-to-left language case.
https://github.com/exchangegroup/PercentageMargin
You can subclass NSLayoutConstraint to accept a margin percentage via #IBInspectable. Then subscribe to UIDeviceOrientationDidChangeNotification to run the calculation and stuff into the constant value so it is updated whenever the layout changes.
/// Layout constraint to calculate size based on multiplier.
class PercentLayoutConstraint: NSLayoutConstraint {
#IBInspectable var marginPercent: CGFloat = 0
var screenSize: (width: CGFloat, height: CGFloat) {
return (UIScreen.mainScreen().bounds.width, UIScreen.mainScreen().bounds.height)
}
override func awakeFromNib() {
super.awakeFromNib()
guard marginPercent > 0 else { return }
NSNotificationCenter.defaultCenter().addObserver(self,
selector: #selector(layoutDidChange),
name: UIDeviceOrientationDidChangeNotification,
object: nil)
}
/**
Re-calculate constant based on orientation and percentage.
*/
func layoutDidChange() {
guard marginPercent > 0 else { return }
switch firstAttribute {
case .Top, .TopMargin, .Bottom, .BottomMargin:
constant = screenSize.height * marginPercent
case .Leading, .LeadingMargin, .Trailing, .TrailingMargin:
constant = screenSize.width * marginPercent
default: break
}
}
deinit {
guard marginPercent > 0 else { return }
NSNotificationCenter.defaultCenter().removeObserver(self)
}
}
First you specify the new subclass in Identity Inspector:
Then you can use it like this:
The only caveat I can think of is the constants in the Storyboard are not used at runtime, but instead are overwritten with the percentage based calculation. So it does require some duplicate effort, once to actually layout the views on Storyboard based on points just so you get a sense of what the screen layout looks like, then percentages kick in at runtime.
For more details, check out this article: http://basememara.com/percentage-based-margin-using-autolayout-storyboard/
Along with the Kevin answer you also need to add constraint for the box superview.
var view = UIView();
view.backgroundColor = UIColor.redColor();
view.setTranslatesAutoresizingMaskIntoConstraints(false);
self.view.addSubview(view);
var viewObject = ["mainview" : self.view , "view" : view];
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:[view(==320)]", options: NSLayoutFormatOptions(0), metrics: nil, views: viewObject));
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:[view(==400)]", options: NSLayoutFormatOptions(0), metrics: nil, views: viewObject));