Swift Change button position with AutoLayout - swift

I have created the button programmatically in this way
func CreateButtonWithIndex(index:Int) {
let widthButton = 120
let heightButton = 80
let line = (index / 3)
let column = index % 3
let newButton = UIButton.buttonWithType(UIButtonType.System) as UIButton
newButton.frame = CGRect(x:line*widthButton+95, y:column*heightButton+15, width:widthButton, height:heightButton)
self.view.addSubview(newButton)
}
Now trying to place NSLayoutConstraint for this button in this way
func CreateButtonWithIndex(index:Int) {
let widthButton = 120
let heightButton = 80
let line = (index / 3)
let column = index % 3
let newButton = UIButton.buttonWithType(UIButtonType.System) as UIButton
newButton.setTranslatesAutoresizingMaskIntoConstraints(false)
self.view.addSubview(newButton)
let constraint1 = NSLayoutConstraint(item: newButton, attribute: .CenterX, relatedBy: .Equal, toItem: view, attribute: .CenterX, multiplier: 1, constant: 1)
let constraint2 = NSLayoutConstraint(item: newButton, attribute: .CenterY, relatedBy: .Equal, toItem: view, attribute: .CenterY, multiplier: 1, constant: 10)
view.addConstraints([constraint1,constraint2])
}
but obviously i get only that the various buttons are created in the same location.
i have tried e.g.
let constraint1 = NSLayoutConstraint(item: newButton, attribute: .CenterX, relatedBy: .Equal, toItem: view, attribute: .CenterX, multiplier: 1, constant: line)
But don't go !!!
how can I place the line and column with the NSLayoutConstraint ?

You are on the right track! The problem is that your calculation is not giving a big enough difference in the x positions. You are saying:
let constraint1 = NSLayoutConstraint(
item: newButton, attribute: .CenterX, relatedBy: .Equal,
toItem: view, attribute: .CenterX, multiplier: 1, constant: line)
But what is line?
let line = (index / 3)
Well, if index is 0, then 1, then 2, line is going to 0 every time. You need a much bigger difference if you want these buttons to be in different places!
(You should also declare or cast line to a CGFloat, obviously.)

Related

How to put a uiview back in a stackview after I took it out?

