Adding Constraint messed up view - swift

I have a view with a UICollectionView and a UISegmentedControl.
I want to change constraints so the segment controller won't overlap collection view, like in this picture:
This is my code :
override func viewDidLoad()
{
super.viewDidLoad()
self.navigationItem.leftBarButtonItem = nil
self.tabBarController?.tabBar.isHidden = true
self.SegmentController.setTitle(SegmentAtext, forSegmentAt: 0)
self.SegmentController.setTitle(SegmentBtext, forSegmentAt: 1)
self.view.bringSubview(toFront: SegmentController)
self.LoadProducts(productsToShow: SegmentAtype)
}
SO I add this command:
self.ProductsCollection.topAnchor.constraint(equalTo: SegmentController.bottomAnchor, constant: 10).isActive = true
But the result is worse:
Now the segment controller is almost completely hidden!
How do I fix this?
Edit:
My viewDidLayoutSubviews function:
override func viewDidLayoutSubviews()
{
ProductsCollection.translatesAutoresizingMaskIntoConstraints = false
let topConstraint = NSLayoutConstraint(item: ProductsCollection, attribute: .top, relatedBy: .equal, toItem: self.view, attribute: .top, multiplier: 1, constant: 20)
let bottomConstraint = NSLayoutConstraint(item: ProductsCollection, attribute: .bottom, relatedBy: .equal, toItem: self.view, attribute: .bottom, multiplier: 1, constant: -50) //leaving space for search field
let leadingConstraint = NSLayoutConstraint(item: ProductsCollection, attribute: .leading, relatedBy: .equal, toItem: self.view, attribute: .leading, multiplier: 1, constant: 0)
let trailingConstraint = NSLayoutConstraint(item: ProductsCollection, attribute: .trailing, relatedBy: .equal, toItem: self.view, attribute: .trailing, multiplier: 1, constant: 0)
self.view.addConstraints([topConstraint, bottomConstraint, leadingConstraint, trailingConstraint])
}
Notice:
my viewDidLayoutSubviews is implemented in the super view, which does not contain a UISegmentedControl. the UISegmentedControl is contained in an inheriting view.
Edit: An updated view

