Displaying property with Horizontal and Vertical Stack View in Swift - swift

I am new to swift . I am following the programmatic approach to create the view . I created two stack view . One is horizontal and other one is vertical. Into horizontal stack view I want to display the label property and Vertical stack view I want to display the Image. I want to display the image on left side and label properties on right side.
Here is the code I used ..
import UIKit
class PeopleCell: UITableViewCell {
static let identifier = "PeopleCell"
private lazy var mainStackView: UIStackView = {
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.distribution = .fill
stackView.alignment = .leading
return stackView
}()
private lazy var stackView: UIStackView = {
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .horizontal
stackView.distribution = .fill
stackView.alignment = .center
return stackView
}()
private lazy var lastnameTitleLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.textAlignment = .center
return label
}()
private lazy var firstnameTitleLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.textAlignment = .center
return label
}()
private lazy var peopleImageView: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFit
// imageView.backgroundColor = .blue
return imageView
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setUpUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configureCell(firstName: String, lastName: String) {
firstnameTitleLabel.text = "Firstname :\(firstName)"
lastnameTitleLabel.text = "Lastname : \(lastName)"
}
func configureImageCell(row: Int, viewModel: ViewModel) {
peopleImageView.image = nil
viewModel
.downloadImage(row: row) { [weak self] data in
let image = UIImage(data: data)
self?.peopleImageView.image = image
}
}
private func setUpUI() {
stackView.addArrangedSubview(lastnameTitleLabel)
stackView.addArrangedSubview(firstnameTitleLabel)
mainStackView.addArrangedSubview(peopleImageView)
mainStackView.addArrangedSubview(stackView)
contentView.addSubview(mainStackView)
// constraints
let safeArea = contentView.safeAreaLayoutGuide
mainStackView.topAnchor.constraint(equalTo: safeArea.topAnchor).isActive = true
mainStackView.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor).isActive = true
mainStackView.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor, constant: 10).isActive = true
mainStackView.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor, constant: -10).isActive = true
peopleImageView.heightAnchor.constraint(equalToConstant: 140).isActive = true
peopleImageView.widthAnchor.constraint(equalToConstant: 140).isActive = true
stackView.leadingAnchor.constraint(equalTo: mainStackView.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: mainStackView.trailingAnchor).isActive = true
}
}
Here is the screenshot ..
Here is the expected result.

The main problem is that you have your .axis properties reversed.
You want your mainStackView.axis to be .horizontal and your stackView.axis to be .vertical.
Also, these two lines are not needed (and cause problems):
// don't do this
//stackView.leadingAnchor.constraint(equalTo: mainStackView.leadingAnchor).isActive = true
//stackView.trailingAnchor.constraint(equalTo: mainStackView.trailingAnchor).isActive = true
As a side note, instead of:
let safeArea = contentView.safeAreaLayoutGuide
you may want to use:
let safeArea = contentView.layoutMarginsGuide
which gives you the default cell margins.
Edit
// this is the "main" stack view with
// the image on the left
// the labels on the right
// so it needs to be HORIZONTAL
private lazy var mainStackView: UIStackView = {
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
//stackView.axis = .vertical
stackView.axis = .horizontal
stackView.distribution = .fill
return stackView
}()
// this is the "labels" stack view with
// two (or more) labels from top down
// so it needs to be VERTICAL
private lazy var stackView: UIStackView = {
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
//stackView.axis = .horizontal
stackView.axis = .vertical
stackView.distribution = .fill
return stackView
}()

Related

stack programmatically, giving space for bottom

i am trying to place the image below the text i add
class SolicitudViewController: BaseViewController {
lazy var imagePrincipal : UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "diseƱo")
imageView.contentMode = .scaleAspectFit
imageView.translatesAutoresizingMaskIntoConstraints = false
//imageView.heightAnchor.constraint(equalToConstant: 50).isActive = true
//imageView.bottomAnchor.constraint(equalToConstant: 100).isActive = true
return imageView
}()
lazy var stackView : UIStackView = {
let stack = UIStackView()
stack.axis = .vertical
stack.distribution = .fill
stack.translatesAutoresizingMaskIntoConstraints = false
stack.addArrangedSubview(imagePrincipal)
//stack.addArrangedSubview(lblsubTitulo)
//stack.addArrangedSubview(lineView)
stack.addArrangedSubview(imageEvaluando2)
stack.addArrangedSubview(imageEvaluando3)
return stack
}()
lazy var scrollView : UIScrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(stackView)
return scrollView
}()
override func viewDidLoad() {
super.viewDidLoad()
setSubtitle(subtitle: "test View")
}
I have tried to use this: stack.setCustomSpacing(30, after: imagePrincipal) but it positions the image on top, I want the image to be below the text

