Constraints to superview not working - swift

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.

Related

Changing position of UIProgressView?

I added a progress bar to my screen. I want it to be centered horizontally in my container, but I want to move it to the bottom of my screen. How do I edit the third line to change its position?
func addControls() {
progressView = UIProgressView(progressViewStyle: UIProgressViewStyle.Default)
progressView?.center = self.view.center
view.addSubview(progressView!)
}
You could use NSLayoutConstraints and do something like this. The 3rd line being where you put the progress bar on top or beneath the other object.
var otherObject = UIView()
self.view.addSubview(progressView)
let topConstraint = NSLayoutConstraint(item: progressView, attribute: .topMargin, relatedBy: .equal, toItem: otherObject, attribute: .bottomMargin, multiplier: 1, constant: 0)
let leftConstraint = NSLayoutConstraint(item: progressView, attribute: .leftMargin, relatedBy: .equal, toItem: self.view, attribute: .leftMargin, multiplier: 1, constant: 0)
let rightConstraint = NSLayoutConstraint(item: progressView, attribute: .rightMargin, relatedBy: .equal, toItem: self.view, attribute: .rightMargin, multiplier: 1, constant: 0)
let bottomConstraint = NSLayoutConstraint(item: progressView, attribute: .bottomMargin, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 50)
self.view.addConstraints([topConstraint, leftConstraint, rightConstraint, bottomConstraint])
That's really simple.
If you want to add a subview above another one, use this function:
func insertSubview(_ view: UIView, aboveSubview siblingSubview: UIView)
To add a subview below another one, use this function:
func insertSubview(_ view: UIView, belowSubview siblingSubview: UIView)

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

Cocoa window doubles in size when replacing view

I am trying to replace one View (and its ViewController) by a different one at runtime. Now when I do that, the old View disappears from the window, but the new one does not appear and the window becomes double its original width.
If I switch to full screen, I can see the new View quite small in the bottom left corner of my application's window.
Does anybody have a clue why this happens? I have been trying everything for hours now, but I cannot seem to find the reason why this happens.
For replacing the View (as well as its ViewController) I use the following code:
func exchangeDiashowViewController(for newDiashowViewController: NSViewController) {
if let oldDiashowViewControllerAsStateObserver = self.diashowViewController as? DiashowStateObserver {
DiashowManager.sharedInstance.remove(stateObserver: oldDiashowViewControllerAsStateObserver)
}
if let oldDiashowViewControllerAsSpeedObserver = self.diashowViewController as? DiashowSpeedObserver {
DiashowManager.sharedInstance.remove(speedObserver: oldDiashowViewControllerAsSpeedObserver)
}
if let newDiashowViewControllerAsStateObserver = newDiashowViewController as? DiashowStateObserver {
DiashowManager.sharedInstance.add(stateObserver: newDiashowViewControllerAsStateObserver)
}
if let newDiashowViewControllerAsSpeedObserver = newDiashowViewController as? DiashowSpeedObserver {
DiashowManager.sharedInstance.add(speedObserver: newDiashowViewControllerAsSpeedObserver)
}
self.view.replaceSubview(self.diashowViewController.view, with: newDiashowViewController.view)
self.diashowViewController.removeFromParentViewController()
self.addChildViewController(newDiashowViewController)
self.diashowViewController = newDiashowViewController
let centerHorizontallyConstraint = NSLayoutConstraint(item: self.diashowViewController.view, attribute: .centerX, relatedBy: .equal, toItem: self.view, attribute: .centerX, multiplier: 1.0, constant: 0.0)
let centerVerticallyConstraint = NSLayoutConstraint(item: self.diashowViewController.view, attribute: .centerY, relatedBy: .equal, toItem: self.view, attribute: .centerY, multiplier: 1.0, constant: 0.0)
let widthConstraint = NSLayoutConstraint(item: self.diashowViewController.view, attribute: .width, relatedBy: .equal, toItem: self.view, attribute: .width, multiplier: 1.0, constant: 0.0)
let heightConstraint = NSLayoutConstraint(item: self.diashowViewController.view, attribute: .height, relatedBy: .equal, toItem: self.view, attribute: .height, multiplier: 1.0, constant: 0.0)
self.view.addConstraints([centerHorizontallyConstraint, centerVerticallyConstraint, widthConstraint, heightConstraint])
}
I really hope someone has an idea. Thanks a lot in advance!
It appears that self.view.replaceSubview() is the problem.
I got everything working by using
self.diashowViewController.view.removeFromSuperview()
self.view.addSubview(newDiashowViewController.view)
instead.

