Programmatically creating constraints bound to view controller margins - swift

I'm trying to make a view that will act as a sort of "panel", attached to the right side of the view controller.
That is, it is bound to the trailing, top, and bottom margins of the parent view controller, with a static width of 300
However, I just can't seem to get it right, I'm either breaking a constraint or doing something xcode tells me is illegal.
What am I doing wrong?
Here is the code in the controller
let myView = UIView()
view.backgroundColor = UIColor.redColor()
self.view.addSubview(view)
let topConstraint = NSLayoutConstraint(item: myView,
attribute: .Top,
relatedBy: .Equal,
toItem: self.topLayoutGuide,
attribute: .Bottom,
multiplier: 1,
constant: 0)
let trailingConstraint = NSLayoutConstraint(item: self.view,
attribute: .TrailingMargin,
relatedBy: .Equal,
toItem: myView,
attribute: .Trailing,
multiplier: 1,
constant: 0)
let bottomConstraint = NSLayoutConstraint(item: self.bottomLayoutGuide,
attribute: .Top,
relatedBy: .Equal,
toItem: myView,
attribute: .Bottom,
multiplier: 1,
constant: 0)
let widthConstraint = NSLayoutConstraint(item: myView,
attribute: .Width,
relatedBy: .Equal,
toItem: nil,
attribute: .NotAnAttribute,
multiplier: 1,
constant: 300)
self.view.addConstraints([trailingConstraint])
view.addConstraints([topConstraint, bottomConstraint, widthConstraint])

Actually the problem in your code is that you did not set the translatesAutoresizingMaskIntoConstraints of myview to false, whenever you want to use auto-layout constraints then you have to set translatesAutoresizingMaskIntoConstraints of a view to false.
Another Problem is that you do not add myview on self.view I have updated your code and Its working fine According your constraints.
Put below code in your ViewController .
let myView = UIView()
myView.backgroundColor = UIColor.redColor()
self.view.addSubview(myView)
myView.translatesAutoresizingMaskIntoConstraints = false
view.addConstraint(NSLayoutConstraint(item: myView, attribute: .Top, relatedBy: .Equal, toItem: self.topLayoutGuide, attribute: .Bottom, multiplier: 1, constant: 0))
view.addConstraint(NSLayoutConstraint(item: myView, attribute: .Bottom, relatedBy: .Equal, toItem: self.bottomLayoutGuide, attribute:.Top, multiplier: 1, constant: 20))
view.addConstraint(NSLayoutConstraint(item: myView, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute,multiplier: 1, constant: 300))
view.addConstraint(NSLayoutConstraint(item: myView, attribute: .TrailingMargin, relatedBy: .Equal, toItem: view, attribute: .TrailingMargin, multiplier: 1, constant: 0))

In your example code above, it seems like you are mixing up view and myView in a few places. In any event, widthConstraint should be added to myView and topConstraint, trailingConstraint, and bottomConstraint should be added to self.view. The reason for this is constraints must be added to the closest superview ancestor that lays out both views involved in the constraint. In the case where you are constraining a child view attribute to an attribute on its parent view, the constraint must be added to the parent view, since it lays out both itself and the child view. If you have a constraint between two sibling views, the constraint would be added to their parent view, since it is the closest ancestor that lays out both the views involved.
If you're able to target iOS 9.0 and above, it's much cleaner and easier to use the new NSLayoutAnchor and NSLayoutDimension API for creating these kinds of constraints. It also provides strict type checking and the compiler can verify correctness. With these new APIs, your example code would simply become:
let myView = UIView()
myView.backgroundColor = UIColor.redColor()
self.view.addSubview(myView)
let margins = self.view.layoutMarginsGuide
myView.trailingAnchor.constraintEqualToAnchor(margins.trailingAnchor).active = true
myView.topAnchor.constraintEqualToAnchor(margins.topAnchor).active = true
myView.bottomAnchor.constraintEqualToAnchor(margins.bottomAnchor).active = true
myView.widthAnchor.constraintEqualToConstant(300.0).active = true
No need to explicitly add the constraints to the right view, etc. You can read more about this method of creating constraints here:
https://developer.apple.com/library/ios/documentation/AppKit/Reference/NSLayoutAnchor_ClassReference/
and here:
https://developer.apple.com/library/ios/documentation/AppKit/Reference/NSLayoutDimension_ClassReference/