Cell not displaying elements when being reused on a stack that adds a LinkPresentation view

I had this custom view who worked like a charm before i introduce a LinkView for a Metadata
After i introduce a LinkView, since it was inside a stackView i had to remove linkView from superview when preparing for reusable (not sure why tried to redraw layout, but seems this not work with LinkView) the problems shows up when scrolling down elements, seems the data get lost at certain point, curious thing is that it only happens with the reusable element that contains the linkView item, is there any reason for this ? How can i fix it ?
Here is the code i use for the cell
final class TimeLineTableViewCell: UITableViewCell {
var cornerRadius: CGFloat = 6
var shadowOffsetWidth = 0
var shadowOffsetHeight = 3
var shadowColor: UIColor = .gray
var shadowOpacity: Float = 0.3
lazy var containerView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .white
view.addSubview(stackViewContainer)
let shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius)
view.layer.cornerRadius = cornerRadius
view.clipsToBounds = true
view.layer.masksToBounds = false
view.layer.shadowColor = shadowColor.cgColor
view.layer.shadowOffset = CGSize(width: shadowOffsetWidth, height: shadowOffsetHeight);
view.layer.shadowOpacity = shadowOpacity
view.layer.shadowPath = shadowPath.cgPath
return view
}()
lazy var stackViewContainer: UIStackView = {
let stack = UIStackView()
stack.axis = .horizontal
stack.alignment = .center
stack.translatesAutoresizingMaskIntoConstraints = false
stack.distribution = .fill
stack.spacing = 10.0
stack.addArrangedSubview(profileImage)
stack.addArrangedSubview(stackViewDataHolder)
return stack
}()
lazy var profileImage: UIImageView = {
let image = UIImage()
let imageView = UIImageView(image: image)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFit
return imageView
}()
lazy var userName: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
return label
}()
lazy var tweetInfo: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
return label
}()
lazy var tweetText: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
return label
}()
lazy var linkView: LPLinkView = {
let viewer = LPLinkView(frame: CGRect(origin: .zero, size: .init(width: 200, height: 20)))
viewer.translatesAutoresizingMaskIntoConstraints = false
return viewer
}()
lazy var stackViewDataHolder: UIStackView = {
let stack = UIStackView()
stack.axis = .vertical
stack.translatesAutoresizingMaskIntoConstraints = false
stack.distribution = .fillProportionally
stack.addArrangedSubview(userName)
stack.addArrangedSubview(tweetInfo)
stack.addArrangedSubview(tweetText)
return stack
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
override func prepareForReuse() {
linkView.removeFromSuperview()
}
func configure(viewModel: ProfileTweetViewModel) {
tweetInfo.configure(model: viewModel.tweetInfo)
userName.configure(model: viewModel.name)
tweetText.configure(model: viewModel.tweet)
if let metadata = viewModel.linkData {
linkView = LPLinkView(metadata: metadata)
stackViewDataHolder.addArrangedSubview(linkView)
//Tried almost all layoyt options but seems a previous view can't be updated since frame is wrong
}
if let url = viewModel.profilePic {
profileImage.downloadImage(from: url)
}
}
}
private extension TimeLineTableViewCell {
struct Metrics {
static let lateralPadding: CGFloat = 8
}
func constraints() {
NSLayoutConstraint.activate([
stackViewContainer.topAnchor.constraint(equalTo: containerView.topAnchor, constant: Metrics.lateralPadding),
stackViewContainer.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -Metrics.lateralPadding),
stackViewContainer.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: Metrics.lateralPadding),
stackViewContainer.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -Metrics.lateralPadding),
profileImage.heightAnchor.constraint(equalTo: profileImage.widthAnchor, multiplier: 1.0),
profileImage.widthAnchor.constraint(equalToConstant: 50.0),
])
}
func commonInit() {
addSubview(containerView)
backgroundColor = .clear
NSLayoutConstraint.activate([
containerView.topAnchor.constraint(equalTo: topAnchor),
containerView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 4),
containerView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -4),
containerView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -4),
])
constraints()
}
}
Thank you for your time.
The issue was related to .fillProportionally in stackView
since the linkView sometimes renders with 0 height, i just had to use .fill property in stackView in order to show it fully

