UILabel skeleton not showing while it's inside a stack view - ios

Simply, I'm trying to display the skeleton for two UILabels that are subviews of a stack view. When I'm saying label.isSkeletonable = true it doesn't work at all. However, when I make the stack view isSkeletonable = true it works and becomes like the picture below
class ArticleCellView: UITableViewCell {
// MARK: - *** Properties ***
static let cellIdentifier = "ArticleTableViewCell"
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.isSkeletonable = true
contentView.isSkeletonable = true
configure()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - *** UI Elements ***
lazy var titleLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 0
label.textAlignment = .natural
label.textColor = UIColor(named: "AccentColor")
label.font = UIFont.systemFont(ofSize: 13.0, weight: .medium)
label.isSkeletonable = true
return label
}()
lazy var abstractLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 2
label.textAlignment = .natural
label.textColor = UIColor(named: "AccentColor")
label.font = UIFont.systemFont(ofSize: 12.0)
label.isSkeletonable = true
return label
}()
lazy var thumbnailImageView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFill
imageView.frame.size.width = 100
imageView.layer.cornerRadius = imageView.frame.size.width / 2
imageView.clipsToBounds = true
imageView.isSkeletonable = true
return imageView
}()
lazy var stackView: UIStackView = {
let stack = UIStackView()
stack.contentMode = .scaleToFill
stack.distribution = .fillEqually
stack.spacing = 20
stack.isSkeletonable = true
return stack
}()
func configure() {
// Adding subviews
contentView.addSubview(thumbnailImageView)
contentView.addSubview(stackView)
stackView.addSubview(titleLabel)
stackView.addSubview(abstractLabel)
// Setting up the constraints
thumbnailImageView.snp.makeConstraints {
$0.leading.equalToSuperview().inset(10)
$0.width.height.equalTo(100)
$0.centerY.equalToSuperview()
}
stackView.snp.makeConstraints {
$0.leading.equalTo(thumbnailImageView.snp.trailing).offset(10)
$0.trailing.equalToSuperview().inset(10)
$0.top.bottom.equalTo(thumbnailImageView)
}
titleLabel.snp.makeConstraints {
$0.leading.equalToSuperview().inset(10)
$0.trailing.equalToSuperview().inset(2)
$0.top.equalToSuperview().inset(10)
}
abstractLabel.snp.makeConstraints {
$0.leading.equalToSuperview().inset(10)
$0.trailing.equalToSuperview().inset(2)
$0.top.equalTo(titleLabel.snp.bottom).offset(10)
$0.bottom.equalToSuperview().offset(2)
}
}
}
As far as you can tell, no other solution out there worked for me even clipsToBounds did nothing. I'm using the SkeletonView from the following: https://github.com/Juanpe/SkeletonView

