I can't figure out how to fix this: I want to create a match game, where the size of the cards depends on the device.
I thought I would create a contentView which I pinned to top and bottom of the rootView with a margin of 20 and alligned it to its center. Then I give it an aspect ratio of 3/4 (for 3 rows and 4 columns).
let contentView = UIView()
// place contentView on the view
self.view.addSubview(contentView)
contentView.translatesAutoresizingMaskIntoConstraints = false
//add position constraints to thecontentView
let topMargin = NSLayoutConstraint(item: contentView, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: self.view, attribute: NSLayoutAttribute.Top, multiplier: 1, constant: 20)
let bottomMargin = NSLayoutConstraint(item: contentView, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: self.view, attribute: NSLayoutAttribute.Bottom, multiplier: 1, constant: -20)
let horzontalAllignment = NSLayoutConstraint(item: contentView, attribute: NSLayoutAttribute.CenterY, relatedBy: NSLayoutRelation.Equal, toItem: self.view, attribute: NSLayoutAttribute.CenterY, multiplier: 1, constant: 0)
let verticalAllignment = NSLayoutConstraint(item: contentView, attribute: NSLayoutAttribute.CenterX, relatedBy: NSLayoutRelation.Equal, toItem: self.view, attribute: NSLayoutAttribute.CenterX, multiplier: 1, constant: 0)
self.view.addConstraints([topMargin, bottomMargin, horzontalAllignment, verticalAllignment])
// create an aspect ratio for the contentView
let aspect = NSLayoutConstraint(item: contentView, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: contentView, attribute: NSLayoutAttribute.Height, multiplier: 4/3, constant: 0)
contentView.addConstraint(aspect)
That works perfectly. (Whohoo!) Then I want to create a card that has a quarter of the contentView width and a third of the hight:
let thisCard = Card()
contentView.addSubview(thisCard)
thisCard.translatesAutoresizingMaskIntoConstraints = false
let hightConstraint = NSLayoutConstraint(item: thisCard, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: contentView, attribute: NSLayoutAttribute.Height, multiplier: 1/3, constant: 0)
let widthConstraint = NSLayoutConstraint(item: thisCard, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: contentView, attribute: NSLayoutAttribute.Width, multiplier: 1/4, constant: 0)
thisCard.addConstraints([hightConstraint, widthConstraint])
There the code breaks and I get the message:
The view hierarchy is not prepared for the constraint:
NSLayoutConstraint:0x78f1b290
Meeres_Memo_temp_caseinsensitive_renamery.Card:0x78f41820.height ==
UIView:0x78f49fc0.height 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.
Is it even possible, to do want I want to?
Update:
I just found my error.
I added the heightConstraint and widthConstraint to thisCard, but they need to be added to the the related view that is higher in the view hierarchy, in this case the contentView, like this:
contentView.addConstraints([hightConstraint, widthConstraint])
Read the error message. You can only add constraints if the items are in the same view hierarchy. I would think that you tried to add the constraints before either thisCard or contentView were added to a view hierarchy. You need to do that first.
BTW. 1/4 is not what you think - it is a big fat zero. 1/4 uses integer division. Use 1.0 / 4, for example.
Related
In my Swift app, I need to update an AutoLayout constraint multiple times, so I have a function which does this. I'm having an issue with this constraint not being updated sometimes, and I've narrowed down the issue to being that it updates the first time the function is called, but not if that function is called again.
The code from the function looks like this:
let verticalSpace = NSLayoutConstraint(item: self.prompt, attribute: .top, relatedBy: .equal, toItem: self.view, attribute: .top, multiplier: 1, constant: 10)
NSLayoutConstraint.activate([verticalSpace])
Does anyone know what could be causing this? Is there something which needs to be done in order to update a function multiple times?
You cannot have "competing" constraints. If you set the verticalSpace to 10, then set another verticalSpace to 20, which constraint should be used?
What you need to do is remove the existing constraint (or deactivate it), and then add/activate your new constraint.
Or...
Create your verticalConstraint and save a reference to it... then when you want to change it you can change the .constant however you wish.
Try this:
let verticalSpace = NSLayoutConstraint(item: self.prompt, attribute: .top, relatedBy: .equal, toItem: self.view, attribute: .top, multiplier: 1, constant: 10)
NSLayoutConstraint.activate([verticalSpace])
view.layoutIfNeeded()
Edit:
I've just achieved this by the following rule.
Either
Step 1:
remove that constraint which you want to update.
Step 2:
Add constraint again.
or
Update constraint value.
In both cases you should have the reference of that constraint which you want to update.
How:
I have just added my demo code.I have programmatically added a view and and changed it's vertical constraint by button click.Just go through the code.
Upto viewDidLoad method:
import UIKit
class ViewController: UIViewController {
let someView = UIView()
var topValue:CGFloat = 10
var verticalConstraint:NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
//This below codes are used to add a red color UIView in center which has width and height both 100
someView.backgroundColor = UIColor.red
someView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(someView)
let horizontalConstraint = NSLayoutConstraint(item: someView, attribute: NSLayoutAttribute.centerX, relatedBy: NSLayoutRelation.equal, toItem: view, attribute: NSLayoutAttribute.centerX, multiplier: 1, constant: 0) //Center horizontally
verticalConstraint = NSLayoutConstraint(item: someView, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem: view, attribute: NSLayoutAttribute.top, multiplier: 1, constant: topValue) // center vertically.want to change that constraint later so took a variable.
let widthConstraint = NSLayoutConstraint(item: someView, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: 100) //width 100
let heightConstraint = NSLayoutConstraint(item: someView, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: 100) //height 100
view.addConstraints([horizontalConstraint, verticalConstraint, widthConstraint, heightConstraint])
}
And for each button click topConstant will be gradually updated.
This is the code for Either part which i mentioned.
self.view.removeConstraint(verticalConstraint)
verticalConstraint = NSLayoutConstraint(item: someView, attribute:
NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem:
view, attribute: NSLayoutAttribute.top, multiplier: 1, constant:
topValue)
self.view.addConstraint(verticalConstraint)
And this is the code for or part.
verticalConstraint.constant = topValue
And my buttonClick event method is basically like this.
#IBAction func updateView(_ sender: Any) {
topValue += 10
//In this case I've removed the previous constraint and add that constraint again with new Value
self.view.removeConstraint(verticalConstraint)
verticalConstraint = NSLayoutConstraint(item: someView, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem: view, attribute: NSLayoutAttribute.top, multiplier: 1, constant: topValue)
self.view.addConstraint(verticalConstraint)
//In this case I've just update that constraint value.Commented because we are using first method
//verticalConstraint.constant = topValue
}
And my output.
I have this situation:
I want this:
The black view is at X distance between violet view and if I tap I button in the violet view the violet view's height reduces(or increase) and I want that the black view is at the same X distance between violet view.
For this reason I write this code:
For button event that increase or reduce the violet's view I use this method:
#IBAction func tapOpenButton(){
self.altezza.constant = self.altezza.constant + 20
}
#IBAction func tapCloseButton(){
self.altezza.constant = self.altezza.constant - 20
}
where altezza is a NSLayoutConstraint
The both views are in a scrollview and I set constraint by code in this way:
var newView = blackView
var view = violetView
let horizontalConstraint = NSLayoutConstraint(item: newView, attribute: NSLayoutAttribute.centerX, relatedBy: NSLayoutRelation.equal, toItem: view, attribute: NSLayoutAttribute.centerX, multiplier: 1, constant: 0)
let verticalConstraint = NSLayoutConstraint(item: newView, attribute: NSLayoutAttribute.centerY, relatedBy: NSLayoutRelation.equal, toItem: view, attribute: NSLayoutAttribute.centerY, multiplier: 1, constant: 120)
let widthConstraint = NSLayoutConstraint(item: newView, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: 100)
let heightConstraint = NSLayoutConstraint(item: newView, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: 100)
self.altra.addConstraints([horizontalConstraint,verticalConstraint, widthConstraint, heightConstraint])
altra is the scrollview where both views are
My problem is that the distance between the violet view and black view is not the same when I reduce or increase violet's height , in particular , for example , when I increase the violet view's height , violet view goes under the black view.
I'm new in auto layout by code.
Can you help me?
Check if this works in place of your heightConstaint
let heightConstraint = NSLayoutConstraint(item: newView, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: view, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: 100)
Instead of pinning the views by centerY, set the top of the black view to the bottom of the purple one with a margin.
let verticalConstraint = NSLayoutConstraint(item: newView, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem: view, attribute: NSLayoutAttribute.bottom, multiplier: 1, constant: 10)
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 using the ios-charts library by Daniel Gindi to try and create a bar chart.
I programatically create a BarChartView with a red background, and fill it with hard-coded data. The view is sized correctly, however the bar chart doesn't scale properly and is cut off when it reaches the top of the view.
My view looks like this:
This view controller is instantiated inside a scrollview using the storyboard method instantiateViewControllerWithIdentifier. However when I make this view controller the initial view controller, the chart scales correctly, and looks like this:
Why does my chart scale incorrectly?
I would also like to note that if I set the leftAxis.axisMaxValue property of the incorrectly-scaled graph to something large, like 100, the graph looks like this:
I will also provide the code I used to create the graph, minus the 30+ lines I used to set properties and the data of the graph.
override func viewDidLoad(){
var chart : UIView?
let gBFrame = self.graphBackground.frame
let frame = CGRect(origin: gBFrame.origin, size: CGSize(width: gBFrame.width, height: gBFrame.height-25))
chart = BarChartView(frame: frame)
self.view.addSubview(chart!)
constrainChart()
}
func constrainChart(){
if type == "Bar Chart"{
chart!.translatesAutoresizingMaskIntoConstraints = false
let leftConstraint = NSLayoutConstraint(item: self.chart!, attribute: NSLayoutAttribute.Left, relatedBy: NSLayoutRelation.Equal, toItem: self.graphBackground, attribute: NSLayoutAttribute.Left, multiplier: 1, constant: 0)
let rightConstraint = NSLayoutConstraint(item: self.chart!, attribute: NSLayoutAttribute.Right, relatedBy: NSLayoutRelation.Equal, toItem: self.graphBackground, attribute: NSLayoutAttribute.Right, multiplier: 1, constant: 0)
let topConstraint = NSLayoutConstraint(item: self.chart!, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: self.graphBackground, attribute: NSLayoutAttribute.Top, multiplier: 1, constant: 0)
let bottomConstraint = NSLayoutConstraint(item: self.chart!, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: self.graphBackground, attribute: NSLayoutAttribute.Bottom, multiplier: 1, constant: -25)
self.view.addConstraints([leftConstraint,rightConstraint,topConstraint,bottomConstraint])
} else if type == "Horizontal Bar Chart"{
} else if type == "Pie Chart"{
} else {
}
chart?.layoutIfNeeded()
}
I can provide any additional information if it is helpful.
Any ideas on what could be the problem? Or what to try next?
EDIT:
when I instantiate the view controller inside the scrollview, I use NSLayoutConstraints to position it such that its left boundary is 2*self.view.frame.width from the left boundary of the scrollview.
I find that if I set that constraint to 0, such that the view controller with the chart appears in the leftmost frame of the scrollview, the chart appears correctly. However if I change that constraint at all (like by one unit), the chart scales incorrectly again.
Right after I instantiate the view controller using the aforementioned storyboard method, I position it using the method whose code is shown below:
func setUpQuestionFrame(newViewController: UIViewController){
var frame = newViewController.view.frame
frame.origin.x = self.view.frame.size.width*2
newViewController.view.frame = frame
self.addChildViewController(newViewController)
self.scrollView.addSubview(newViewController.view)
newViewController.didMoveToParentViewController(self)
newViewController.view.translatesAutoresizingMaskIntoConstraints = false
let widthConstraintVCBAR = NSLayoutConstraint(item: view, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: newViewController.view, attribute: NSLayoutAttribute.Width, multiplier: 1, constant: 0)
view.addConstraint(widthConstraintVCBAR)
let heightConstraintVCBAR = NSLayoutConstraint(item: view, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: newViewController.view, attribute: NSLayoutAttribute.Height, multiplier: 1, constant: 0)
view.addConstraint(heightConstraintVCBAR)
let horizontalConstraintVCBAR = NSLayoutConstraint(item: newViewController.view, attribute: NSLayoutAttribute.Left, relatedBy: NSLayoutRelation.Equal, toItem: scrollView, attribute: NSLayoutAttribute.Right, multiplier: 1, constant: 5)//self.view.frame.width*2)
view.addConstraint(horizontalConstraintVCBAR)
let verticalConstraintVCBAR = NSLayoutConstraint(item: newViewController.view, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: scrollView, attribute: NSLayoutAttribute.Top, multiplier: 1, constant: 0)
view.addConstraint(verticalConstraintVCBAR)
}
The issue was solved by adding chart!.notifyDataSetChanged(), which tells the chart to reconfigure once it receives new data.
It was the same solution that was used in this question.
I'm new to AutoLayout and would like to display my UITextField at 100% width with a consistent 15px left and right margin, like so:
Typically I would do this using CGRect, setting the width to the containing view's width minus 30px, then offset the left side by 15px:
searchTextField.frame = CGRectMake(15, 0, view.frame.width - 30, 50)
But I'd like to learn AutoLayout for this sort of thing, since it's the way to go these days. I should note that I am doing everything programmatically -- no Storyboards here.
I'd love it if someone could help me out!
Update
Wow! Thank you for all the responses. I believe all of them would achieve what I'm trying to do, but there can only be one :)
Usually I use for this cocoapod that is dealing with constraints, but if you need pure apple solution documentation states:
You have three choices when it comes to programmatically creating
constraints: You can use layout anchors, you can use the
NSLayoutConstraint class, or you can use the Visual Format Language.
Approach with NSLayoutConstraints in your case would be:
NSLayoutConstraint(item: textField, attribute: .Leading, relatedBy: .Equal, toItem: parentView, attribute: .LeadingMargin, multiplier: 1.0, constant: 15.0).active = true
NSLayoutConstraint(item: textField, attribute: .Trailing, relatedBy: .Equal, toItem: parentView, attribute: .TrailingMargin, multiplier: 1.0, constant: -15.0).active = true
NSLayoutConstraint(item: textField, attribute: .Top, relatedBy: .Equal, toItem: parentView, attribute: .TopMargin, multiplier: 1.0, constant: 50.0).active = true
Remember that if you don't have any constraints in the view, they would be added automatically and you'll have to deal with them and conflicts that would be created by adding new constraints on runtime. To avoid this you could either create textField manually and add it to the view or set constraints with low priority in the Interface Builder .
Assuming the parent of the text field is view, do this after view.addSubview(searchTextField):
NSLayoutConstraint.activateConstraints([
searchTextField.leadingAnchor.constraintEqualToAnchor(view.leadingAnchor, constant: 15),
searchTextField.trailingAnchor.constraintEqualToAnchor(view.trailingAnchor, constant: -15),
])
Use this code:
let topConstraint = NSLayoutConstraint(item: textField, attribute: NSLayoutAttribute.Top, relatedBy: .Equal, toItem: self.view, attribute: NSLayoutAttribute.Top, multiplier: 1.0, constant: 0)
let trailingConstraint = NSLayoutConstraint(item: textField, attribute: NSLayoutAttribute.Trailing, relatedBy: .Equal, toItem: self.view, attribute: NSLayoutAttribute.Trailing, multiplier: 1.0, constant: 15)
let leadingConstraint = NSLayoutConstraint(item: textField, attribute: NSLayoutAttribute.Leading, relatedBy: .Equal, toItem: self.view, attribute: NSLayoutAttribute.Leading, multiplier: 1.0, constant: 15)
let heightConstraint = NSLayoutConstraint(item: textField, attribute: NSLayoutAttribute.Height, relatedBy: .Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1.0, constant: 50)
self.view.addConstraint(topConstraint )
self.view.addConstraint(trailingConstraint)
self.view.addConstraint(leadingConstraint)
self.view.addConstraint(heightConstraint)
Set the constraints in storyboard.
Click on the text field then click on in the bottom left. From there you can choose constraints like that.
To use Auto Layout, you need to define constraints for your text field.Here, I have created four constraints(Leading, Trailing, Top and Height) related to its superview.
func addLabelConstraints(superView:UIView) {
let leading = NSLayoutConstraint(item: searchTextField, attribute: .Leading, relatedBy: .Equal, toItem: superView, attribute: .Leading, multiplier: 1, constant: 15)
superview!.addConstraint(leading)
let trailing = NSLayoutConstraint(item: searchTextField, attribute: .Trailing, relatedBy: .Equal, toItem: superView, attribute: .Trailing, multiplier: 1, constant: 15)
superView.addConstraint(trailing)
let top = NSLayoutConstraint(item: searchTextField, attribute: .Top, relatedBy: .Equal, toItem: superView, attribute: .Top, multiplier: 1, constant: 0)
superView.addConstraint(top)
let height = NSLayoutConstraint(item: searchTextField, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .Height, multiplier: 0, constant: 50)
superView.addConstraint(height)
}