Can't figure out why my stackviews are not arranging vertically

I have done this many times but this time for some reason won't work the way it usually does. Am I doing something wrong here? I am just trying to get two views into my UIstackView and distribute them vertically. It seems to keep overlapping and going all over the place. At one point it was only even showing one view.
My viewdidload():
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .red
view.addSubview(headerView)
view.addSubview(contentView)
contentView.addSubview(contentStack)
headerView.heightAnchor.constraint(equalTo: self.view.heightAnchor, multiplier: 0.30).isActive = true
headerView.widthAnchor.constraint(equalTo: self.view.widthAnchor).isActive = true
headerView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
headerView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
contentView.heightAnchor.constraint(equalTo: self.view.heightAnchor, multiplier: 0.70).isActive = true
contentView.widthAnchor.constraint(equalTo: self.view.widthAnchor,multiplier: 0.90).isActive = true
contentView.topAnchor.constraint(equalTo: headerView.bottomAnchor,constant: 20).isActive = true
contentView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
}
My Views and Labels:
fileprivate lazy var headerView : UIView = {
var view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .black
return view
}()
fileprivate lazy var contentView : UIView = {
var view = UIView()
view.backgroundColor = .lightGray
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
fileprivate lazy var contentStack : UIStackView = {
var stack = UIStackView(arrangedSubviews: [EarningsView,ListingsView,])
stack.translatesAutoresizingMaskIntoConstraints = true
stack.distribution = .fillEqually
stack.alignment = .fill
stack.axis = .vertical
// stack.spacing = 5
return stack
}()
fileprivate lazy var EarningsView : UIView = {
let EarningsView = UIView()
EarningsView.translatesAutoresizingMaskIntoConstraints = false
EarningsView.backgroundColor = .blue
EarningsView.addSubview(EarningsLabel)
EarningsView.addViewBorder(borderColor: UIColor.black.cgColor, borderWith: 0.5, borderCornerRadius: 0.0)
return EarningsView
}()
fileprivate lazy var EarningsLabel : UILabel = {
let EarningsLabel = UILabel()
EarningsLabel.translatesAutoresizingMaskIntoConstraints = false
let earningsText = NSAttributedString(string: "My Earnings", attributes: self.stringAttrib)
EarningsLabel.attributedText = earningsText
EarningsLabel.textColor = .black
EarningsLabel.backgroundColor = .white
EarningsLabel.textAlignment = .center
return EarningsLabel
}()
fileprivate lazy var ListingsView : UIView = {
let ListingsView = UIView()
ListingsView.translatesAutoresizingMaskIntoConstraints = false
ListingsView.addSubview(ListingLabel)
ListingsView.addViewBorder(borderColor: UIColor.black.cgColor, borderWith: 0.5, borderCornerRadius: 0.0)
ListingsView.backgroundColor = .red
return ListingsView
}()
fileprivate lazy var ListingLabel : UILabel = {
let ListingLabel = UILabel()
ListingLabel.translatesAutoresizingMaskIntoConstraints = false
let listingText = NSAttributedString(string: "My Listing", attributes: self.stringAttrib)
ListingLabel.attributedText = listingText
return ListingLabel
}()
This is driving me crazy because I have done it so many times before and now I have spent atleast 6 hours on this little part trying to figure it out. Of course I could just create a whole new viewcontroller but I just want to figure this out.
EarningsView, EarningsLabel, ListingsView, ListingLabel have
translatesAutoresizingMaskIntoConstraints = false
But they have no constraints added to replace the Autoresizing constraints, so they just act goofy and go up to the top left. I've seen stuff like this before. On my stuff. Either autosize or put in constraints.

UIScrollView not scrolling w/ programmatic autolayout constraints

I am trying to understand how to use a UIScrollView programmatically.
When I give my content a size that does not fit on screen though, it does not scroll.
final class ProfileView: UIView {
private var isIpad: Bool {
return UIDevice.current.userInterfaceIdiom == .pad
}
private lazy var headerImageView: UIImageView = {
let iv = UIImageView(frame: .zero)
iv.translatesAutoresizingMaskIntoConstraints = false
iv.heightAnchor.constraint(equalToConstant: 600).isActive = true
iv.backgroundColor = .purple
return iv
}()
private lazy var profileImageView: UIImageView = {
let iv = UIImageView(frame: .zero)
iv.translatesAutoresizingMaskIntoConstraints = false
[iv.heightAnchor, iv.widthAnchor].forEach { $0.constraint(equalToConstant: 170).isActive = true }
iv.layer.cornerRadius = 170 / 2
iv.layer.borderColor = .white
iv.layer.borderWidth = 3
iv.layer.masksToBounds = true
iv.contentMode = .scaleAspectFill
iv.clipsToBounds = true
iv.backgroundColor = .red
return iv
}()
private(set) lazy var nameLabel: UILabel = {
let label = UILabel(frame: .zero)
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "Foo\nBar"
label.numberOfLines = 2
return label
}()
private lazy var contentScrollView: UIScrollView = {
let sv = UIScrollView(frame: .zero)
sv.translatesAutoresizingMaskIntoConstraints = false
return sv
}()
override init(frame: CGRect) {
super.init(frame: frame)
configureLayout()
}
required init?(coder: NSCoder) {
return nil
}
private func configureLayout() {
addSubview(contentScrollView)
[headerImageView, profileImageView, nameLabel].forEach { contentScrollView.addSubview($0) }
let compactConstraints = [
contentScrollView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor),
contentScrollView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor),
contentScrollView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor),
contentScrollView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor),
headerImageView.topAnchor.constraint(equalTo: topAnchor),
headerImageView.leadingAnchor.constraint(equalTo: leadingAnchor),
headerImageView.trailingAnchor.constraint(equalTo: trailingAnchor),
profileImageView.centerYAnchor.constraint(equalTo: headerImageView.bottomAnchor),
profileImageView.centerXAnchor.constraint(equalTo: centerXAnchor),
nameLabel.topAnchor.constraint(equalTo: profileImageView.bottomAnchor, constant: 16),
nameLabel.centerXAnchor.constraint(equalTo: profileImageView.centerXAnchor),
]
NSLayoutConstraint.activate(compactConstraints)
}
}
This gives me the following -
The header image pushes the name label and avatar off screen and scrolling does not work.
I've read a bunch about giving the scroll view a huge offset so it scrolls, but that surely cannot be correct.
You need to create the constraints of all the subviews to the scrollView , add width constraint to the header img = profileview width and finally make bottom of label = bottom of the scrollview to make it infer it's height
let compactConstraints = [
contentScrollView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor),
contentScrollView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor),
contentScrollView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor),
contentScrollView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor),
headerImageView.topAnchor.constraint(equalTo:contentScrollView.topAnchor),
headerImageView.leadingAnchor.constraint(equalTo:contentScrollView.leadingAnchor),
headerImageView.trailingAnchor.constraint(equalTo:contentScrollView.trailingAnchor),
headerImageView.widthAnchor.constraint(equalTo: self.widthAnchor) // add this
profileImageView.centerYAnchor.constraint(equalTo: headerImageView.bottomAnchor),
profileImageView.centerXAnchor.constraint(equalTo: centerXAnchor),
nameLabel.topAnchor.constraint(equalTo: profileImageView.bottomAnchor, constant: 16),
nameLabel.centerXAnchor.constraint(equalTo: profileImageView.centerXAnchor),
nameLabel.bottomAnchor.constraint(equalTo: contentScrollView.bottomAnchor) // and this
]