There seems to be some ambiguity in your code, you are creating a UIView as myView but adding view to self.view and even constraint also to view itself. So correct your code and replace view with myView.
Secondly setTranslayesAutoresizingMaskIntoConstraints to false.
Then add all the constraints to self.view. This should solve your problem.
myView.setTranslatesAutoresizingMaskIntoConstraints(false)
self.view.addConstraints([trailingConstraint, bottomConstraint, widthConstraint])
VFL is also a better and clean approach. It actually gives a visualization of how constraint are setup.

Related

UIView Loaded From Nib Appears Wrongly Scaled Intermittently

So we've just released an iOS app that makes heavy use of loading views from Nibs (use of Nib's may or may not be related).
On all my own test devices, everything works as expected, however some users are reporting UI that's scaled disproportionally to their device.
By way of example, here is what the screen should look like:
Correctly Proportioned Screenshot vs Erroneous Screenshot
Both screenshots were taken on an iPhone 6s, running iOS 11.4, and both using the same version of the app.
The code to load the nib is fairly straightforward:
extension UINib {
static func instantiateViewAndEmbedWithConstraints <T: UIView> (viewType: T.Type, embedInto containerView: UIView) -> T {
let view = UINib(nibName: String(describing: viewType), bundle: nil).instantiate(withOwner: nil, options: nil).first as! T
containerView.addSubview(view)
view.translatesAutoresizingMaskIntoConstraints = false
let leadingConstraint : NSLayoutConstraint = NSLayoutConstraint(item: view, attribute: .leading, relatedBy: .equal, toItem: containerView, attribute: .leading, multiplier: 1, constant: 0)
leadingConstraint.identifier = "leading"
leadingConstraint.isActive = true
NSLayoutConstraint(item: view, attribute: .trailing, relatedBy: .equal, toItem: containerView, attribute: .trailing, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: view, attribute: .top, relatedBy: .equal, toItem: containerView, attribute: .top, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: view, attribute: .bottom, relatedBy: .equal, toItem: containerView, attribute: .bottom, multiplier: 1, constant: 0).isActive = true
return view
}
}
I'm somewhat bewildered... Everything has the same auto-layout constraints in place, so there's no logical reason I can think of for it to be resizing views like this.
It uses auto-layout, buttons are a set height / leading / trailing across all devices... But for some users everything is just being scaled up.
Anyway, I was just hoping someone on here might have even an inkling of what might be the root cause that I could try delve into; as I'm drawing a blank / have just been banging my head against a wall the entire day!
Many thanks.

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

How we set constraint for adding subview

I want to add view on my View, but they are not set well this width stretch. I am new to iOS. Any help would be appreciated. This is what I get when I try to do what I want, can someone help to fix this:
openView.translatesAutoresizingMaskIntoConstraints = false
let topConstraint = NSLayoutConstraint(item: openView, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: 0)
let bottomConstraint = NSLayoutConstraint(item: openView, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1, constant: 0)
let leadingConstraint = NSLayoutConstraint(item: openView, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1, constant: 0)
let trailingConstraint = NSLayoutConstraint(item: openView, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1, constant: 0)
self.view.addSubview(openView)
openView.addConstraints([topConstraint, bottomConstraint, leadingConstraint, trailingConstraint])
openView.layoutIfNeeded()
In your xcode open main.Storyboard --> Open your viewController -->Add UIView --> Make your view frame like you want --> And add constraints
Ex: If you want width=600 & height=600, set frame like you want and then assign constraints.
.Refer linked image for reference
Here you are setting subview's frame as the initial frame of controller view. ie, the controller's view will be fully covered by subview.
If you want to add constraints,
1) Take the storyboard or xib.
2) Tap on the UI element.
3) Tap on the constraints button as shown in image.
4) Set the values from each border.
You can also set height, width such like parameters. The whole need is the system should be able to interpret the accurate position from the constraints.

Swift: programatically copy auto layout constraints from one view to another