First, you need to add the labels as arranged subviews of the stack view.
So, this:
stackView.addArrangedSubview(titleLabel)
stackView.addArrangedSubview(abstractLabel)
instead of this:
stackView.addSubview(titleLabel)
stackView.addSubview(abstractLabel)
Next, because you want the labels to be arranged Vertically, set the stack view axis (and spacing) accordingly:
stack.axis = .vertical
stack.spacing = 10
and, because we want the stack view to arrange the labels, don't set any constraints on the labels:
// titleLabel.snp.makeConstraints {
// $0.leading.equalToSuperview().inset(10)
// $0.trailing.equalToSuperview().inset(2)
// $0.top.equalToSuperview().inset(10)
// }
//
// abstractLabel.snp.makeConstraints {
// $0.leading.equalToSuperview().inset(10)
// $0.trailing.equalToSuperview().inset(2)
// $0.top.equalTo(titleLabel.snp.bottom).offset(10)
// $0.bottom.equalToSuperview().offset(2)
// }
and finally, it is a really good idea to add comments so you understand what your code is trying to do:
// Setting up the constraints
thumbnailImageView.snp.makeConstraints {
// image view leading is 10-points from superview (contentView) leading
$0.leading.equalToSuperview().inset(10)
// 100 x 100
$0.width.height.equalTo(100)
// top and bottom constrained to top and bottom of superview (contenView)
$0.top.bottom.equalToSuperview()
}
stackView.snp.makeConstraints {
// stackView leading is 10=points from image view trailing
$0.leading.equalTo(thumbnailImageView.snp.trailing).offset(10)
// 10-points from superview (contentView) trailing
$0.trailing.equalToSuperview().inset(10)
// stackView centered vertically to image view
$0.centerY.equalTo(thumbnailImageView)
}
Here's the full, modified version of your cell class:
class ArticleCellView: UITableViewCell {
// MARK: - *** Properties ***
static let cellIdentifier = "ArticleTableViewCell"
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
// from the SkeletonView docs:
// SkeletonView is recursive, so if you want show the skeleton in all skeletonable views,
// you only need to call the show method in the main container view. For example, with UIViewControllers.
// So, we only need to set it on self
self.isSkeletonable = true
configure()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - *** UI Elements ***
lazy var titleLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 0
label.textAlignment = .natural
label.textColor = UIColor(named: "AccentColor")
label.font = UIFont.systemFont(ofSize: 13.0, weight: .medium)
return label
}()
lazy var abstractLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 2
label.textAlignment = .natural
label.textColor = UIColor(named: "AccentColor")
label.font = UIFont.systemFont(ofSize: 12.0)
return label
}()
lazy var thumbnailImageView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFill
imageView.frame.size.width = 100
imageView.layer.cornerRadius = imageView.frame.size.width / 2
imageView.clipsToBounds = true
return imageView
}()
lazy var stackView: UIStackView = {
let stack = UIStackView()
stack.distribution = .fillEqually
// we want Vertical Stack View
stack.axis = .vertical
stack.spacing = 10
return stack
}()
// MARK: - *** Methods ***
override func prepareForReuse() {
super.prepareForReuse()
thumbnailImageView.kf.cancelDownloadTask()
thumbnailImageView.image = nil
}
func configure() {
// Adding subviews
contentView.addSubview(thumbnailImageView)
contentView.addSubview(stackView)
stackView.addArrangedSubview(titleLabel)
stackView.addArrangedSubview(abstractLabel)
// Setting up the constraints
thumbnailImageView.snp.makeConstraints {
// leading is 10-points from superview (contentView) leading
$0.leading.equalToSuperview().inset(10)
// 100 x 100
$0.width.height.equalTo(100)
// top and bottom constrained to top and bottom of superview (contenView)
$0.top.bottom.equalToSuperview()
}
stackView.snp.makeConstraints {
// stackView leading is 10=points from image view trailing
$0.leading.equalTo(thumbnailImageView.snp.trailing).offset(10)
// 10-points from superview (contentView) trailing
$0.trailing.equalToSuperview().inset(10)
// stackView centered vertically to image view
$0.centerY.equalTo(thumbnailImageView)
}
}
}

Related

UIKit UIStackView with auto width

