items inside UIStackView overflowing - swift

I've created a UIStackView programmatically and inside placed UITextViews.
when the number of the UITextViews is more than 5 the boxes start overflowing off the screen. Here's a screenshot of the situation:
And here's the code :
let stackview = UIStackView(arrangedSubviews: letterBoxes)
stackview.axis = .horizontal
stackview.spacing = 10
stackview.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(stackview)
stackview.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
stackview.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
what am I missing?
thank you

Also add .leading and .trailing constraints like this
view.addConstraint(NSLayoutConstraint(item: stackview, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1, constant: 0))
view.addConstraint(NSLayoutConstraint(item: stackview, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1, constant: 0))
so the complete code looks like:
let stackview = UIStackView(arrangedSubviews: letterBoxes)
stackview.axis = .horizontal
stackview.spacing = 10
stackview.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(stackview)
stackview.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
stackview.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
view.addConstraint(NSLayoutConstraint(item: stackview, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1, constant: 0))
view.addConstraint(NSLayoutConstraint(item: stackview, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1, constant: 0))

Related

Adding Constraint messed up view

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

UIScrollView with NSLayoutConstraint

UIScrollView is not scrolling the subviews but it does show a scroll-bar.
Here is what I am trying to do
class SetupViewController: UIViewController {
let scrollView = UIScrollView()
let pageLabel = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
self.setupViews()
self.setupConstraints()
self.setText()
}
func setupViews() {
self.scrollView.backgroundColor = .red
self.scrollView.translatesAutoresizingMaskIntoConstraints = false
// Page Label
self.pageLabel.font = UIFontLocalized(englishFontSize: 22, arabicFontSize: 22)
self.pageLabel.textAlignment = .center
self.pageLabel.translatesAutoresizingMaskIntoConstraints = false
}
func setupConstraints() {
// Add To Sub Views
self.view.addSubview(self.pageLabel)
self.view.addSubview(self.scrollView)
// Page Label
NSLayoutConstraint(item: self.pageLabel, attribute: .top, relatedBy: .equal, toItem: self.topLayoutGuide, attribute: .bottom, multiplier: 1.0, constant: 30.0).isActive = true
NSLayoutConstraint(item: self.pageLabel, attribute: .leading, relatedBy: .equal, toItem: self.view, attribute: .leading, multiplier: 1.0, constant: 20.0).isActive = true
NSLayoutConstraint(item: self.pageLabel, attribute: .width, relatedBy: .equal, toItem: self.view, attribute: .width, multiplier: 1.0, constant: -40.0).isActive = true
NSLayoutConstraint(item: self.pageLabel, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 36.0).isActive = true
// Scroll View
NSLayoutConstraint(item: self.scrollView, attribute: .top, relatedBy: .equal, toItem: self.line1, attribute: .bottom, multiplier: 1.0, constant: 0.0).isActive = true
NSLayoutConstraint(item: self.scrollView, attribute: .leading, relatedBy: .equal, toItem: self.view, attribute: .leading, multiplier: 1.0, constant: 0.0).isActive = true
NSLayoutConstraint(item: self.scrollView, attribute: .width, relatedBy: .equal, toItem: self.view, attribute: .width, multiplier: 1.0, constant: 0.0).isActive = true
NSLayoutConstraint(item: self.scrollView, attribute: .height, relatedBy: .equal, toItem: self.view, attribute: .height, multiplier: 1.0, constant: -100.0).isActive = true
self.scrollView.contentSize.height = 2000
// NSLayoutConstraint for rest of elements are removed in this example code.
}
}
I can confirm UIScrollView is added in the view because I checked by giving background red color to scrollview and it does show the background red color in correct position. My issue is the subviews does not move only the scroll bar is moving.
What could be the possible issue here?
Note: NSLayoutConstraint exist for all UIKit elements, I have not added it in the code.
I got this issue sorted our. As pointed out in the comment, the issue was related to superview in constraint which was set to self.view changing it to self.scrollView solved the issue.

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()

UIStackView not displaying

In my viewController viewDidLoad method I have:
let stackView = UIStackView()
stackView.axis = .Vertical
self.view.addSubview(stackView)
for _ in 1..<100{
let vw = UIButton(type: .System)
vw.setTitle("Button", forState: .Normal)
stackView.addArrangedSubview(vw)
}
but when I compile I only get a totally white screen.
What am I doing wrong?
You're adding the stack view to the parent view, but you're not telling the parent view how to lay the stack view out. If you're using constraint based layout, you need to pin the stack view to some of the edges of the parent view:
view.addConstraint(NSLayoutConstraint(item: stackView, Attribute: .Top, relatedBy: .Equal, toItem: self.view, attribute: .Top, multiplier: 1.0, constant: 0.0)
view.addConstraint(NSLayoutConstraint(item: stackView, Attribute: .Leading, relatedBy: .Equal, toItem: self.view, attribute: .Leading, multiplier: 1.0, constant: 0.0)
view.addConstraint(NSLayoutConstraint(item: stackView, Attribute: .Trailing, relatedBy: .Equal, toItem: self.view, attribute: .Trailing, multiplier: 1.0, constant: 0.0)
This will pin the stack view to the top, leading and trailing edges of the view.
The other approach would be to use Interface Builder to set up the constraints, as they are pretty verbose to manage in code.
You have add constraints or set frame for your stack view. Please check modified code below :
let stackView = UIStackView()
stackView.axis = .Vertical
stackView.distribution = .FillEqually
for i in 1..<100{
let vw = UIButton(type: .System)
vw.setTitle("Button\(i)", forState: .Normal)
stackView.addArrangedSubview(vw)
}
self.view.addSubview(stackView)
stackView.frame = self.view.bounds

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)
])