If you would want the UISegmentedControl to be centred on the screen and below it to have your collection view you would do
NSLayoutConstraint.activate([
segmentedControl.topAnchor.constraint(equalTo: view.topAnchor),
segmentedControl.centerXAnchor.constraint(equalTo: view.centerXAnchor),
collectionView.topAnchor.constraint(equalTo: segmentedControl.bottomAnchor),
collectionView.leftAnchor.constraint(equalTo: view.leftAnchor),
collectionView.rightAnchor.constraint(equalTo: view.rightAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
So we have segmented controls top be top of the view, and center it,
then top of collection view is bottom of segmented control (you can add a constant for padding if needed) and left right and bottom to the view

Related

A layout issue (NSLayoutConstraint) in an iOS app

In an iOS app I have an autolayout issue.
The 2 following screenshots show the problem.
The switch (UISwitch object) on the right is displaced horizontally, when it should be fixed. Can anyone see what is happening?
It is true that the string on the left is changing length, but I think (according to the way I have set the constraints up) the font should be resized or the string split in 2 lines; but not the switch displaced.
Here is the relevant swift code:
import UIKit
class My_ViewController: UIViewController {
let xPanel = UILabel(), yPanel = UILabel(),
khToggle = UISwitch(), khLabel = UILabel()
....
override func viewDidLoad() {
super.viewDidLoad()
layOutUI()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
.....
toggleKeepHide(khToggle)
}
func layOutUI() {
for component in [xPanel,yPanel,khLabel,khToggle] {
component.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(component)
}
...........
khLabel.numberOfLines = 0
khLabel.adjustsFontSizeToFitWidth = true
khToggle.addTarget(self,
action: #selector(toggleKeepHide(_:)),
for: .valueChanged)
view.addConstraints([
.........
NSLayoutConstraint(item: khToggle, attribute: .right, relatedBy: .equal,
toItem: view, attribute: .right, multiplier: 1.0, constant: -30.0),
NSLayoutConstraint(item: khToggle, attribute: .top, relatedBy: .equal,
toItem: yPanel, attribute: .bottom, multiplier: 1.0,
constant: 50.0),
NSLayoutConstraint(item: khLabel, attribute: .right, relatedBy: .equal,
toItem: khToggle, attribute: .left, multiplier: 1.0, constant: -23.0),
NSLayoutConstraint(item: khLabel, attribute: .centerY, relatedBy: .equal,
toItem: khToggle, attribute: .centerY, multiplier: 1.0, constant: 0.0),
NSLayoutConstraint(item: khLabel, attribute: .left, relatedBy: .equal,
toItem: view, attribute: .left, multiplier: 1.0, constant: 30.0)])
}
#objc func toggleKeepHide(_ sender: UISwitch) {
if sender.isOn {khLabel.text = "Hide this object from the wyxoug list."}
else {khLabel.text = "Keep this object in the wyxoug list."}
}
}
You haven't provided sufficient information to reproduce the problem. Here's a reduction of your code, in my view controller's viewDidLoad (I eliminated everything but the label and the switch, fixed your left and right (you should never use those), and changed the alignment between the two views to top instead of center):
khToggle = UISwitch()
khToggle.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(khToggle)
khLabel = UILabel()
khLabel.text = String(repeating: "word ", count: 40)
khLabel.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(khLabel)
// Do any additional setup after loading the view.
khLabel.numberOfLines = 0
view.addConstraints([
NSLayoutConstraint(item: khToggle!, attribute: .trailing, relatedBy: .equal,
toItem: view, attribute: .trailing, multiplier: 1.0, constant: -30.0),
NSLayoutConstraint(item: khToggle!, attribute: .top, relatedBy: .equal,
toItem: view, attribute: .top, multiplier: 1.0,
constant: 50.0),
NSLayoutConstraint(item: khLabel!, attribute: .trailing, relatedBy: .equal,
toItem: khToggle, attribute: .leading, multiplier: 1.0, constant: -23.0),
NSLayoutConstraint(item: khLabel!, attribute: .top, relatedBy: .equal,
toItem: khToggle, attribute: .top, multiplier: 1.0, constant: 0.0),
NSLayoutConstraint(item: khLabel!, attribute: .leading, relatedBy: .equal,
toItem: view, attribute: .leading, multiplier: 1.0, constant: 30.0)])
The result displays fine; there are no constraint conflicts or ambiguities, and it looks as one would expect:
The horizontal constraints for the label and the toggle are competing against each other.
(If you step in to Xcode's visual debugger, you'll find that there is a warning: the UISwitch instance has an ambiguous width and horizontal position.)
Solution
You have provided absolute values to AutoLayout, and it cannot resolve the requirements. To fix this, introduce some flexibility in to the requirements by setting the compression resistance of the label to a lower value:
khLabel.setCompressionResistance(.defaultLow, for: .horizontal)

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

How do I add constraints programmatically to my UILabel

Is there a way in swift to have a label and make constraints for it programmatically. For example on all devices to 'Pin to the top' or 'Pin to the right side' so that on all devices it just pins to whatever device is being used.
It's because I've created a label programmatically, so I want to make constraints for it.
If you need more info, just let me know. Thanks :)
override func viewDidLoad() {
super.viewDidLoad()
let label = UILabel()
self.view.addSubview(label)
label.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
label.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
label.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
label.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
}
This will bind the item to all the edges, making it as big as the screen. Anchor is used to refer to an point in game. which is either its superview or some view on the same level as your current view. You can use references as i did self.view.trailingAnchor, and you can also add offsets and insets view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 8).isActive = true. Or you can name the direct size of something label.heightAnchor.constraint(equalToConstant: CGFloat).isActive = true. Good luck
You can easily add Constraints programatically using NSLayoutConstraints. Below is the code sample for it. I have used centering constraint , you can use leading and top constraints with height and width.
func addLabel(){
let newView =UILabel()
self.view.addSubview(newView)
newView.translatesAutoresizingMaskIntoConstraints = false
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: 0)
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)
NSLayoutConstraint.activate([horizontalConstraint, verticalConstraint, widthConstraint, heightConstraint])
}
the code below, will stick your label to top-right as you want, on all d devices,
let aLabel = UILabel()
self.view.addSubview(aLabel)
aLabel.backgroundColor = UIColor.gray
aLabel.text = " i am a label"
aLabel.textAlignment = .center
aLabel.translatesAutoresizingMaskIntoConstraints = false
let horizontalConstraint = NSLayoutConstraint(item: aLabel, attribute: NSLayoutAttribute.trailing, relatedBy: NSLayoutRelation.equal, toItem: view, attribute:NSLayoutAttribute.trailing, multiplier: 1, constant: 0)
let verticalConstraint = NSLayoutConstraint(item: aLabel, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem: view, attribute: NSLayoutAttribute.top, multiplier: 1, constant: 0)
NSLayoutConstraint.activate([horizontalConstraint, verticalConstraint])
self.view.layoutIfNeeded()