I'm making an app with UIkit and I'm making some UITableViewCells that contain an array of user images. I want these images to be displayed in a horizontal stack and overlay each other.
This is how I want it to look:
That's how it looks:
Code:
import UIKit
import SDWebImage
class CountryTableCell: UITableViewCell {
static let reuseIdentifier = "CountryTableCell"
//MARK: - Propeties
var viewModel: CountryViewModel? {
didSet {
viewModel?.delegate = self
setUpSpace(viewModel)
}
}
//MARK: - SubViews
private let container: UIView = {
let view = UIView()
view.withSize(CGSize(width: Dimensions.maxSafeWidth, height: 150))
view.backgroundColor = .secondary_background
view.layer.cornerRadius = 20
return view
}()
private let adminsContainer: UIView = {
let view = UIView()
view.withSize(CGSize(width: (Dimensions.maxSafeWidth - 32), height: Dimensions.image.mediumHeigthWithOverlay))
view.backgroundColor = .secondary_background
return view
}()
private let adminsStack: UIStackView = {
let stack = UIStackView()
stack.axis = .horizontal
stack.alignment = .leading
stack.spacing = -10
stack.sizeToFit()
stack.distribution = .fillEqually
stack.withSize(CGSize(width: Dimensions.image.mediumHeigthWithOverlay, height: Dimensions.image.mediumHeigthWithOverlay))
return stack
}()
private let nameLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: UIFont.preferredFont(forTextStyle: .title2).pointSize, weight: .bold)
label.textColor = .primary_label
label.textAlignment = .left
label.sizeToFit()
label.numberOfLines = 0
return label
}()
var headerView: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleAspectFill
iv.clipsToBounds = true
iv.withSize(CGSize(width: Dimensions.image.headerSizeForCell.width, height: ((Dimensions.image.mediumHeigth/2) + Padding.horizontal)))
iv.backgroundColor = .secondary_background
iv.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner]
iv.layer.cornerRadius = 20
return iv
}()
//MARK: - Init
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
addSubview(container)
container.center(inView: self)
addSubview(headerView)
headerView.anchor(top: container.topAnchor, left: container.leftAnchor, right: container.rightAnchor)
addSubview(nameLabel)
nameLabel.anchor(top: headerView.bottomAnchor, left: container.leftAnchor, right: container.rightAnchor, paddingLeft: Padding.horizontal, paddingRight: Padding.horizontal)
addSubview(adminsContainer)
adminsContainer.anchor(left: container.leftAnchor, bottom: headerView.bottomAnchor, right: container.rightAnchor ,paddingLeft: Padding.horizontal, paddingBottom: -(Dimensions.image.mediumHeigth/2), paddingRight: Padding.horizontal)
bringSubviewToFront(adminsContainer)
adminsContainer.backgroundColor = .clear.withAlphaComponent(0.0)
adminsContainer.addSubview(adminsStack)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//MARK: - Lifecycle
override func willRemoveSubview(_ subview: UIView) {
}
//MARK: Selectors
//MARK: - Helpers
private func setUpSpace(_ viewModel: CountryViewModel?) {
guard let viewModel = viewModel else {return}
if let url = viewModel.country.headerImage?.url {
headerView.sd_setImage(with: URL(string: url))
}
nameLabel.text = viewModel.space.name
}
}
// MARK: Extension
extension SpaceTableCell: CountryViewModelDelegate {
func didFetchAdmin(_ admin: User) {
if let admins = viewModel?.admins.count {
if admins == 1 {
adminsStack.withSize(CGSize(width: Dimensions.image.mediumHeigthWithOverlay, height: Dimensions.image.mediumHeigthWithOverlay))
} else if admins == 2 {
adminsStack.withSize(CGSize(width: ((Dimensions.image.mediumHeigthWithOverlay*2) - 10), height: Dimensions.image.mediumHeigthWithOverlay))
} else if admins == 3 {
adminsStack.withSize(CGSize(width: ((Dimensions.image.mediumHeigthWithOverlay*3) - 20), height: Dimensions.image.mediumHeigthWithOverlay))
} else if admins == 4 {
adminsStack.withSize(CGSize(width: ((Dimensions.image.mediumHeigthWithOverlay*4) - 30), height: Dimensions.image.mediumHeigthWithOverlay))
} else if admins == 5 {
adminsStack.withSize(CGSize(width: ((Dimensions.image.mediumHeigthWithOverlay*5) - 40), height: Dimensions.image.mediumHeigthWithOverlay))
}
}
let image = UserImageView(height: Dimensions.image.mediumHeigth)
image.sd_setImage(with: admin.profileImageURL)
adminsStack.addArrangedSubview(image)
}
}
class UserImageView: UIImageView {
//MARK: - Propeties
let selectedHeigth: CGFloat
init(height: CGFloat) {
self.selectedHeigth = height
super.init(frame: CGRect(x: 0, y: 0, width: selectedHeigth, height: selectedHeigth))
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
contentMode = .scaleAspectFill
clipsToBounds = true
backgroundColor = .secondary_background
layer.cornerRadius = Dimensions.userImageCornerRadious(selectedHeigth)
}
}
Can someone please help me
Thank you :)
I'd suggest a couple changes:
Change #1:
Your UIStackView's distribution set to .fillEqually. By doing this, you're giving it the permission to stretch its arrangedSubviews as and when needed to fit the width that you've provided. This does prove useful in some cases but in your case, it's better to go with .fill instead
Here's an excerpt from Apple's documentation:
For all distributions except the UIStackView.Distribution.fillEqually
distribution, the stack view uses each arranged view’s
intrinsicContentSize property when calculating its size along the
stack’s axis. UIStackView.Distribution.fillEqually resizes all the
arranged views so they’re the same size, filling the stack view along
its axis. If possible, the stack view stretches all the arranged views
to match the view with the longest intrinsic size along the stack’s
axis.
I suggest that you go through this documentation and read more about this
Change #2:
Since you've now changed the distribution to respect intrinsic size, don't set up constraints that conflict with UIStackView's distribution axis. In your case, you have a horizontal stack view and seem to be also defining its width constraints based on the number of admins you have. I'd suggest getting rid of it and leave it to your stack view to take care of it.
One more pointer:
Make sure your UserImageViews have proper unified intrinsicContentSize i.e: the images you set follow the same size. If doubtful, I'd suggest setting up constraints for its width and height respectively. To do so, in your UserImageView's init, include:
self.widthAnchor.constraint(equalToConstant: height).isActive = true
self.heightAnchor.constraint(equalToConstant: height).isActive = true

Multiple UIStackViews inside a custom UITableViewCell in Custom Cell without Storyboard not working