UITableViewCell subclass with Auto Layout constraints incorrect height

I have a UITableViewCell subclass, which I setup the views all in code and than add NSLayoutConstraints to. Everything is working, except my UITabbleViewCell is not calculating its height correctly.
Here is the code of the UITableViewCell
override func updateConstraints() {
setupThumbnailImages()
super.updateConstraints()
}
func setupThumbnailImages() {
var imageViewXOrigin : CGFloat = 5.0
var imageViewYOrigin : CGFloat = 0.0
for thumbnailUrl in self.thumbnailsUrlArray {
let miniPictureView = UIImageView()
miniPictureView.backgroundColor = UIColor.blueColor()
miniPictureView.contentMode = UIViewContentMode.ScaleAspectFill
miniPictureView.clipsToBounds = true
miniPictureView.frame = CGRectMake(0, 0, 0, 0)
miniPictureView.translatesAutoresizingMaskIntoConstraints = false
if((imageViewXOrigin + 50) > frame.size.width){
imageViewYOrigin += 50
imageViewXOrigin = 5
}
contentView.addSubview(miniPictureView)
contentView.addConstraint(NSLayoutConstraint(item: miniPictureView, attribute: .Leading, relatedBy: .Equal, toItem: contentView, attribute: .Leading, multiplier: 1, constant: imageViewXOrigin))
contentView.addConstraint(NSLayoutConstraint(item: miniPictureView, attribute: .Top, relatedBy: .Equal, toItem: contentView, attribute: .Top, multiplier: 1, constant: imageViewYOrigin))
contentView.addConstraint(NSLayoutConstraint(item: miniPictureView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .Height, multiplier: 1, constant: 50))
contentView.addConstraint(NSLayoutConstraint(item: miniPictureView, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .Width, multiplier: 1, constant: 50))
let lastImage = thumbnailsUrlArray.last
if (lastImage == thumbnailUrl){
contentView.addConstraint(NSLayoutConstraint(item: miniPictureView, attribute: .Bottom, relatedBy: .Equal, toItem: contentView, attribute: .Bottom, multiplier: 1, constant: 0))
}
imageViewXOrigin += 50
}
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.addConstraint(NSLayoutConstraint(item: contentView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .Height, multiplier: 1, constant: imageViewYOrigin))
}
What happens is the miniPictureView displays as it should, however its displayed outside of the UITableViewCell's bounds and the cell stays at a height of 44.
If you want the cell to size itself based on its contents, make sure you set the following on your UITableView:
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44.0 // Or any other estimate you want, just make sure to set this to some value
A couple more notes:
Your miniPictureView constraints should be added to miniPictureView itself, since they don't involve contentView or a sibling view
In order for self-sizing to work, you also have to have a complete chain of constraints from the top of the contentView to the bottom. So you should add an equality constraint between the bottom of miniPictureView and contentView so there is a constraint that will actually push the bottom of the content view.
I suspect your last two lines aren't helping. The contentView's height should be established by its top and bottom edges being constrained to its contents. And the autoresizing mask is used to scale the cell and its contentView together. If you set that to false, you should at least replace it with code that sets the cell's frame to be equal to the contentView frame AFTER the layout is calculated.

Resize superview with autolayout depending on largest subview