I have a viewcontroller that holds multiple stackviews. There is a button that when pressed, the corresponding uiview wil become fullscreen inside the original view. There is a different button that is supposed to make the uiview go back to its original stackview. The uiview itself contains other views. I am having some problems doing that. The uiview does end up in the stackview, but not near the same size/place it used to be. I am not sure how to solve this, and been going at it for several hours now, looking at multiple sources.
This is the code that makes the uiview go fullscreen:
private func moveToFrontOfCardView(v: UIView) {
originalView = v.superview
if let stack = originalView as? UIStackView {
stack.removeArrangedSubview(v)
}
myCardView.addSubview(v)
let topConstraint = NSLayoutConstraint(item: v, attribute: .top, relatedBy: .equal, toItem: myCardView, attribute: .top, multiplier: 1, constant: 10)
let bottomConstraint = NSLayoutConstraint(item: v, attribute: .bottom, relatedBy: .equal, toItem: myCardView, attribute: .bottom, multiplier: 1, constant: -10)
let leftConstraint = NSLayoutConstraint(item: v, attribute: .left, relatedBy: .equal, toItem: myCardView, attribute: .left, multiplier: 1, constant: 10)
let rightConstraint = NSLayoutConstraint(item: v, attribute: .right, relatedBy: .equal, toItem: myCardView, attribute: .right, multiplier: 1, constant: -10)
myCardView.addConstraints([topConstraint, bottomConstraint, leftConstraint, rightConstraint])
}
And this is the code I use when I want it to go back:
private func moveToOriginalPosition(v: UIView) {
if let stack = originalView as? UIStackView {
stack.addArrangedSubview(v)
}
}
Does anyone have a clue how I could fix this?
EDIT
I've tried Saqib and Bilals answer, but I get this as a result:
Declare a class variable for tracking view's index
var selectedIndex = 0 // Contains Current Seleceted view's index
overrie func viewDidLoad() { ...
Before removing view from stackview get the view index like this selectedIndex = stack.subviews.index(of: v)
keep reference to all the constraints.
Before adding it back disable all the constraints topConstraint.isActive = false
Now add the view at the same index using stack.insertArrangedSubview(view, at: selectedIndex)
An other option is to create a same new view and just hide/unhide the one in stackview. StackView automatically fills the space accordingly for the hidden views.
You should deActivate the constraints you added to view when removed it from stackView, at the time you want add the view to the stackView again.
For this you should make the constraints instance of your viewController class and next, write your moveToOriginalPosition(v: UIView) method like this:
private func moveToOriginalPosition(v: UIView) {
if let stack = originalView as? UIStackView {
stack.addArrangedSubview(v)
topConstraint.isActive = false
bottomConstraint.isActive = false
leftConstraint.isActive = false
rightConstraint.isActive = false
}
}
Ofcourse, you should remove, these lines of codes from moveToFrontOfCardView(v: UIView) method:
self.topConstraint = NSLayoutConstraint(item: v, attribute: .top, relatedBy: .equal, toItem: myCardView, attribute: .top, multiplier: 1, constant: 10)
self.bottomConstraint = NSLayoutConstraint(item: v, attribute: .bottom, relatedBy: .equal, toItem: myCardView, attribute: .bottom, multiplier: 1, constant: -10)
self.leftConstraint = NSLayoutConstraint(item: v, attribute: .left, relatedBy: .equal, toItem: myCardView, attribute: .left, multiplier: 1, constant: 10)
self.rightConstraint = NSLayoutConstraint(item: v, attribute: .right, relatedBy: .equal, toItem: myCardView, attribute: .right, multiplier: 1, constant: -10)
myCardView.addConstraints([topConstraint, bottomConstraint, leftConstraint, rightConstraint])
and add them where you make your view initialized. and replace below lines with above lines in moveToFrontOfCardView(v: UIView) method:
topConstraint.isActive = true
bottomConstraint.isActive = true
leftConstraint.isActive = true
rightConstraint.isActive = true
By the looks of things you don't need to remove the original view. You could make a copy of it then display the copy full screen. Then when you dismiss this copy you release the reference to it

Positioning miltiple views programmatically in swift

With your last help my loop is functioning well. But only once. If I try to reload it I get the following mistake: "Index out of range" in the following line:
let cardOnTop = cards[index-8]
Could somebody help me?
Here is my code:
func layoutCards() {
// create cards array with several elements
var cards = (1...64).map { _ in UIView() }// array with several cards
//Loop through each card in the array
for index in 0...cards.count-1 {
// place the card in the view and turn off translateAutoresizingMask
let thisCard = cards[index]
thisCard.layer.borderWidth = 1
thisCard.layer.borderColor = UIColor.blackColor().CGColor
thisCard.backgroundColor = UIColor.greenColor()
thisCard.translatesAutoresizingMaskIntoConstraints = false
midView.addSubview(thisCard)
//set the height and width constraints
let widthConstraint = NSLayoutConstraint(item: thisCard, attribute: .Width, relatedBy: .Equal, toItem: midView, attribute: .Width, multiplier: 0.125, constant: 0)
let heightConstraint = NSLayoutConstraint(item: thisCard, attribute: .Height, relatedBy: .Equal, toItem: midView, attribute: .Height, multiplier: 0.125, constant: 0)
midView.addConstraints([heightConstraint, widthConstraint])
//set the horizontal position
if (columnCounter > 0) {
// card is not in the first column
let cardOnTheLeft = cards[index-1]
let leftSideConstraint = NSLayoutConstraint(item: thisCard, attribute: .Left, relatedBy: .Equal, toItem: cardOnTheLeft, attribute: .Right, multiplier: 1, constant: 0)
//add constraint to the contentView
midView.addConstraint(leftSideConstraint)
} else {
//card is in the first column
let leftSideConstraint = NSLayoutConstraint(item: thisCard, attribute: .Left, relatedBy: .Equal, toItem: midView, attribute: .Left, multiplier: 1, constant: 0)
//add constraint to the contentView
midView.addConstraint(leftSideConstraint)
}
//set the vertical position
if (rowCounter > 0) {
// card is not in the first row
let cardOnTop = cards[index-8]
let topConstraint = NSLayoutConstraint(item: thisCard, attribute: .Top, relatedBy: .Equal, toItem: cardOnTop, attribute: .Bottom, multiplier: 1, constant: 0)
// add constraint to the contentView
midView.addConstraint(topConstraint)
} else {
//card is in the first row
let topConstraint = NSLayoutConstraint(item: thisCard, attribute: .Top, relatedBy: .Equal, toItem: midView, attribute: .Top, multiplier: 1, constant: 0)
//add constraint to the contentView
midView.addConstraint(topConstraint)
}
//increment the column counter
columnCounter = columnCounter+1
//if the column counter reaches the fifth column reset it and increase the row counter
if (columnCounter >= 8) {
columnCounter = 0
rowCounter = rowCounter+1
}
} // end of the loop
}
You say this is failing when you run it a second time. That is because you apparently have rowCounter and columnCounter declared as properties and you are not setting rowCounter and columnCounter back to 0 each time layoutCards() is called.
Make rowCounter and columnCounter be local variables to layoutCards():
func layoutCards() {
var rowCounter = 0
var columnCounter = 0
// create cards array with several elements
var cards = (1...64).map { _ in UIView() }// array with several cards

Constraints to superview not working

I add a subView to a superview and I want to constrain the subview. But it does not do anything. The subview is added fullscreen over the superview. Please help me, what is going wrong?
This is my code:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
setupConstraints()
}
func setupConstraints(){
heightConstraint = NSLayoutConstraint(item: view, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: 80)
bottomConstraint = NSLayoutConstraint(item: view, attribute: NSLayoutAttribute.CenterY, relatedBy: NSLayoutRelation.Equal, toItem: view.superview!, attribute: NSLayoutAttribute.CenterY, multiplier: 1, constant: 30)
widthConstraint = NSLayoutConstraint(item: view, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: view.superview!, attribute: NSLayoutAttribute.Width, multiplier: 1, constant: 0)
view.setTranslatesAutoresizingMaskIntoConstraints(false)
view.superview!.setTranslatesAutoresizingMaskIntoConstraints(false)
view.addConstraint(heightConstraint!)
view.addConstraint(widthConstraint!)
view.addConstraint(bottomConstraint!)
}
The problem is that your constraints are ambiguous (underdetermined). There is not enough information to know where to put your view.
There are four pieces of information that must be known:
* x position
* y position
* width
* height
Look at your constraints, and consider what each one determines:
heightConstraint = NSLayoutConstraint(item: view, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: 80)
That's height.
bottomConstraint = NSLayoutConstraint(item: view, attribute: NSLayoutAttribute.CenterY, relatedBy: NSLayoutRelation.Equal, toItem: view.superview!, attribute: NSLayoutAttribute.CenterY, multiplier: 1, constant: 30)
That's y position.
widthConstraint = NSLayoutConstraint(item: view, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: view.superview!, attribute: NSLayoutAttribute.Width, multiplier: 1, constant: 0)
That's width.
Ooops! What about x position?
Another problem with your code could be this line:
view.superview!.setTranslatesAutoresizingMaskIntoConstraints(false)
You remove view.superview's automatically generated constraints by saying that โ€” but you do not replace them by any new constraints. So it may be ambiguously configured too at this point (unless it already has a complete set of constraints we don't know about, but then in that case that line is unnecessary).
In my book, I provide code for some utility methods to help track down this kind of thing:
extension NSLayoutConstraint {
class func reportAmbiguity (var v:UIView?) {
if v == nil {
v = UIApplication.sharedApplication().keyWindow
}
for vv in v!.subviews as! [UIView] {
println("\(vv) \(vv.hasAmbiguousLayout())")
if vv.subviews.count > 0 {
self.reportAmbiguity(vv)
}
}
}
class func listConstraints (var v:UIView?) {
if v == nil {
v = UIApplication.sharedApplication().keyWindow
}
for vv in v!.subviews as! [UIView] {
let arr1 = vv.constraintsAffectingLayoutForAxis(.Horizontal)
let arr2 = vv.constraintsAffectingLayoutForAxis(.Vertical)
NSLog("\n\n%#\nH: %#\nV:%#", vv, arr1, arr2);
if vv.subviews.count > 0 {
self.listConstraints(vv)
}
}
}
}
If you run NSLayoutConstraint.reportAmbiguity(view.superview!), preferably in viewDidLayoutSubviews at a time after your view has been injected into the view hierarchy, you will find that view reports as ambiguous. That's why you are not seeing what you expect.