I am currently building out a screen in my app which is basically a long UITableView containing 3 Sections, each with different amounts of unique custom cells. Setting up The tableview works fine, I added some random text in the cells to make sure every cell is correctly called and positioned. I have completely deletet my storyboard from my project because it would lead to problems later because of reasons. So I can't do anything via storyboard.
Next step is to build the custom cells. Some of those are fairly complex for me as a beginner. This is one of my cells:
I want to split the cell in multiple UIStackViews, one for the picture and the name and one for the stats on the right side which in itself will contain two stackviews containing each of the two rows of stats. Each of these could then contain another embedded stackview with the two uiLabels inside, the number and the description. Above all that is a toggle button.
I can't seem to grasp how to define all this. As I said, I defined the Tableview and am calling the right cells in my cellForRowAt as shown here for example:
if indexPath.row == 0 && indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: StatsOverViewCell.identifier, for: indexPath) as! StatsOverViewCell
cell.configure()
return cell
} else if ...
I have created files for each cell, one of them being StatsOverViewCell.
In this file, I have an Identifier with the same name as the class.
I have also added the configure function I am calling from my tableview, the layoutSubviews function which I use to layout the views inside of the cell and I have initialized every label and image I need. I have trimmed the file down to a few examples to save you some time:
class StatsOverViewCell: UITableViewCell {
//Set identifier to be able to call it later on
static let identifier = "StatsOverViewCell"
let myProfileStackView = UIStackView()
let myImageView = UIImageView()
let myName = UILabel()
let myGenderAndAge = UILabel()
let myStatsStackView = UIStackView()
let oneView = UIStackView()
let oneStat = UILabel()
let oneLabel = UILabel()
let twoStackView = UIStackView()
let twoStat = UILabel()
let twoLabel = UILabel()
//Do this for each of the labels I have in the stats
public func configure() {
myImageView.image = UIImage(named: "person-icon")
myName.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
myImageView.contentMode = .scaleAspectFill
myName.text = "Name."
myName.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
myName.textAlignment = .center
//Add the Name label to the stackview
myProfileStackView.addArrangedSubview(myName)
myProfileStackView.addArrangedSubview(myImageView)
myName.centerXAnchor.constraint(equalTo: myProfileStackView.centerXAnchor).isActive = true
oneStat.text = "5.187"
oneStat.font = UIFont(name: "montserrat", size: 18)
oneLabel.text = "Text"
oneLabel.font = UIFont(name: "montserrat", size: 14)
}
//Layout in the cell
override func layoutSubviews() {
super.layoutSubviews()
contentView.backgroundColor = Utilities.hexStringToUIColor(hex: "#3F454B")
contentView.layer.borderWidth = 1
//Stackview
contentView.addSubview(myProfileStackView)
myProfileStackView.axis = .vertical
myProfileStackView.distribution = .equalSpacing
myProfileStackView.spacing = 3.5
myProfileStackView.backgroundColor = .red
myProfileStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 23).isActive = true
myProfileStackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 76).isActive = true
}
As you can see, I am adding all arrangedsubviews to the stackview in the configure method which I call when creating the cell in the tableview. I then set the stackviews constraints inside the layoutsubviews. I am not getting any errors or anything. But the cell shows up completely empty.
I feel like I am forgetting something or I am not understanding how to create cells with uistackviews. Where should I create the stackviews, where should I add the arrangedsubviews to this stackview and what do I do in the LayoutSubviews?
I would be very thankful for any insights.
Thanks for your time!
You're doing a few things wrong...
your UI elements should be created and configured in init, not in configure() or layoutSubviews()
you need complete constraints to give your elements the proper layout
Take a look at the changes I made to your cell class. It should get you on your way:
class StatsOverViewCell: UITableViewCell {
//Set identifier to be able to call it later on
static let identifier = "StatsOverViewCell"
let myProfileStackView = UIStackView()
let myImageView = UIImageView()
let myName = UILabel()
let myGenderAndAge = UILabel()
let myStatsStackView = UIStackView()
let oneView = UIStackView()
let oneStat = UILabel()
let oneLabel = UILabel()
let twoStackView = UIStackView()
let twoStat = UILabel()
let twoLabel = UILabel()
//Do this for each of the labels I have in the stats
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
myImageView.image = UIImage(named: "person-icon")
// frame doesn't matter - stack view arrangedSubvies automatically
// set .translatesAutoresizingMaskIntoConstraints = false
//myName.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
myImageView.contentMode = .scaleAspectFill
myName.text = "Name."
myName.textAlignment = .center
//Add the Name label to the stackview
myProfileStackView.addArrangedSubview(myName)
myProfileStackView.addArrangedSubview(myImageView)
// no need for this
//myName.centerXAnchor.constraint(equalTo: myProfileStackView.centerXAnchor).isActive = true
oneStat.text = "5.187"
oneStat.font = UIFont(name: "montserrat", size: 18)
oneLabel.text = "Text"
oneLabel.font = UIFont(name: "montserrat", size: 14)
contentView.backgroundColor = Utilities.hexStringToUIColor(hex: "#3F454B")
contentView.layer.borderWidth = 1
//Stackview
contentView.addSubview(myProfileStackView)
myProfileStackView.axis = .vertical
// no need for equalSpacing if you're explicitly setting the spacing
//myProfileStackView.distribution = .equalSpacing
myProfileStackView.spacing = 3.5
myProfileStackView.backgroundColor = .red
// stack view needs .translatesAutoresizingMaskIntoConstraints = false
myProfileStackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
// stack view leading 23-pts from contentView leading
myProfileStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 23),
// stack view top 76-pts from contentView top
myProfileStackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 76),
// need something to set the contentView height
// stack view bottom 8-pts from contentView bottom
myProfileStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8),
// set imageView width and height
myImageView.widthAnchor.constraint(equalToConstant: 100.0),
myImageView.heightAnchor.constraint(equalTo: myImageView.widthAnchor),
])
}
public func configure() {
// here you would set the properties of your elements, such as
// label text
// imageView image
// colors
// etc
}
}
Edit
Here's an example cell class that comes close to the layout in the image you posted.
Note that there are very few constraints needed:
NSLayoutConstraint.activate([
// role element 12-pts from top
myRoleElement.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 12.0),
// centered horizontally
myRoleElement.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
// it will probably be using intrinsic height and width, but for demo purposes
myRoleElement.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 0.4),
myRoleElement.heightAnchor.constraint(equalToConstant: 40.0),
// stack view 24-pts on each side
hStack.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 24),
hStack.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -24),
// stack view 20-pts on bottom
hStack.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20),
// stack view top 20-pts from Role element bottom
hStack.topAnchor.constraint(equalTo: myRoleElement.bottomAnchor, constant: 20),
// set imageView width and height
myImageView.widthAnchor.constraint(equalToConstant: 100.0),
myImageView.heightAnchor.constraint(equalTo: myImageView.widthAnchor),
// we want the two "column" stack views to be equal widths
hStack.arrangedSubviews[1].widthAnchor.constraint(equalTo: hStack.arrangedSubviews[2].widthAnchor),
])
Here's the full cell class, including an example "UserStruct" ... you will, of course, want to tweak the fonts / sizes, spacing, etc:
// sample struct for user data
struct UserStruct {
var profilePicName: String = ""
var name: String = ""
var gender: String = ""
var age: Int = 0
var statValues: [String] = []
}
class StatsOverViewCell: UITableViewCell {
//Set identifier to be able to call it later on
static let identifier = "StatsOverViewCell"
// whatever your "role" element is...
let myRoleElement = UILabel()
let myImageView = UIImageView()
let myName = UILabel()
let myGenderAndAge = UILabel()
var statValueLabels: [UILabel] = []
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
// create 6 Value and 6 text labels
// assuming you have 6 "Text" strings, but for now
// we'll use "Text A", "Text B", etc
let tmp: [String] = [
"A", "B", "C",
"D", "E", "F",
]
var statTextLabels: [UILabel] = []
for i in 0..<6 {
var lb = UILabel()
lb.font = UIFont.systemFont(ofSize: 16, weight: .regular)
lb.textAlignment = .center
lb.textColor = .white
lb.text = "0"
statValueLabels.append(lb)
lb = UILabel()
lb.font = UIFont.systemFont(ofSize: 13, weight: .regular)
lb.textAlignment = .center
lb.textColor = .lightGray
lb.text = "Text \(tmp[i])"
statTextLabels.append(lb)
}
// name and Gender/Age label properties
myName.textAlignment = .center
myGenderAndAge.textAlignment = .center
myName.font = UIFont.systemFont(ofSize: 15, weight: .regular)
myGenderAndAge.font = UIFont.systemFont(ofSize: 15, weight: .regular)
myName.textColor = .white
myGenderAndAge.textColor = .white
// placeholder text
myName.text = "Name"
myGenderAndAge.text = "(F, 26)"
myImageView.contentMode = .scaleAspectFill
// create the "Profile" stack view
let myProfileStackView = UIStackView()
myProfileStackView.axis = .vertical
myProfileStackView.spacing = 2
//Add imageView, name and gender/age labels to the profile stackview
myProfileStackView.addArrangedSubview(myImageView)
myProfileStackView.addArrangedSubview(myName)
myProfileStackView.addArrangedSubview(myGenderAndAge)
// create horizontal stack view to hold
// Profile stack + two "column" stack views
let hStack = UIStackView()
// add Profile stack view
hStack.addArrangedSubview(myProfileStackView)
var j: Int = 0
// create two "column" stack views
// each with three "label pair" stack views
for _ in 0..<2 {
let columnStack = UIStackView()
columnStack.axis = .vertical
columnStack.distribution = .equalSpacing
for _ in 0..<3 {
let pairStack = UIStackView()
pairStack.axis = .vertical
pairStack.spacing = 4
pairStack.addArrangedSubview(statValueLabels[j])
pairStack.addArrangedSubview(statTextLabels[j])
columnStack.addArrangedSubview(pairStack)
j += 1
}
hStack.addArrangedSubview(columnStack)
}
// whatever your "Roles" element is...
// here, we'll simulate it with a label
myRoleElement.text = "Role 1 / Role 2"
myRoleElement.textAlignment = .center
myRoleElement.textColor = .white
myRoleElement.backgroundColor = .systemTeal
myRoleElement.layer.cornerRadius = 8
myRoleElement.layer.borderWidth = 1
myRoleElement.layer.borderColor = UIColor.white.cgColor
myRoleElement.layer.masksToBounds = true
// add Role element and horizontal stack view to contentView
contentView.addSubview(myRoleElement)
contentView.addSubview(hStack)
myRoleElement.translatesAutoresizingMaskIntoConstraints = false
hStack.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
// role element 12-pts from top
myRoleElement.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 12.0),
// centered horizontally
myRoleElement.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
// it will probably be using intrinsic height and width, but for demo purposes
myRoleElement.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 0.4),
myRoleElement.heightAnchor.constraint(equalToConstant: 40.0),
// stack view 24-pts on each side
hStack.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 24),
hStack.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -24),
// stack view 20-pts on bottom
hStack.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20),
// stack view top 20-pts from Role element bottom
hStack.topAnchor.constraint(equalTo: myRoleElement.bottomAnchor, constant: 20),
// set imageView width and height
myImageView.widthAnchor.constraint(equalToConstant: 100.0),
myImageView.heightAnchor.constraint(equalTo: myImageView.widthAnchor),
// we want the two "column" stack views to be equal widths
hStack.arrangedSubviews[1].widthAnchor.constraint(equalTo: hStack.arrangedSubviews[2].widthAnchor),
])
//contentView.backgroundColor = Utilities.hexStringToUIColor(hex: "#3F454B")
contentView.backgroundColor = UIColor(red: 0x3f / 255.0, green: 0x45 / 255.0, blue: 0x4b / 255.0, alpha: 1.0)
contentView.layer.borderWidth = 1
contentView.layer.borderColor = UIColor.lightGray.cgColor
// since we're setting the image view to explicit 100x100 size,
// we can make it round here
myImageView.layer.cornerRadius = 50
myImageView.layer.masksToBounds = true
}
public func configure(_ user: UserStruct) {
// here you would set the properties of your elements
// however you're getting your profile image
var img: UIImage!
if !user.profilePicName.isEmpty {
img = UIImage(named: user.profilePicName)
}
if img == nil {
img = UIImage(named: "person-icon")
}
if img != nil {
myImageView.image = img
}
myName.text = user.name
myGenderAndAge.text = "(\(user.gender), \(user.age))"
// probably want error checking to make sure we have 6 values
if user.statValues.count == 6 {
for (lbl, s) in zip(statValueLabels, user.statValues) {
lbl.text = s
}
}
}
}
and a sample table view controller:
class UserStatsTableViewController: UITableViewController {
var myData: [UserStruct] = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(StatsOverViewCell.self, forCellReuseIdentifier: StatsOverViewCell.identifier)
// generate some sample data
// I'm using Female "pro1" and Male "pro2" images
for i in 0..<10 {
var user = UserStruct(profilePicName: i % 2 == 0 ? "pro2" : "pro1",
name: "Name \(i)",
gender: i % 2 == 0 ? "F" : "M",
age: Int.random(in: 21...65))
var vals: [String] = []
for _ in 0..<6 {
let v = Int.random(in: 100..<1000)
vals.append("\(v)")
}
user.statValues = vals
myData.append(user)
}
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myData.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: StatsOverViewCell.identifier, for: indexPath) as! StatsOverViewCell
let user = myData[indexPath.row]
cell.configure(user)
return cell
}
}
This is how it looks at run-time:

Why my 2D UIViews don't appear on screen?

I'm trying to make UIView that contains 12x7 UIViews with margins. I thought that the best way gonna be make 7 Vertical Stacks and then add all them on one big Horizontal stack. And I coded it, but problem is that this Horizontal Stacks doesn't appear on the screen at all (I've tried Xcode feature to see layers there is nothing).
This is my code:
import UIKit
class CalendarView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
private func setupView() {
// array to add in future in columnsStackView
var columnStacks: [UIStackView] = []
for columns in 1...12 {
// array to add in future in columnStackView
var columnViews: [UIView] = []
for cell in 1...7 {
let cellView = UIView(frame: CGRect(x: 0, y: 0, width: 24, height: 24))
cellView.backgroundColor = .orange
columnViews.append(cellView)
}
// create columnStackView and add all 7 views
let columnStackView = UIStackView(arrangedSubviews: columnViews)
columnStackView.axis = .vertical
columnStackView.distribution = .fillEqually
columnStackView.alignment = .fill
columnStackView.spacing = 4
columnStacks.append(columnStackView)
}
// create columnsStackView and add those 12 stacks
let columnsStackView = UIStackView(arrangedSubviews: columnStacks)
columnsStackView.axis = .horizontal
columnsStackView.distribution = .fillEqually
columnsStackView.alignment = .fill
columnsStackView.spacing = 4
columnsStackView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(columnsStackView)
}
}
Can you please help me with that!!!
Couple things...
A UIStackView uses auto-layout when arranging its subviews, so this line:
let cellView = UIView(frame: CGRect(x: 0, y: 0, width: 24, height: 24))
will create a UIView, but the width and height will be ignored.
You need to set those with constraints:
for cell in 1...7 {
let cellView = UIView()
cellView.backgroundColor = .orange
// we want each "cellView" to be 24x24 points
cellView.widthAnchor.constraint(equalToConstant: 24.0).isActive = true
cellView.heightAnchor.constraint(equalTo: cellView.widthAnchor).isActive = true
columnViews.append(cellView)
}
Now, because we've explicitly set the width and height of the "cellViews" we can set the stack view .distribution = .fill (instead of .fillEqually).
Next, we have to constrain the "outer" stack view (columnsStackView) to the view itself:
// constrain the "outer" stack view to self
NSLayoutConstraint.activate([
columnsStackView.topAnchor.constraint(equalTo: topAnchor),
columnsStackView.leadingAnchor.constraint(equalTo: leadingAnchor),
columnsStackView.trailingAnchor.constraint(equalTo: trailingAnchor),
columnsStackView.bottomAnchor.constraint(equalTo: bottomAnchor),
])
otherwise, the view will have 0x0 dimensions.
Here is a modified version of your class:
class CalendarView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
private func setupView() {
// array to add in future in columnsStackView
var columnStacks: [UIStackView] = []
for columns in 1...12 {
// array to add in future in columnStackView
var columnViews: [UIView] = []
for cell in 1...7 {
let cellView = UIView()
cellView.backgroundColor = .orange
// we want each "cellView" to be 24x24 points
cellView.widthAnchor.constraint(equalToConstant: 24.0).isActive = true
cellView.heightAnchor.constraint(equalTo: cellView.widthAnchor).isActive = true
columnViews.append(cellView)
}
// create columnStackView and add all 7 views
let columnStackView = UIStackView(arrangedSubviews: columnViews)
columnStackView.axis = .vertical
columnStackView.distribution = .fill
columnStackView.alignment = .fill
columnStackView.spacing = 4
columnStacks.append(columnStackView)
}
// create columnsStackView and add those 12 stacks
let columnsStackView = UIStackView(arrangedSubviews: columnStacks)
columnsStackView.axis = .horizontal
columnsStackView.distribution = .fill
columnsStackView.alignment = .fill
columnsStackView.spacing = 4
columnsStackView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(columnsStackView)
// constrain the "outer" stack view to self
NSLayoutConstraint.activate([
columnsStackView.topAnchor.constraint(equalTo: topAnchor),
columnsStackView.leadingAnchor.constraint(equalTo: leadingAnchor),
columnsStackView.trailingAnchor.constraint(equalTo: trailingAnchor),
columnsStackView.bottomAnchor.constraint(equalTo: bottomAnchor),
])
}
}
and a simple test controller to show how it can be used:
class CalendarTestViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let cv = CalendarView()
cv.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(cv)
// the CalendarView will size itself, so we only need to
// provide x and y position constraints
NSLayoutConstraint.activate([
cv.centerXAnchor.constraint(equalTo: view.centerXAnchor),
cv.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
// let's give it a background color so we can see its frame
cv.backgroundColor = .systemYellow
}
}
the result:

Configure automatically components in UIStackView (Programatically)

Hi. As you can see, components are overlapped in a UIStackView and I am struggling with that.
Here are some code snippets
class BottomSheetController: UIViewController {
lazy var headerLabel: UILabel = {
let label = UILabel()
label.text = "Workout"
return label
}()
lazy var header: UIView = {
let uiView = UIView()
return uiView
}()
lazy var workoutInput: UITextField = {
let textField = UITextField()
textField.placeholder = "Find or add option"
textField.sizeToFit()
textField.clipsToBounds = true
textField.backgroundColor = UIColor(red: 65 / 255.0, green: 65 / 255.0, blue: 65 / 255.0, alpha: 0.4)
textField.layer.cornerRadius = 5
textField.setImage(image: UIImage(named: "search_black")!)
textField.setClearTextButton()
return textField
}()
lazy var tagsView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.backgroundColor = .red
return scrollView
}()
lazy var containerStackView: UIStackView = {
let stackView = UIStackView(arrangedSubviews: [header, workoutInput, tagsView])
stackView.clipsToBounds = true
stackView.axis = .vertical
stackView.backgroundColor = .systemGray2
return stackView
}()
// ...
private func setConstraint() {
header.addSubview(headerLabel)
header.addSubview(doneButton)
containerStackView.snp.makeConstraints { make in
make.top.left.right.bottom.equalToSuperview().inset(20)
}
headerLabel.snp.makeConstraints { make in
make.top.centerX.equalToSuperview()
}
doneButton.snp.makeConstraints { make in
make.top.right.equalToSuperview()
}
workoutInput.snp.makeConstraints {make in
make.height.equalTo(40)
}
}
}
My problem might be, header view doesn't have constant height value
But I don't want to set the magic number to header view. But rather assign the value corresponding to child views (workoutInput)
Is there fancy ways to adjust the height?
How to prevent components from overlapping?
You need to add enough constraints to satisfy auto-layout requirements.
A UIView has no intrinsic size... so your header ends up with a height of Zero.
Make a couple changes to control the height of that view:
private func setConstraint() {
header.addSubview(headerLabel)
header.addSubview(doneButton)
containerStackView.snp.makeConstraints { make in
// constrain to safe area, not to superView
make.top.left.right.bottom.equalTo(view.safeAreaLayoutGuide).inset(20)
}
headerLabel.snp.makeConstraints { make in
make.top.centerX.equalToSuperview()
// constrain label bottom to superview
make.bottom.equalToSuperview()
}
doneButton.snp.makeConstraints { make in
make.top.right.equalToSuperview()
// constrain button bottom to superview
make.bottom.equalToSuperview()
}
workoutInput.snp.makeConstraints {make in
make.height.equalTo(40)
}
}
As a side note, you'll find it much easier to debug your layout if you give your UI elements contrasting background colors, such as:
header.backgroundColor = .systemTeal
headerLabel.backgroundColor = .yellow
doneButton.backgroundColor = .green
With those changes, this is what you should see:
I believe you should be able to do header.frame.size.height = [height] in the viewDidLoad() function and it should change it like that.