What is the best practice to resize superview with autolayout if we have inner NSView columns with dynamic heights?
For example. If we have two column layout, where left column height is bigger than right column, the superview height should be as right column height. Than, if we change right column height to be bigger than left column height, superview height should change to height of right column. How to accomplish this?
I made sample project to test this:
Initially we have layout with two columns, where .Bottom constraint of left NSView is attached to bottom of superview.
If we press Make Right Bigger button, I make height of right NSView bigger than left one.
So I want here superview to change height depending on bigger column (right column). Is there a good practice to do so?
Code:
import Cocoa
class ViewController: NSViewController {
let leftView = NSView()
let rightView = NSView()
let button = NSButton()
var rightViewHeightConstraint: NSLayoutConstraint?
override func loadView() {
self.view = TestView()
}
override func viewDidLoad() {
super.viewDidLoad()
leftView.backgroundColor = NSColor.redColor()
rightView.backgroundColor = NSColor.orangeColor()
layoutLeft(view, insertView: leftView)
layoutRight(view, insertView: rightView)
button.title = "Make Right Bigger"
button.target = self
button.action = "makeBigger:"
ViewControllerLayout.layoutBotton(view, insertView: button, bottom: -20)
}
func makeBigger(sender: AnyObject) {
rightViewHeightConstraint?.animator().constant = 150.0
}
func layoutLeft(containerView: NSView, insertView: NSView) {
insertView.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(insertView)
let c1 = NSLayoutConstraint(item: insertView, attribute: .Left, relatedBy: .Equal, toItem: containerView, attribute: .Left, multiplier: 1.0, constant: 0.0)
let c2 = NSLayoutConstraint(item: insertView, attribute: .Width, relatedBy: .Equal, toItem: containerView, attribute: .Width, multiplier: 0.5, constant: 0.0)
let c3 = NSLayoutConstraint(item: insertView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 100.0)
let c4 = NSLayoutConstraint(item: insertView, attribute: .Top, relatedBy: .Equal, toItem: containerView, attribute: .Top, multiplier: 1.0, constant: 0.0)
let c5 = NSLayoutConstraint(item: insertView, attribute: .Bottom, relatedBy: .Equal, toItem: containerView, attribute: .Bottom, multiplier: 1.0, constant: -60.0)
containerView.addConstraint(c1)
containerView.addConstraint(c2)
containerView.addConstraint(c3)
containerView.addConstraint(c4)
containerView.addConstraint(c5)
}
func layoutRight(containerView: NSView, insertView: NSView) {
insertView.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(insertView)
let c1 = NSLayoutConstraint(item: insertView, attribute: .Right, relatedBy: .Equal, toItem: containerView, attribute: .Right, multiplier: 1.0, constant: 0.0)
let c2 = NSLayoutConstraint(item: insertView, attribute: .Width, relatedBy: .Equal, toItem: containerView, attribute: .Width, multiplier: 0.5, constant: 0.0)
let c3 = NSLayoutConstraint(item: insertView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 50.0)
let c4 = NSLayoutConstraint(item: insertView, attribute: .Top, relatedBy: .Equal, toItem: containerView, attribute: .Top, multiplier: 1.0, constant: 0.0)
let c5 = NSLayoutConstraint(item: insertView, attribute: .Bottom, relatedBy: .Equal, toItem: containerView, attribute: .Bottom, multiplier: 1.0, constant: -60.0)
containerView.addConstraint(c1)
containerView.addConstraint(c2)
containerView.addConstraint(c3)
containerView.addConstraint(c4)
// containerView.addConstraint(c5) // Cant add .Bottom constraint here, because of different column sizes.
rightViewHeightConstraint = c3
}
}
struct ViewControllerLayout {
static func layoutBotton(containerView: NSView, insertView: NSView, bottom: Double) {
insertView.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(insertView)
containerView.addConstraint(NSLayoutConstraint(item: insertView, attribute: .CenterX, relatedBy: .Equal, toItem: containerView, attribute: .CenterX, multiplier: 1.0, constant: 0.0))
containerView.addConstraint(NSLayoutConstraint(item: insertView, attribute: .Bottom, relatedBy: .Equal, toItem: containerView, attribute: .Bottom, multiplier: 1.0, constant: CGFloat(bottom)))
}
}
Download test project: GitHub
Managed to accomplish this with constraints only. Just added the container view for columns and set its height as GreaterThanOrEqual to left column and right column.
view.addConstraint(NSLayoutConstraint(item: containerView, attribute: .Height, relatedBy: .GreaterThanOrEqual, toItem: leftView, attribute: .Height, multiplier: 1, constant: 0))
view.addConstraint(NSLayoutConstraint(item: containerView, attribute: .Height, relatedBy: .GreaterThanOrEqual, toItem: rightView, attribute: .Height, multiplier: 1, constant: 0))
First create a constraint on superview with height
let heightConstraint = NSLayoutConstraint(item: view, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: greaterHeightAmongTwoColumns)
As discussed in the comment, you can post a notification and then let the super view know the updated frame. Super view would check if the updated height of any of the two columns is greater than its current height and update its heightConstraint declared above and then call
[self layoutIfNeeded]
on the superview's superview so that it is laid out with the new frame.