I have a UIButton that I set up with auto layout constraints in a storyboard. i also have a UIView that I initiate in the UIViewController's viewDidLoad method. I make this view have (almost) all the same properties as the UIButton but when it I run it in the simulator it doesn't "stick" to the button. here's what I have:
class ViewController: UIViewController {
#IBOutlet weak var someButton: UIButton!
func viewDidLoad() {
super.viewDidLoad()
let someView = UIView()
someView.backgroundColor = UIColor.greenColor()
someView.frame = someButton.bounds
someView.frame.origin = someButton.frame.origin
someView.autoresizingMask = someButton.autoresizingMask
someView.autoresizesSubviews = true
someView.layer.cornerRadius = someButton.layer.cornerRadius
someView.clipsToBounds = true
someView.userInteractionEnabled = false
view.insertSubview(someView, belowSubview: someButton)
}
}
I guess I’m missing the someView.auto layout constraint?
EDIT: i thought accessing the UIButton's constraints would work but they appear to be an empty array. are story board constraints are hidden?
someView.addConstraints(someButton.constraints)
thanks.
Copying the constraints that way fails because:
the constraints in the storyboard are added to the superview and not the button itself
the constraints you try to copy are referencing the button and not your new view
Instead of copying the constraints keep it simple and create new ones and reference the button:
let someView = UIView()
someView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(someView)
view.addConstraints([
NSLayoutConstraint(item: someView, attribute: .Leading, relatedBy: .Equal, toItem: someButton, attribute: .Leading, multiplier: 1, constant: 0),
NSLayoutConstraint(item: someView, attribute: .Trailing, relatedBy: .Equal, toItem: someButton, attribute: .Trailing, multiplier: 1, constant: 0),
NSLayoutConstraint(item: someView, attribute: .Top, relatedBy: .Equal, toItem: someButton, attribute: .Top, multiplier: 1, constant: 0),
NSLayoutConstraint(item: someView, attribute: .Bottom, relatedBy: .Equal, toItem: someButton, attribute: .Bottom, multiplier: 1, constant: 0)
])

Adding NSLayoutConstraints to UISegmentedControl causes UISegmentedControl to disappear

I have a UISegmentedControl defined programmatically.
I am trying to add a layout constraint so that when my iPad rotates, the segmented control sizes correctly within the rotated view rather than spill off screen.
I apply the following constraint:
streamSegmentedControl.translatesAutoresizingMaskIntoConstraints = false
let segmentedControlWidth = NSLayoutConstraint(item: streamSegmentedControl,
attribute: .Width,
relatedBy: .Equal,
toItem: self.containerView,
attribute: .Width,
multiplier: 1.0,
constant: -10.0)
containerView.addConstraint(segmentedControlWidth)
My UIsegmentControl is defined as follows:
streamSegmentedControl = UISegmentedControl(items: ["Today's Events", "Past Events"])
streamSegmentedControl.frame = CGRectMake(-10,containerView.frame.size.height*0.3,containerView.frame.width+20,40)
streamSegmentedControl.selectedSegmentIndex = 0
streamScope = "today"
streamSegmentedControl.setTitleTextAttributes(segmentedControlFont as [NSObject : AnyObject], forState: .Normal)
streamSegmentedControl.backgroundColor = UIColor.colorFromClass("background")
streamSegmentedControl.tintColor = UIColor.colorFromClass("default")
streamSegmentedControl.addTarget(self, action: "changeStreamScope:", forControlEvents: UIControlEvents.ValueChanged)
containerView.addSubview(streamTableView)
containerView.addSubview(streamSegmentedControl)
I don't get an error, but at runtime, my segmented control disappears. Not sure what I am missing here as I've only done auto layout within storyboards in the past.
I only want to be able to adjust the width of the segmented control, so I assume I only need a single layout constraint.
Can anyone give me some direction? Thanks.
You need to add three constraints at least. Leading, top and trailing.
I am weak in swift but your constraints should look like.
let segmentedControlTop = NSLayoutConstraint(item: streamSegmentedControl,
attribute: .Top,
relatedBy: .Equal,
toItem: self.containerView,
attribute: .Top,
multiplier: 1.0,
constant: 0.0)
let segmentedControlLeading = NSLayoutConstraint(item: streamSegmentedControl,
attribute: .Leading,
relatedBy: .Equal,
toItem: self.containerView,
attribute: .Leading,
multiplier: 1.0,
constant: 0.0)
let segmentedControlTrailing = NSLayoutConstraint(item: streamSegmentedControl,
attribute: .Trailing,
relatedBy: .Equal,
toItem: self.containerView,
attribute: .Trailing,
multiplier: 1.0,
constant: 0.0)
containerView.addConstraint(segmentedControlTop)
containerView.addConstraint(segmentedControlLeading)
containerView.addConstraint(segmentedControlTrailing)
If you want to keep a 5 point offset from superview, try playing with constant values of leading and trailing using 5 points.