have a vertical stackview take all the spacing inside another stackview

I am trying to achieve the following layout in a table cell (it has a dynamic height):
The result I am getting is that 2nd stackview doesn't stretch to add more height to fill the multiline label.
lazy var stackView: StackView = {
let subviews: [UIView] = []
let view = StackView(arrangedSubviews: subviews)
view.axis = .horizontal
view.alignment = .center
self.containerView.addSubview(view)
view.snp.makeConstraints({ (make) in
make.edges.equalToSuperview().inset(inset)
})
return view
}()
lazy var detailsStackView: StackView = {
let subviews: [UIView] = []
let view = StackView(arrangedSubviews: subviews)
view.axis = .vertical
stackView.alignment = .fill
return view
}()
lazy var nameLabel: Label = {
let view = Label(style: .styleBoldSize24)
return view
}()
let detailsLabel: Label = {
let label = Label(style: .styleRegular25)
label.textAlignment = .left
label.numberOfLines = 10
label.text = "Where does it come from Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of anything embarrassing hidden in the mid words etc."
return label
}()
lazy var avatarImageView: ImageView = {
let view = ImageView(frame: CGRect())
view.snp.makeConstraints({ (make) in
make.width.height.equalTo(100)
})
return view
}()
override func setup() {
super.setup()
stackView.axis = .horizontal
stackView.alignment = .top
stackView.addArrangedSubview(avatarImageView)
detailsStackView.spacing = self.inset
detailsStackView.addArrangedSubview(nameLabel)
detailsStackView.addArrangedSubview(detailsLabel)
stackView.addArrangedSubview(avatarImageView)
stackView.addArrangedSubview(detailsStackView)
stackView.snp.remakeConstraints({ (make) in
let inset = self.inset
make.edges.equalToSuperview().inset(UIEdgeInsets(top: inset/2, left: inset, bottom: inset/2, right: inset))
})}

Resources