displaying images in a stack view - ios

Is it possible to display an image from the assets folder to a uistackview programmatically? If so, how would you go about doing it?
I already know how to create a stack view filled with labels.
fileprivate lazy var stack: UIStackView = {
let stack = UIStackView(arrangedSubviews: [goalCompleteLabel, completeMoreGoalsLabel])
stack.translatesAutoresizingMaskIntoConstraints = false
stack.axis = .vertical
return stack
} ()

First set your image in imageView (set in it its constraints for more control of image dimension) and your label under your class controller:
let image: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "yourImage")?.withRenderingMode(.alwaysOriginal)
imageView.backgroundColor = .gray
imageView.layer.cornerRadius = 8
imageView.clipsToBounds = true
imageView.contentMode = .scaleAspectFill
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.heightAnchor.constraint(equalToConstant: 200).isActive = true
imageView.widthAnchor.constraint(equalToConstant: 200).isActive = true
return imageView
}()
let completeMoreGoalsLabel: UILabel = {
let label = UILabel()
label.text = "Dummytext"
label.textAlignment = .center
return label
}()
now set your stack view with distribution fillProportionally:
lazy var stack: UIStackView = {
let stackView = UIStackView(arrangedSubviews: [image, completeMoreGoalsLabel])
stackView.axis = .vertical
stackView.distribution = .fillProportionally
stackView.translatesAutoresizingMaskIntoConstraints = false
return stackView
}()
in viewDidLoad present your stack and add constraints:
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .darkGray
view.addSubview(stack)
stack.widthAnchor.constraint(equalToConstant: 200).isActive = true
stack.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
stack.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
stack.heightAnchor.constraint(equalToConstant: 250).isActive = true //200 imageHeight + 50 label height
}
I add corner radius on image to make it more cute...

Yes. You simply need to add an ImageView as an arranged subview of your stackview. Just like the labels. Here's the code -
class StackViewController: UIViewController {
var stackView = UIStackView()
var label = UILabel()
var imageView = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
configStackView()
}
func configStackView() -> Void {
// Add StackView as SubView
view.addSubview(stackView)
// Set StackView properties
stackView.axis = .vertical
stackView.alignment = .center
stackView.distribution = .equalSpacing
// Set imageView as 1st arranged subview of stackview
stackView.addArrangedSubview(imageView)
configImageView()
// Set Label as 2nd arranged subview of stackview
stackView.addArrangedSubview(label)
configLabel()
// Set StackView Constraints
setStackViewCostraints()
}
func setStackViewCostraints() -> Void {
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20).isActive = true
stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20).isActive = true
stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
stackView.heightAnchor.constraint(equalToConstant: 200).isActive = true
}
func configImageView() -> Void {
imageView.image = UIImage(named: "bolt")
imageView.contentMode = .scaleAspectFit
// Set Constraints (ideally in a separate function)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.widthAnchor.constraint(equalToConstant: 100).isActive = true
}
func configLabel() -> Void {
label.text = "Label"
}
}
Here's the how it renders -

Related

How to programmatically align elements in a Stackview using SnapKit?