Swift Assign Button Position from Plist

I have Add Constraint in My func
func CreateButtonWithIndex(var index:Int) {
let path = loadPath()
var data = NSMutableDictionary(contentsOfFile: path)
let newButton = UIButton.buttonWithType(UIButtonType.System) as UIButton newButton.setTranslatesAutoresizingMaskIntoConstraints(false)
self.view.addSubview(newButton)
let newButtonConstraintL = NSLayoutConstraint(item: newButton, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 50)
let newButtonConstraintH = NSLayoutConstraint(item: newButton, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 50)
// Read from Plist the coordinate
var reloadCoordX:[String] = data!.valueForKey("\(strDaPassare) X") as[String]
var reloadCoordY:[String] = data!.valueForKey("\(strDaPassare) Y") as[String]
var coordX: Int = ("\(reloadCoordX[index-1])").toInt()!
var coordY: Int = ("\(reloadCoordY[index-1])").toInt()!
// Add Constraint
newButtonConstraintX = NSLayoutConstraint(item: newButton, attribute: .CenterX, relatedBy: .Equal, toItem: view, attribute: .CenterX, multiplier: 1, constant: CGFloat(coordX))
newButtonConstraintY = NSLayoutConstraint(item: newButton, attribute: .CenterY, relatedBy: .Equal, toItem: view, attribute: .CenterY, multiplier: 1, constant: CGFloat(coordY))
}
And it's ok !!!! but this takes as a reference center of the UIView it self for the values โ€‹โ€‹of newButtonConstraintX and newButtonConstraintY . but if I want to set attribute for newButtonConstraintX and newButtonConstraintY for the Top left corner ?

