Autolayout for dynamically added UIViews - ios

I need some recommendation on how to add constraints to dynamically added UILabels
Say I have a UIView called (A) of size (screenwidth, 200) added to a UIViewcontroller view. On view A, I have set the autoresize masks on it's leading edge and trailing edge with the superview.
Now at runtime, I am adding UILabels with characters from a String. For example, if the string is "HELLO", I am adding five UILabels at equally spaced intervals for each character in HELLO.
When the orientation changes from portrait to landscape the UILabels are not evenly spaced. I haven't set any constraints on the UILabels and need some suggestion on how to add the constraints.
Is there any other solution other than constraints available to make the UILabels evenly spread out in landscape and portrait mode?
Thanks

I can recommend using UIStackViews. Sweeper's solution is not using dynamic addition to the screen, so I thought I would provide a programmatic solution.
To add these dynamically, use the following example.
First:
#IBOutlet weak var containerView: UIView!
This should be view A from your description, and make sure to hook this up with the storyboard.
To add labels, we first add a stack view to the container view:
let stackView = UIStackView()
stackView.distribution = .fillEqually
stackView.alignment = .fill
stackView.spacing = 15
stackView.autoresizingMask = [.flexibleLeftMargin,.flexibleRightMargin]
containerView.addSubview(stackView)
stackView.centerXAnchor.constraint(equalTo: (stackView.superview?.centerXAnchor)!, constant: 0).isActive = true
stackView.centerYAnchor.constraint(equalTo: (stackView.superview?.centerYAnchor)!, constant: 0).isActive = true
stackView.heightAnchor.constraint(equalTo: (stackView.superview?.heightAnchor)!, constant: 0).isActive = true
stackView.widthAnchor.constraint(equalToConstant: 200).isActive = true
You can play around with the width anchor to get what you want.
Then when you want to add a label, use this:
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 5, height: 5))
label.text = "E"
stackView.addArrangedSubview(label)
let labelTwo = UILabel(frame: CGRect(x: 0, y: 0, width: 5, height: 5))
labelTwo.text = "L"
stackView.addArrangedSubview(labelTwo)
//... And so on
Stack views make it easy to add subviews without too many constraints. However, you do have to add constraints to the stack view itself to get something on the screen!
I hope this helps.

In iOS 9, UIStackViews got introduced. This is a great opportunity to use them!
The stack view will have constraints on leading, trailing and other necessary properties so that it stays where you want. The distribution of the stack view should be "Fill Equally".
Add an outlet to your VC and when needed, add your labels into the stack view!

Related

How can I change height of the view which is present in StackView

I have a custom UIView using for Custom UITabBar whose height is fixed 50 and I am adding a stackview for each button but I need middle button height should be more.
let view: UIView = views[3]
view.heightAnchor.equalToConstant(100.0).isActive = true
let stackView: UIStackView = UIStackView(arrangedSubviews: [views])
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .horizontal
stackView.distribution = .fillEqually
addSubview(stackView)
stackView.topAnchor.constraint(equalTo: topAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
stackView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
How can I change the height of a particular view inside the stackView?
I may not understand your problem but you can change anyviews height using constraints as you have already done.
It is same when the view is subview of a stackview. Stackview updates it's size to satisfy its constraint.
You have to have a reference to height constraint of the view inside stackview.
Then you can change it whenever need to.
Also it would be nice to set stackviews alignment property to have desired look.
let heightConstraint = view.heightAnchor.equalToConstant(100.0)
heightConstraint.isActive = true
.
.
.
heightConstraint.constant = 150
You want to change the Alignment property of the stack view...
Assuming 5 views, each with a height constraint of 60, and you want the 4th view (views[3]) to have a height of 100.
StackView Alignment Top:
StackView Alignment Center:
StackView Alignment Bottom:

Horizontal UIStackView - fix center item's size and stretch other items

I am trying to achieve a layout like this:
Essentially, I need the or label to stay in the middle and occupy a fixed width and the lines to stretch out towards the edges of the screen.
Here's what I did in the XIB:
created a horizontal UIStackview, with center alignment.
Set the height constraint of the stackview to 20, distribution to fill.
added two UIView elements(for the gray lines), with a height constraint set to 5.
Added a UILabel between the two UIView elements above.
Added more constraints:
left UIView leads with 0 from superview and trails with 5 to middle label
right UIView leads with 4 from middle label and trails with 0 to superview.
Looks fine on the Interface builder, but on different screen sizes and landscapes, I find the middle "or" label to stretch and kick away the left and right UIViews to make up for the available space, which seems right:
If I set a width constraint of 20 on the middle label, the right UIView stretches unevenly like this:
I know the CHP of the elements matters to some extent here, I have even tried setting CHPs like this:
CHP of the middle label is 251
CHP of left and right UIViews is 250.
This still leaves me with the uneven stretching of right UIView.
What is it that I am doing wrong? Insights much appreciated!
Thanks a lot!
You need to set widthConstraint on UIStackView and the make leftView.widthAnchor = rightView.widthAnchor. Alternatively you can set leading and trailing constraints on UIStackView and then set leftView.widthAnchor = rightView.widthAnchor.
Below is the sample code you can try out in Playgrounds
let leftView = UIView()
leftView.translatesAutoresizingMaskIntoConstraints = false
leftView.backgroundColor = .lightGray
let rightView = UIView()
rightView.translatesAutoresizingMaskIntoConstraints = false
rightView.backgroundColor = .lightGray
let orLabel = UILabel()
orLabel.translatesAutoresizingMaskIntoConstraints = false
orLabel.textColor = .black
orLabel.text = "or"
let stackView = UIStackView(arrangedSubviews: [leftView, orLabel, rightView])
stackView.alignment = .center
stackView.distribution = .fill
stackView.axis = .horizontal
stackView.spacing = 5
stackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackView)
stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
stackView.heightAnchor.constraint(equalToConstant: 20).isActive = true
stackView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
leftView.heightAnchor.constraint(equalToConstant: 5).isActive = true
rightView.heightAnchor.constraint(equalToConstant: 5).isActive = true
leftView.widthAnchor.constraint(equalTo: rightView.widthAnchor).isActive = true
Don't set any leading or trailing constraints...
Set "right view" width constraint equal to "left view" width, and give your stack view a spacing value of 4 or 5.
Storyboard:
Portrait:
Landscape:

How to increase UIView height which contains UIStackView

I have a custom view which contains a label, label can have multiple line text. So i have added that label inside a UIStackView, now my StackView height is increasing but the custom view height doesn't increases. I haven't added bottom constraint on my StackView. What should I do so that my CustomView height also increases with the StackView.
let myView = Bundle.main.loadNibNamed("TestView", owner: nil, options: nil)![0] as! TestView
myView.lbl.text = "sdvhjvhsdjkvhsjkdvhsjdvhsdjkvhsdjkvhsdjkvhsjdvhsjdvhsjdvhsjdvhsjdvhsjdvhsjdvhsdjvhsdjvhsdjvhsdjvhsdjvhsjdvhsdjvhsdjvhsjdvhsdjvhsjdvhsdjvhsdjvhsdjvhsjdv"
myView.lbl.sizeToFit()
myView.frame = CGRect(x: 10, y: 100, width: UIScreen.main.bounds.width, height: myView.frame.size.height)
myView.setNeedsLayout()
myView.layoutIfNeeded()
self.view.addSubview(myView)
I want to increase my custom view height as per my stackview height.
Please help.
Example of stackView constraints with its superview.
Also superview should not have constraints for its height.
You should set the top and bottom anchors of your custom view to be constrained to the top and bottom anchors of your stackview. As your stackView grows, it will push that bottom margin along. Here's a programmatic example:
//: A UIKit based Playground for presenting user interface
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
private lazy var stackView = UIStackView()
private lazy var addLabelButton = UIButton(type: .system)
override func loadView() {
let view = UIView()
view.backgroundColor = .white
let stackViewContainer = UIView(frame: view.bounds)
stackViewContainer.backgroundColor = .yellow
stackViewContainer.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackViewContainer)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
addLabelButton.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(addLabelButton)
stackViewContainer.addSubview(stackView)
NSLayoutConstraint.activate([
// Container constrained to three edges of its superview (fourth edge will grow as the stackview grows
stackViewContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor),
stackViewContainer.trailingAnchor.constraint(equalTo: view.trailingAnchor),
stackViewContainer.topAnchor.constraint(equalTo: view.topAnchor),
// stackView constraints - stackView is constrained to the
// for corners of its contaier, with margins
{
// Stackview has a height of 0 when no arranged subviews have been added.
let heightConstraint = stackView.heightAnchor.constraint(equalToConstant: 0)
heightConstraint.priority = .defaultLow
return heightConstraint
}(),
stackView.topAnchor.constraint(equalTo: stackViewContainer.topAnchor, constant: 8),
stackView.leadingAnchor.constraint(equalTo: stackViewContainer.leadingAnchor, constant: 8),
stackView.trailingAnchor.constraint(equalTo: stackViewContainer.trailingAnchor, constant: -8),
stackView.bottomAnchor.constraint(equalTo: stackViewContainer.bottomAnchor, constant: -8),
// button constraints
addLabelButton.topAnchor.constraint(equalTo: stackViewContainer.bottomAnchor, constant: 8),
addLabelButton.centerXAnchor.constraint(equalTo: stackViewContainer.centerXAnchor)
])
addLabelButton.setTitle("New Label", for: .normal)
addLabelButton.addTarget(self, action: #selector(addLabel(sender:)), for: .touchUpInside)
self.view = view
}
private(set) var labelCount = 0
#objc func addLabel(sender: AnyObject?) {
let label = UILabel()
label.text = "Label #\(labelCount)"
labelCount += 1
stackView.addArrangedSubview(label)
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
Note that when the UIStackView is empty, its height is not well defined. That is why I set its heightAnchor constraint to 0 with a low priority.
First of all you should add bottom constraint on your UIStackView. This will help auto layout in determining the run time size of UIStackView.
Now create instance of your custom UIView but do not set it's frame and add it to UIStackView. Make sure you Custom UiView has all the constraints set for auto layout to determine it's run time frame.
This will increase height of both UIView and UIStackView based on content of UIView elements.
For more details you can follow my detailed answer on this at https://stackoverflow.com/a/57954517/3339966