Programmatically create stack view

I am trying to create a date component by using a stack view programmatically. If user enters wrong date an error label will display message accordingly.Its working fine if I create it through storyboard, but when I use my programmatically created component which has added all elements in a stack view. I am not able to see error label.
I verified the autolayout I've added in storyboard and in my component class, both look similar. Here is code for my component.
class CustomDateView: UIView {
// MARK: - Variable
private let dayTextField: UITextField = {
let inputField = UITextField()
inputField.borderStyle = .none
inputField.layer.cornerRadius = 8.0
inputField.layer.borderColor = UIColor.darkGray.cgColor
inputField.backgroundColor = UIColor.blue
inputField.textAlignment = .center
inputField.tag = 0
inputField.translatesAutoresizingMaskIntoConstraints = false
return inputField
}()
private let monthTextField: UITextField = {
let inputField = UITextField()
inputField.borderStyle = .none
inputField.layer.cornerRadius = 8.0
inputField.layer.borderColor = UIColor.darkGray.cgColor
inputField.backgroundColor = UIColor.blue
inputField.textAlignment = .center
inputField.tag = 1
inputField.translatesAutoresizingMaskIntoConstraints = false
return inputField
}()
private let yearTextField: UITextField = {
let inputField = UITextField()
inputField.borderStyle = .none
inputField.layer.cornerRadius = 8.0
inputField.layer.borderColor = UIColor.darkGray.cgColor
inputField.backgroundColor = UIColor.blue
inputField.textAlignment = .center
inputField.tag = 2
inputField.translatesAutoresizingMaskIntoConstraints = false
return inputField
}()
private let errorLabel: UILabel = {
let errorLabel = UILabel()
errorLabel.textColor = .red
errorLabel.textAlignment = .center
errorLabel.numberOfLines = 0
errorLabel.translatesAutoresizingMaskIntoConstraints = false
return errorLabel
}()
private let monthSeparator: UILabel = {
let label = UILabel()
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
private let yearSeparator: UILabel = {
let label = UILabel()
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
private let horizontalStackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .horizontal
stackView.alignment = .fill
stackView.distribution = .fillProportionally
stackView.spacing = 16.0
stackView.translatesAutoresizingMaskIntoConstraints = false
return stackView
}()
private let verticalStackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.alignment = .fill
stackView.distribution = .fillProportionally
stackView.spacing = 8.0
stackView.translatesAutoresizingMaskIntoConstraints = false
return stackView
}()
// MARK: - Initialisers
override init(frame: CGRect) {
super.init(frame: frame)
self.setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setup()
}
// MARK: - Private Functions
private func setup() {
self.horizontalStackView.addArrangedSubview(self.dayTextField)
self.horizontalStackView.addArrangedSubview(self.monthSeparator)
self.horizontalStackView.addArrangedSubview(self.monthTextField)
self.horizontalStackView.addArrangedSubview(self.yearSeparator)
self.horizontalStackView.addArrangedSubview(self.yearTextField)
self.verticalStackView.addArrangedSubview(self.horizontalStackView)
self.verticalStackView.addArrangedSubview(self.errorLabel)
self.addSubview(self.verticalStackView)
self.translatesAutoresizingMaskIntoConstraints = false
let selfType = type(of: self)
NSLayoutConstraint.activate([
self.dayTextField.heightAnchor.constraint(equalToConstant: 48.0),
self.dayTextField.widthAnchor.constraint(equalToConstant: 62.0),
self.monthTextField.widthAnchor.constraint(equalToConstant: 62.0),
self.monthSeparator.widthAnchor.constraint(equalToConstant: 14.0),
self.yearSeparator.widthAnchor.constraint(equalToConstant: 14.0)
])
NSLayoutConstraint.activate([
self.verticalStackView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 0.0),
self.verticalStackView.topAnchor.constraint(equalTo: self.topAnchor, constant: 0.0),
self.bottomAnchor.constraint(equalTo: self.verticalStackView.bottomAnchor),
self.trailingAnchor.constraint(equalTo: self.verticalStackView.trailingAnchor)
])
}
}
It render view correctly but after setting error label stack view won't grow to display the text.I have given fixed width to my separators and textfield's cause I want them to be of that exact width and height.
Is there any mistakes I'm making while applying autolayout in here.