I'm trying to achieve a specific design inside a custom reusable view.
(Something like this: )
So I pass an URL to retrive the image and I pass a String to add below.
Firstly, I want the entire view to be the width of the elements (if the text is long then the entire view will be wider), I don't know how to do that, the view seems to be the entire width of the screen.
Secondly, I want the items to be centered horizontally and vertically, what I tried does not work.
Here is my current code :
func initLayout() {
stackView.axis = NSLayoutConstraint.Axis.vertical
stackView.distribution = UIStackView.Distribution.fillEqually
stackView.alignment = UIStackView.Alignment.center
stackView.spacing = 10.0
stackView.addArrangedSubview(imageView)
stackView.addArrangedSubview(textContainer)
stackView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(stackView)
self.snp.makeConstraints { (make) -> Void in
make.height.equalTo(70)
}
self.stackView.snp.makeConstraints{ (make) -> Void in
make.edges.equalTo(self)
}
}
And it results in something like this:
As you can (or cannot) see, the view is centered in the middle of the screen, which is not what I want. The view should be the width of the text and everything centered inside this particular width, then I add it inside my VC and place it so it's leading.
if I understand well the is your constraint without Snapkit:
Set your objects under your class controller declaration:
let myImageView: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleToFill
iv.clipsToBounds = true
iv.backgroundColor = .red
iv.translatesAutoresizingMaskIntoConstraints = false
return iv
}()
let myLabel: UILabel = {
let label = UILabel()
label.text = "Débats"
label.textColor = .white
label.textAlignment = .center
label.font = .systemFont(ofSize: 30, weight: .semibold) // set your font size here
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
Now in viewDidLoad set parameters and constraints:
myImageView.image = UIImage(named: "profilo") // set here your image
let myWidth = myLabel.intrinsicContentSize.width // This reveal only text width in label
view.addSubview(myLabel)
myLabel.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5).isActive = true
myLabel.heightAnchor.constraint(equalToConstant: 50).isActive = true
myLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 25).isActive = true
myLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
view.addSubview(myImageView)
myImageView.widthAnchor.constraint(equalToConstant: myWidth).isActive = true
myImageView.heightAnchor.constraint(equalTo: myImageView.widthAnchor).isActive = true
myImageView.bottomAnchor.constraint(equalTo: myLabel.topAnchor).isActive = true
myImageView.centerXAnchor.constraint(equalTo: myLabel.centerXAnchor).isActive = true
This is the result:
Intere code:
class Aiutotipo: UIViewController {
let myImageView: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleToFill
iv.clipsToBounds = true
iv.backgroundColor = .red
iv.translatesAutoresizingMaskIntoConstraints = false
return iv
}()
let myLabel: UILabel = {
let label = UILabel()
label.text = "Débats"
label.textColor = .white
label.textAlignment = .center
label.font = .systemFont(ofSize: 30, weight: .semibold)
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
myImageView.image = UIImage(named: "profilo") // set here your image
let myWidth = myLabel.intrinsicContentSize.width // This reveal only text width in label
view.addSubview(myLabel)
myLabel.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5).isActive = true
myLabel.heightAnchor.constraint(equalToConstant: 50).isActive = true
myLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 25).isActive = true
myLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
view.addSubview(myImageView)
myImageView.widthAnchor.constraint(equalToConstant: myWidth).isActive = true
myImageView.heightAnchor.constraint(equalTo: myImageView.widthAnchor).isActive = true
myImageView.bottomAnchor.constraint(equalTo: myLabel.topAnchor).isActive = true
myImageView.centerXAnchor.constraint(equalTo: myLabel.centerXAnchor).isActive = true
}
}

in nested UIStackView when I add more than one arranged subview, it breaks the layout, why?

Playing with UIStackView I encountered a weird issue which does not allow me to add extra arranged subview in nested UIStackView:
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
let subViews = [UIColor.gray, UIColor.darkGray, UIColor.lightGray].map { (color) -> UIView in
let v = UIView()
v.backgroundColor = color
return v
}
let redView = UIStackView(arrangedSubviews: subViews)
redView.distribution = .fillEqually
redView.heightAnchor.constraint(lessThanOrEqualToConstant: 100).isActive = true
let blueView = UIView()
blueView.backgroundColor = .blue
let buttons = [UIColor.gray, UIColor.darkGray, UIColor.lightGray].map { (color) -> UIView in
let v = UIView()
v.backgroundColor = color
return v
}
let buttonsView = UIStackView(arrangedSubviews: buttons)
buttonsView.distribution = .fillEqually
buttonsView.heightAnchor.constraint(lessThanOrEqualToConstant: 100).isActive = true
let stackView = UIStackView(arrangedSubviews: [redView, blueView, buttonsView])
view.addSubview(stackView)
stackView.axis = .vertical
stackView.fillSuperview()
}
In result, I get full-stretched blue view instead of expecting behavior:
But when I leaving ONE subview in bottom stack view - it appears as expected
let buttons = [UIColor.gray].map { (color) -> UIView in
let v = UIView()
v.backgroundColor = color
return v
}
let buttonsView = UIStackView(arrangedSubviews: buttons)
buttonsView.distribution = .fillEqually
buttonsView.heightAnchor.constraint(lessThanOrEqualToConstant: 100).isActive = true
let stackView = UIStackView(arrangedSubviews: [redView, blueView, buttonsView])
why ? What is wrong with the code ? Any help or hint is appreciated, I tried translatesAutoresizingMaskIntoConstraints = false on top and bottom stack views as well but without any luck
on the bottom stackview it was required to set
buttonView.heightAnchor.constraint(equalToConstant: 100).isActive = true
instead of
buttonView.heightAnchor.constraint(lessThanOrEqualToConstant: 100).isActive = true