converting storyboard constraints into code

I'm trying to programmatically set up constraints for a class in my app so that when the keyboard appears everything moves up. i already got it working before in the storyboard, but when i take what it says in the size inspector and write it in code format, it doesn't work as expected.
...
//memo area
var memoArea = UITextView(frame: CGRectMake(20, 291, 275, 225))
memoArea.backgroundColor = majorColor
memoArea.delegate = self
self.view.addSubview(memoArea)
var memoLine = customShadow(theself: self.view, frame: memoArea.frame)
//Spacer View
var spacer:UIView = UIView(frame: CGRectMake(84, 518, 160, 6))
spacer.alpha = 0
self.view.addSubview(spacer)
//Constraints
var memoAreaToSpacer:NSLayoutConstraint = NSLayoutConstraint(item: spacer, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: memoArea, attribute: NSLayoutAttribute.Bottom, multiplier: 1, constant: 8)
spacerToBottom = NSLayoutConstraint(item: bottomLayoutGuide, attribute: NSLayoutAttribute.Top, relatedBy: .Equal, toItem: spacer, attribute: NSLayoutAttribute.Bottom, multiplier: 1, constant: 0)
view.addConstraint(memoAreaToSpacer)
view.addConstraint(spacerToBottom)
...
so when a keyboard fires a notification this happens.
func updateBottomLayoutConstraintWithNotification(notification: NSNotification) {
let userInfo = notification.userInfo!
let animationDuration = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as NSNumber).doubleValue
let keyboardEndFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as NSValue).CGRectValue()
let convertedKeyboardEndFrame = view.convertRect(keyboardEndFrame, fromView: view.window)
let rawAnimationCurve = (notification.userInfo![UIKeyboardAnimationCurveUserInfoKey] as NSNumber).unsignedIntValue << 16
let animationCurve = UIViewAnimationOptions.init(UInt(rawAnimationCurve))
let frame = self.tabBarController?.tabBar.frame
let height = frame?.size.height
spacerToBottom.constant = CGRectGetMaxY(view.bounds) - CGRectGetMinY(convertedKeyboardEndFrame) - height! - 5
UIView.animateWithDuration(animationDuration, delay: 0.0, options: .BeginFromCurrentState | animationCurve, animations: {
self.view.layoutIfNeeded()
}, completion: nil)
}
Here's the full app https://github.com/stanchiang/phoneHub
The code i'm talking about is in the BaseDetailViewController.swift in viewDidLoad().
The constraints i'm transferring from is the Edit Controller in the Main.storyboard file.
also, for reference i got it working through storyboard through this blog post.
http://effortlesscode.com/auto-layout-keyboard-shown-hidden/
thanks for your answers, still trying to get the hang of autolayout so general tips are welcome too.
I'm no Auto Layout expert, but I'll share what I know:
You need to turn off the AutoresizingMask for the views that you are adding constraints to:
view.setTranslatesAutoresizingMaskIntoConstraints(false)
spacer.setTranslatesAutoresizingMaskIntoConstraints(false)
memoArea.setTranslatesAutoresizingMaskIntoConstraints(false)
You are going to need a lot more constraints. I only see 2 in your code. You will need to fully constrain your layout. This includes the settings you are specifying right now with frames (width, height). Positions should probably be specified relative to other objects if you want everything to move when the keyboard shows up. When you got this working in the Storyboard, there had to be more than just 2 constraints for the entire ViewController. You'll need to bring over all of the constraints.
I suggest you add the following 6 constraints to replace the values you were setting with the frames. Put these with your other constraints. I've added the view frames in comments so that you can see where I got the values:
//var memoArea = UITextView(frame: CGRectMake(20, 291, 275, 225))
memoArea.addConstraint(NSLayoutConstraint(item: memoArea, attribute: .Width, relatedBy: .Equal,
toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 275.0))
memoArea.addConstraint(NSLayoutConstraint(item: memoArea, attribute: .Height, relatedBy: .Equal,
toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 225.0))
self.view.addConstraint(NSLayoutConstraint(item: memoArea, attribute: .Leading, relatedBy: .Equal,
toItem: self.view, attribute: .Leading, multiplier: 1.0, constant: 20.0))
// var spacer:UIView = UIView(frame: CGRectMake(84, 518, 160, 6))
spacer.addConstraint(NSLayoutConstraint(item: spacer, attribute: .Width, relatedBy: .Equal,
toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 160.0))
spacer.addConstraint(NSLayoutConstraint(item: spacer, attribute: .Height, relatedBy: .Equal,
toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 6.0))
self.view.addConstraint(NSLayoutConstraint(item: spacer, attribute: .Leading, relatedBy: .Equal,
toItem: self.view, attribute: .Leading, multiplier: 1.0, constant: 84.0))