Auto-Layout fit to parent via code in Swift

I have a view i'm creating via code and adding to another view as subview.
The new superview can change it's frame over time and I want the newly created subview to change it's frame accordingly.
How can I do that using Auto-Layout via code in Swift?
Here is an example:
let view = UIView() // existing view
let subview = UIView()
subview.setTranslatesAutoresizingMaskIntoConstraints(false)
view.addSubview(subview)
view.addConstraint(NSLayoutConstraint(item: subview, attribute: .Top, relatedBy: .Equal, toItem: view, attribute: .Top, multiplier: 1.0, constant: 0.0))
view.addConstraint(NSLayoutConstraint(item: subview, attribute: .Leading, relatedBy: .Equal, toItem: view, attribute: .Leading, multiplier: 1.0, constant: 0.0))
view.addConstraint(NSLayoutConstraint(item: view, attribute: .Bottom, relatedBy: .Equal, toItem: subview, attribute: .Bottom, multiplier: 1.0, constant: 0.0))
view.addConstraint(NSLayoutConstraint(item: view, attribute: .Trailing, relatedBy: .Equal, toItem: subview, attribute: .Trailing, multiplier: 1.0, constant: 0.0))
iOS 13, swift 5
First, you add this code
subview.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(subview)
Then, there are two ways of doing this in newer versions of iOS.
With NSLayoutConstraint class
NSLayoutConstraint(item: subview, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: subview, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: subview, attribute: .left, relatedBy: .equal, toItem: view, attribute: .left, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: subview, attribute: .right, relatedBy: .equal, toItem: view, attribute: .right, multiplier: 1, constant: 0).isActive = true
With NSLayoutAnchor class (less verbose)
subview.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
subview.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
subview.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
subview.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
Either way, on iOS 8 and later Apple recommends using isActive() instead of adding constraints directly to a view.
Additionally, I believe the purpose of the NSLayoutAnchor method is to be more concise and readable compared to NSLayoutConstraint.
As #rjobidon mentioned you should use following code (Swift3)
let view = UIView() // existing view
let subview = UIView()
subview.setTranslatesAutoresizingMaskIntoConstraints(false)
view.addSubview(subview)
NSLayoutConstraint(item: subview, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1.0, constant: 0.0).isActive = true
NSLayoutConstraint(item: subview, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1.0, constant: 0.0).isActive = true
NSLayoutConstraint(item: view, attribute: .bottom, relatedBy: .equal, toItem: subview, attribute: .bottom, multiplier: 1.0, constant: 0.0).isActive = true
NSLayoutConstraint(item: view, attribute: .trailing, relatedBy: .equal, toItem: subview, attribute: .trailing, multiplier: 1.0, constant: 0.0).isActive = true
You can also activate the constraints like that :
let view = UIView() // existing view
let subview = UIView()
subview.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(subview)
NSLayoutConstraint.activate([
view.leadingAnchor.constraint(equalTo: subview.leadingAnchor),
view.trailingAnchor.constraint(equalTo: subview.trailingAnchor),
view.topAnchor.constraint(equalTo: subview.topAnchor),
view.bottomAnchor.constraint(equalTo: subview.bottomAnchor)
])