UIStackView container view height based on subviews

Here is my simple example. I have 1 vertical stack view with 1 subview. I want that subviews height to be based on the intrinsic height of the label within it, so that I can maintain a dynamic height for the entire stack view. How can this be done? Thanks
I think you did it right. But here is the keys:
Don't set height for stackView.
Set label top, bottom, left, trailing constraint to view.
Run. It should be okay on simulator.
If you found label's height seems not wrapping (neither both on storyboard or simulator), then change label's Vertical Content Hugging Priority to 750.
Try this code:
class DyanmicTextLabelViewController: UIViewController {
private var didAddConstraint = false
private let label: UILabel = {
let view = UILabel()
view.translatesAutoresizingMaskIntoConstraints = false
view.setContentHuggingPriority(.required, for: .vertical)
view.setContentCompressionResistancePriority(.required, for: .vertical)
view.text = "Layout anchors let you create constraints in an easy-to-read, compact format. They expose a number of methods for creating different types of constraints, as shown in Listing 13-1."
view.numberOfLines = 0
return view
}()
private lazy var container: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)
view.backgroundColor = .red
return view
}()
private lazy var stackview : UIStackView = {
let view = UIStackView()
view.translatesAutoresizingMaskIntoConstraints = false
view.axis = .horizontal
view.distribution = .fill
view.addArrangedSubview(container)
return view
}()
override func loadView() {
super.loadView()
view.addSubview(stackview)
view.setNeedsUpdateConstraints()
view.backgroundColor = .white
}
override func updateViewConstraints() {
super.updateViewConstraints()
if didAddConstraint == false {
didAddConstraint = true
// stackview constraints
stackview.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
let topAnchor = stackview.topAnchor.constraint(equalTo: view.topAnchor)
topAnchor.constant = 20
topAnchor.isActive = true
stackview.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
// label constraint
// example for giving label a left padding
let labelLeft = label.leftAnchor.constraint(equalTo: container.leftAnchor)
labelLeft.constant = 16.0
labelLeft.isActive = true
label.topAnchor.constraint(equalTo: container.topAnchor).isActive = true
label.rightAnchor.constraint(equalTo: container.rightAnchor).isActive = true
label.bottomAnchor.constraint(equalTo: container.bottomAnchor).isActive = true
}
}
}
The important part here is the initialization of stackview, label & constraint set on label
label initialization
private let label: UILabel = {
let view = UILabel()
view.translatesAutoresizingMaskIntoConstraints = false
view.setContentHuggingPriority(.required, for: .vertical)
view.setContentCompressionResistancePriority(.required, for: .vertical)
view.text = "Layout anchors let you create constraints in an easy-to-read, compact format. They expose a number of methods for creating different types of constraints, as shown in Listing 13-1."
view.numberOfLines = 0
return view
}()
stackview initialization
private lazy var stackview : UIStackView = {
let view = UIStackView()
view.translatesAutoresizingMaskIntoConstraints = false
view.axis = .horizontal
view.distribution = .fill
view.addArrangedSubview(container)
return view
}()
label constraint
// label constraint
// example for giving label a left padding
let labelLeft = label.leftAnchor.constraint(equalTo: container.leftAnchor)
labelLeft.constant = 16.0
labelLeft.isActive = true
label.topAnchor.constraint(equalTo: container.topAnchor).isActive = true
label.rightAnchor.constraint(equalTo: container.rightAnchor).isActive = true
label.bottomAnchor.constraint(equalTo: container.bottomAnchor).isActive = true
This settings could be easily translated to storyboard.

Can't adjust layouts on multiple devices in Swift