How to make auto resizing buttons via swift 3 that work between different Iphones

I'll post down below a part of my code on an app I'm working on for fun, the concept it's different, but the design of the camera session is very similar with the instagram app. I'm also using the PageMenu cocoa pod, that is really good and easy to implement via code.
override func viewDidLoad() {
super.viewDidLoad()
pageMenu?.delegate = self
photoControllerBG.frame = CGRect(x: 0, y : self.view.frame.minY, width: self.view.frame.width, height: self.view.frame.height )
let cameraButton = UIButton(frame: CGRect(x: self.view.frame.midX - , y: photoControllerBG.frame.minY + (self.view.frame.maxY/20*1), width: 130, height: 130))
cameraButton.addTarget(self, action: #selector(cameraButtonPressed(button:)), for: .touchUpInside)
cameraButton.setImage(#imageLiteral(resourceName: "Camera Button.png"), for: UIControlState.normal)
self.view.addSubview(cameraButton)
}
At the moment, the button on an iPhone 7 Plus will display like I want it to, centered in the ViewController and with the Image of the camera button with the same width and height so it stay a perfect circle. The problem presents itself when i switch the simulator with a iPhone SE or iPhone 6, where the button will become too big and not centered. Can you guys help me to find a math formula that it can be used to solve this big but small problem?
Your question implies doing the following in code (swift). Unless you are creating the button in swift I wouldn't do that at all. So, If you have a button created in a storyboard you can scale it appropriately as follows:-
I would use a proportional width constraint combined with an aspect ratio constraint in the story board to achieve this.
It may not be apparent straight away how to do this so I attach a picture journey.
Add your button to the view controller. Image shows final constraints.
Add an Equal Width constraint between the button and it's superview
The button doesn't have equal width to it's superview! My button is 100 px wide and the superview is 375 px wide. So let's calculate that ratio to help us calculate the proportional width.
Edit the Equal width constraint by using the proportional width above as the multiplier.
Add an aspect ratio to the button
Add any remaining constraints (In my case I centred the button horizontally and vertically)
Your constraints for the button should look like this:
let label = UILabel()
label.text = "title"
label.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)
let button = UIButton(type: .system)
button.translatesAutoresizingMaskIntoConstraints = false
button.setImage(#imageLiteral(resourceName: "image"), for: .normal)
view.addSubview(button)
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
label.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor, constant: 16)
])
NSLayoutConstraint.activate([
button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
button.topAnchor.constraint(equalTo: label.bottomAnchor),
button.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.3), //change your multiplier
button.heightAnchor.constraint(equalTo: button.widthAnchor)
])

UIStackView - Want a percentage to define each item

I want to put two views into a UIStackView. I want the top item to always be 30% of the space no matter what device (or orientation) that it appears. Thus, I want to have the bottom view to be 70%. How do I tell a UIStackView that I want to use a percentage?
Example resulting view
Just set a constraint for the top view.
In the storyboard's Document Outline control-drag from the top view to the stack view and select Equal heights. This will make their heights equal. Now go to the Size Inspector and you should see the constraint. Double click on it and set the multiplier to 0.3. Then update the frames.
If the bottom view doesn't automatically size or it gives you an error telling that you need a height constraint, just repeat the process, but now set the multiplier to 0.7.
Swift 5.2
To implement this programmatically
Setting UIView to always be 30% of the size of it's superview within a UIStackView
viewA.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.3).isActive = true
Full code implementation:
// Configure the StackView
let stackView = UIStackView()
stackView.axis = .vertical
stackView.distribution = .fill
view.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
// Configure View A
let viewA = UIView()
viewA.backgroundColor = .darkGray
viewA.translatesAutoresizingMaskIntoConstraints = false
// Configure View B
let viewB = UIView()
viewB.backgroundColor = .lightGray
viewB.translatesAutoresizingMaskIntoConstraints = false
// Add Views to StackView
stackView.addArrangedSubview(viewA)
stackView.addArrangedSubview(viewB)
// Set the height percentage multiplier to View A
viewA.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.3).isActive = true
From apple documentation
UIStackViewDistributionFillProportionally A layout where the stack
view resizes its arranged views so that they fill the available space
along the stack view’s axis. Views are resized proportionally based on
their intrinsic content size along the stack view’s axis.
So according to this you should set UIStackView distribution property to UIStackViewDistributionFillProportionally and set intrinsic size, its.
You can get more info here and on Intrinsic content size

Resources