I'm trying to adjust layout without Storyboard in Swift. But using the code below, I can't adjust layout on iPhone Plus series (6+, 7+, and 8+).
Case1
Using images based on 640x1136 size, it works on iPhone8, 8+, and X.
class Constants {
// the image parts are based on 640x1136
static let guiPartsWidthOnDesign = CGFloat(640)
static var guiPartsMultiplier: CGFloat = UIScreen.main.bounds.width / guiPartsWidthOnDesign
}
Make buttons
for i in 1..<5 {
let subButton = UIImageView.init(frame: CGRect(
x: UIScreen.main.bounds.size.width/2-50*Constants.guiPartsMultiplier,
y: (100*Constants.guiPartsMultiplier)+CGFloat(i*100),
width: 187*Constants.guiPartsMultiplier,
height: 100*Constants.guiPartsMultiplier))
background.addSubview(subButton)
subButton.backgroundColor = UIColor.blue
subButton.alpha = 1.0
}
The results on multiple devices look the same layout.
Case2
Using images based on 750x1334 size, it didn't work well on iPhone8+.
class Constants {
// the images are based on 750x1334
static let guiPartsWidthOnDesign = CGFloat(750)
static var guiPartsMultiplier: CGFloat = UIScreen.main.bounds.width / guiPartsWidthOnDesign
}
Make buttons (same to the previous code.)
Result
As you see, the blue square's y positions are different between iPhone8 and 8 Plus.
(The results of iPhone8 and X are the same.)
How can I solve this problem?
UPDATE
I tried to use UIStackView, but it didn't work well.
UPDATE2
//Define this as class variable
fileprivate lazy var stackView: UIStackView = {
let sv = UIStackView()
sv.backgroundColor = UIColor.green
sv.translatesAutoresizingMaskIntoConstraints = false
sv.axis = .vertical
sv.alignment = .fill
sv.distribution = .fillEqually
sv.spacing = 10
return sv
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(stackView)
stackView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
stackView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
let background = UIImageView.init(frame: UIScreen.main.bounds)
background.image = UIImage(named:"BG")
stackView.addSubview(background)
// background.translatesAutoresizingMaskIntoConstraints = false
// background.contentMode = .scaleAspectFill
for i in 1..<5 {
let subButton = UIImageView()
subButton.translatesAutoresizingMaskIntoConstraints = false
subButton.backgroundColor = UIColor.blue
subButton.alpha = 1.0
stackView.addArrangedSubview(subButton)
subButton.widthAnchor.constraint(equalTo: self.view.widthAnchor, multiplier: 0.3).isActive = true
}
}
*I'm sorry, I don't know but I couldn't upload the screenshot.
UPDATE3
I used the following code and the result was like this on all type devices:
//Define this as class variable
fileprivate lazy var stackView: UIStackView = {
let sv = UIStackView()
sv.translatesAutoresizingMaskIntoConstraints = false
sv.axis = .vertical
sv.alignment = .fill
sv.distribution = .fillEqually
sv.spacing = 10
return sv
}()
fileprivate lazy var catBg: UIImageView = {
let iv = UIImageView()
iv.translatesAutoresizingMaskIntoConstraints = false
iv.contentMode = .scaleAspectFill
iv.image = UIImage(named: "BG")
return iv
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(catBg)
catBg.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
catBg.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
catBg.widthAnchor.constraint(equalTo: self.view.widthAnchor, multiplier: 0.7).isActive = true
catBg.heightAnchor.constraint(equalTo: catBg.widthAnchor, multiplier: 1.78).isActive = true
self.catBg.addSubview(stackView)
stackView.centerXAnchor.constraint(equalTo: catBg.centerXAnchor).isActive = true
stackView.centerYAnchor.constraint(equalTo: catBg.centerYAnchor).isActive = true
for i in 1..<5 {
let subButton = UIImageView()
subButton.translatesAutoresizingMaskIntoConstraints = false
subButton.backgroundColor = UIColor.blue
subButton.alpha = 1.0
stackView.addArrangedSubview(subButton)
subButton.widthAnchor.constraint(equalTo: catBg.widthAnchor, multiplier: 0.3).isActive = true
//If you have an image you can remove or change this anchor
subButton.heightAnchor.constraint(equalToConstant: 54).isActive = true
}
}
UPDATE4
My ideal is the center of this image. But so far the result is like the right image.
Even if you are not using storyboards, I would suggest you using UIStackview. In the stackview you specific axis to be vertical and a spacing if you'd like. Set the stackview to fill equally.
Don't using actual frames to calculate position for multiple screen layouts, it will get tedious.
You should use autolayout constraints in particular anchors.
//Define this as class variable
fileprivate lazy var stackView: UIStackView = {
let sv = UIStackView()
sv.translatesAutoresizingMaskIntoConstraints = false
sv.axis = .vertical
sv.alignment = .fill
sv.distribution = .fillEqually
sv.spacing = 10
return sv
}()
fileprivate lazy var catBg: UIImageView = {
let iv = UIImageView()
iv.translatesAutoresizingMaskIntoConstraints = false
iv.contentMode = .scaleAspectFill
iv.image = UIImage(named: "your_img_name")
return iv
}()
//Put this in viewDidLoad
//Edit
self.view.addSubview(catBg)
//catBg.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
//catBg.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
//catBg.widthAnchor.constraint(equalTo: self.view.widthAnchor, multiplier: 0.7).isActive = true
//catBg.heightAnchor.constraint(equalTo: catBg.widthAnchor, multiplier: 1.78).isActive = true
catBg.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
catBg.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
catBg.rightAnchor.constraint(equalTo: self.view.rightAnchor).isActive = true
catBg.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
self.catBg.addSubview(stackView)
stackView.centerXAnchor.constraint(equalTo: catBg.centerXAnchor).isActive = true
stackView.centerYAnchor.constraint(equalTo: catBg.centerYAnchor).isActive = true
for i in 1..<5 {
let subButton = UIImageView()
subButton.translatesAutoResizingMaskIntoConstraints = false
subButton.backgroundColor = UIColor.blue
subButton.alpha = 1.0
stackView.addArrangedSubview(subButton)
subButton.widthAnchor.constraint(equalTo: catBg.widthAnchor, multiplier: 0.3).isActive = true
//If you have an image you can remove or change this anchor
subButton.heightAnchor.constraint(equalToConstant: 54).isActive = true
}

Setting constant width on subview of UIStackView when axis is vertical

I have a UIScrollView that contains a UIStackView, and I add views to it and if the UIStackView needs more space than the screen has then it will scroll thanks to the UIScrollView.
I am able to set constant heights on the views, but I also need to set a specific width on them, so that they have a specific width and are also centered in the stack view.
Something like this, except the widthAnchor does not work.
import UIKit
class ViewController: UIViewController {
let scrollView: UIScrollView = {
let sv = UIScrollView()
sv.translatesAutoresizingMaskIntoConstraints = false
sv.backgroundColor = .gray
return sv
}()
let stackView: UIStackView = {
let sv = UIStackView()
sv.translatesAutoresizingMaskIntoConstraints = false
sv.axis = .vertical
return sv
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(scrollView)
scrollView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
scrollView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
scrollView.addSubview(stackView)
stackView.leftAnchor.constraint(equalTo: scrollView.leftAnchor).isActive = true
stackView.rightAnchor.constraint(equalTo: scrollView.rightAnchor).isActive = true
stackView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
stackView.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
let view1 = UIView()
view1.backgroundColor = .red
let view2 = UIView()
view2.backgroundColor = .blue
let view3 = UIView()
view3.backgroundColor = .green
let view4 = UIView()
view4.backgroundColor = .purple
stackView.addArrangedSubview(view1)
stackView.addArrangedSubview(view2)
stackView.addArrangedSubview(view3)
stackView.addArrangedSubview(view4)
view1.heightAnchor.constraint(equalToConstant: 200).isActive = true
view2.heightAnchor.constraint(equalToConstant: 300).isActive = true
view3.heightAnchor.constraint(equalToConstant: 420).isActive = true
view4.heightAnchor.constraint(equalToConstant: 100).isActive = true
// This does not work.
// view1.widthAnchor.constraint(equalToConstant: 40).isActive = true
}
}
The alignment property on a UIStackView determines how its layout works perpendicular to its axis. By default, a UIStackView has an alignment of fill. In constraint terms, fill is like adding a constraint to (in this case) the left and right edges of the stack view for each arranged subview. These implicit constraints are likely causing your problem. Solution: set stackView.alignment = either leading, center, or trailing depending on your desired effect.

Resources