Swift Autolayout - Height Anchor not being applied - ios

I am working on a "Profile View Controller" which is inside a TableViewCell and table cell dimension is set as UITableView.automaticDimension:
The problem is that I would like to set the background image view (Blue) with a variable height based on cell width and a 16:9 ratio. For some reason I am not being able to change the view height even if I set the constant.
Here the code:
let topView : UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.layer.cornerRadius = 20
view.backgroundColor = .yoofitDarkBlue
view.layer.masksToBounds = true
view.layer.shadowColor = UIColor.lightGray.cgColor
view.layer.borderWidth = 0.5
view.layer.borderColor = UIColor.yoofitDarkBlue.cgColor
view.layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
view.layer.shadowRadius = 12.0
view.layer.shadowOpacity = 0.7
return view
}()
let userImage : UIImageView = {
let i = UIImageView()
i.translatesAutoresizingMaskIntoConstraints = false
i.image = UIImage(named: "user2")
i.layer.masksToBounds = true
i.layer.cornerRadius = 75
i.layer.borderWidth = 1
i.layer.borderColor = UIColor.white.cgColor
return i
}()
var userNameLabel:UILabel = {
let l = UILabel()
l.translatesAutoresizingMaskIntoConstraints = false
l.font = UIFont(name: "AvenirNext-DemiBold", size: 20)
l.textAlignment = .center
l.textColor = .yoofitBlack
return l
}()
func setupViews() {
self.backgroundColor = .clear
self.selectionStyle = .none
addSubview(topView)
let width = self.frame.width
print(width) // print 320
topView.topAnchor.constraint(equalTo: topAnchor, constant: 10).isActive = true
topView.leftAnchor.constraint(equalTo: leftAnchor, constant: 8).isActive = true
topView.rightAnchor.constraint(equalTo: rightAnchor, constant: -8).isActive = true
topView.heightAnchor.constraint(equalToConstant: width/16*9).isActive = true // this doesn't work even if I set another number like 600
topView.layoutIfNeeded()
addSubview(userImage)
userImage.centerYAnchor.constraint(equalTo: topView.bottomAnchor).isActive = true
userImage.centerXAnchor.constraint(equalTo: topView.centerXAnchor).isActive = true
userImage.widthAnchor.constraint(equalToConstant: 150).isActive = true
userImage.heightAnchor.constraint(equalToConstant: 150).isActive = true
addSubview(userNameLabel)
userNameLabel.topAnchor.constraint(equalTo: userImage.bottomAnchor, constant: 10).isActive = true
userNameLabel.centerXAnchor.constraint(equalTo: userImage.centerXAnchor).isActive = true
userNameLabel.widthAnchor.constraint(equalToConstant: 200).isActive = true
userNameLabel.heightAnchor.constraint(equalToConstant: 20).isActive = true
userNameLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -20).isActive = true
}

PLEASE NOTE THERE ARE 2 answers to this question. The latter is definitely the correct one but the first maybe may help others.
So this is how I fixed it but I hope to get better answers. This answer helped me to go to the right path (iOS: How to Align The Center of a View With The Bottom of Another View With AutoLayout)
I have added a containing view which is twice as the required size of my top view (the blue one).
I centered the picture on the containing view.
self.conteiningView.backgroundColor = .red // I made this red so you can visually see how the views are related to each other
let width = self.frame.width
self.addSubview(conteiningView)
conteiningView.topAnchor.constraint(equalTo: topAnchor, constant: 8).isActive = true
conteiningView.leftAnchor.constraint(equalTo: leftAnchor, constant: 8).isActive = true
conteiningView.rightAnchor.constraint(equalTo: rightAnchor, constant: -8).isActive = true
conteiningView.heightAnchor.constraint(equalToConstant: (width/16*9)*2).isActive = true // twice of the blue view
conteiningView.layoutIfNeeded()
self.conteiningView.addSubview(topView)
topView.topAnchor.constraint(equalTo: conteiningView.topAnchor).isActive = true
topView.leftAnchor.constraint(equalTo: conteiningView.leftAnchor).isActive = true
topView.rightAnchor.constraint(equalTo: conteiningView.rightAnchor).isActive = true
topView.heightAnchor.constraint(equalToConstant: width/16*9).isActive = true // 16:9 ration
conteiningView.addSubview(userImage)
userImage.centerYAnchor.constraint(equalTo: conteiningView.centerYAnchor).isActive = true
userImage.centerXAnchor.constraint(equalTo: conteiningView.centerXAnchor).isActive = true
userImage.widthAnchor.constraint(equalToConstant: width/2).isActive = true
userImage.heightAnchor.constraint(equalToConstant: width/2).isActive = true
userImage.layer.cornerRadius = width/4 // to mantain nice proportions
conteiningView.addSubview(userNameLabel)
userNameLabel.topAnchor.constraint(equalTo: userImage.bottomAnchor, constant: 10).isActive = true
userNameLabel.centerXAnchor.constraint(equalTo: userImage.centerXAnchor).isActive = true
userNameLabel.widthAnchor.constraint(equalToConstant: 200).isActive = true
userNameLabel.heightAnchor.constraint(equalToConstant: 20).isActive = true
With red background:
With clear background:
UPDATE: The answer above works but I have actually found the problem:
Even if I had set:
self.tableView.rowHeight = UITableView.automaticDimension
self.tableView.estimatedRowHeight = 300
For some (silly me) I had also implemented:
tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
This, of course, does not be implemented for the automatic dimension to work.
removing that allowed to use the following code to achieve the same result but without setting a predefined height:
self.addSubview(conteiningView)
conteiningView.topAnchor.constraint(equalTo: topAnchor, constant: 8).isActive = true
conteiningView.leftAnchor.constraint(equalTo: leftAnchor, constant: 8).isActive = true
conteiningView.rightAnchor.constraint(equalTo: rightAnchor, constant: -8).isActive = true
conteiningView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
conteiningView.layoutIfNeeded()
self.conteiningView.addSubview(topView)
topView.topAnchor.constraint(equalTo: conteiningView.topAnchor).isActive = true
topView.leftAnchor.constraint(equalTo: conteiningView.leftAnchor).isActive = true
topView.rightAnchor.constraint(equalTo: conteiningView.rightAnchor).isActive = true
topView.heightAnchor.constraint(equalToConstant: width/16*9).isActive = true
conteiningView.addSubview(userImage)
userImage.centerYAnchor.constraint(equalTo: topView.bottomAnchor).isActive = true // the vertical center is now set nicely at bottom edge of my top view.
userImage.centerXAnchor.constraint(equalTo: topView.centerXAnchor).isActive = true
userImage.widthAnchor.constraint(equalToConstant: width/2).isActive = true
userImage.heightAnchor.constraint(equalToConstant: width/2).isActive = true
userImage.layer.cornerRadius = width/4
conteiningView.addSubview(userNameLabel)
userNameLabel.topAnchor.constraint(equalTo: userImage.bottomAnchor, constant: 2).isActive = true
userNameLabel.centerXAnchor.constraint(equalTo: userImage.centerXAnchor).isActive = true
userNameLabel.widthAnchor.constraint(equalToConstant: 200).isActive = true
userNameLabel.heightAnchor.constraint(equalToConstant: 35).isActive = true

Related

Swift - constrain UITextView

I have a UITextView and I would like to constrain it the same way as I would a UILabel. But if I use the same constrains as I would with a UILabel I am getting a different result. I also do not really quite understand how UITextView.frame works because it doesn't really matter what I set height/width, the result stays the same.
In the picture below "LinkTest" is my UITextView. As you can see it is not lined up with the UILabels below even though I constrain it the same way.
UITextView:
let linkLabel: UITextView = {
let v = UITextView()
v.backgroundColor = .clear
v.text = "Link"
v.textColor = .lightGray
v.font = UIFont(name: "AvenirNext-Regular", size: 18)
v.textAlignment = .right
v.isSelectable = false
v.isScrollEnabled = false
v.frame = CGRect(x: 0, y: 0, width: 20, height: 20)
// v.attributedText = NSAttributedString(string: "", attributes: [.underlineStyle: NSUnderlineStyle.single.rawValue])
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
Constrains:
// constrain linkLabel
linkLabel.topAnchor.constraint(equalTo: linkImage.topAnchor).isActive = true
linkLabel.leadingAnchor.constraint(equalTo: linkImage.leadingAnchor, constant: 30).isActive = true
linkLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20).isActive = true
// constrain priceLabel
priceLabel.topAnchor.constraint(equalTo: linkLabel.topAnchor, constant: 35).isActive = true
priceLabel.leadingAnchor.constraint(equalTo: linkImage.leadingAnchor, constant: 30).isActive = true
priceLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20).isActive = true
// constrain noteLabel
noteLabel.topAnchor.constraint(equalTo: priceLabel.topAnchor, constant: 35).isActive = true
noteLabel.leadingAnchor.constraint(equalTo: linkImage.leadingAnchor, constant: 30).isActive = true
noteLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20).isActive = true
I appreciate any help on this :)
Solution:
The problem was the UITextViews padding. Removing all padding solved the problem:
let padding = v.textContainer.lineFragmentPadding
v.textContainerInset = UIEdgeInsets(top: 0, left: -padding, bottom: 0, right: -padding)
When you set
v.translatesAutoresizingMaskIntoConstraints = false
then frame setting is ignored , btw you need a height
linkLabel.heightAnchor.constraint(equalToConstant: 30).isActive = true
You can set constraints using frame or using autolayout both can not work at same time. when you set v.translatesAutoresizingMaskIntoConstraints = false then frame setting doesn't affect and also you have to add height constraint
// constrain linkLabel
linkLabel.topAnchor.constraint(equalTo: linkImage.topAnchor).isActive = true
linkLabel.leadingAnchor.constraint(equalTo: linkImage.leadingAnchor, constant: 30).isActive = true
linkLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20).isActive = true
linkLabel.heightAnchor.constraint(equalToConstant: 20).isActive = true

Swift - Duplicate UIView

I'm currently trying to achieve a Shimmer effect in Swift. To do that I create a gray UIView, and another on top of it with an effect.
The problem is that I'm writing the same code twice...
let profileShimmerView = UIView()
profileShimmerView.backgroundColor = whiteClear
profileShimmerView.layer.cornerRadius = 20
profileShimmerView.clipsToBounds = true
let profileView = UIView()
profileView.backgroundColor = grayClear
profileView.layer.cornerRadius = 20
profileView.clipsToBounds = true
self.addSubview(profileView)
self.addSubview(profileShimmerView)
profileShimmerView.translatesAutoresizingMaskIntoConstraints = false
profileShimmerView.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 45).isActive = true
profileShimmerView.topAnchor.constraint(equalTo: self.topAnchor, constant: 15).isActive = true
profileShimmerView.widthAnchor.constraint(equalToConstant: 40).isActive = true
profileShimmerView.heightAnchor.constraint(equalToConstant: 40).isActive = true
profileView.translatesAutoresizingMaskIntoConstraints = false
profileView.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 45).isActive = true
profileView.topAnchor.constraint(equalTo: self.topAnchor, constant: 15).isActive = true
profileView.widthAnchor.constraint(equalToConstant: 40).isActive = true
profileView.heightAnchor.constraint(equalToConstant: 40).isActive = true
Is there a more simple way to achieve that?
You can create a function
func shared(color : UIColor)->UIView {
let v = UIView()
v.backgroundColor = color
v.layer.cornerRadius = 20
v.clipsToBounds = true
self.addSubview(v)
v.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
v.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 45),
v.topAnchor.constraint(equalTo: self.topAnchor, constant: 15),
v.widthAnchor.constraint(equalToConstant: 40),
v.heightAnchor.constraint(equalToConstant: 40)
])
return v
}

Positioning constraints not having any effect

I am trying to code the label in the viewDidLoad function. The label is showing up but the code I am using now is not affecting the positioning. I am trying to code everything in the viewDidLoad function. You can see the screenshot below.
override func viewDidLoad() {
super.viewDidLoad()
let backbutton = UILabel()
backbutton.backgroundColor = UIColor.orange
backbutton.translatesAutoresizingMaskIntoConstraints = false
backbutton.widthAnchor.constraint(equalToConstant: 300).isActive = true
backbutton.heightAnchor.constraint(equalToConstant: 300).isActive = true
backbutton.centerXAnchor.constraint(equalTo: backbutton.centerXAnchor, constant: 100).isActive = true
backbutton.centerYAnchor.constraint(equalTo: backbutton.centerYAnchor, constant: 300).isActive = true
view.addSubview(backbutton)
}
This is how you can align the label into the center of the view,
let backbutton = UILabel()
view.addSubview(backbutton)
backbutton.backgroundColor = UIColor.orange
backbutton.translatesAutoresizingMaskIntoConstraints = false
backbutton.widthAnchor.constraint(equalToConstant: 300).isActive = true
backbutton.heightAnchor.constraint(equalToConstant: 300).isActive = true
backbutton.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0).isActive = true
backbutton.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 0).isActive = true
When you are setting constraints programmatically, make sure you add the view into the super/parent view before applying constraints. Secondly in the below lines, you are telling the backButton label to align its center to itself (i.e, backbutton.centerXAnchor.constraint(equalTo: backbutton.centerXAnchor).
backbutton.centerXAnchor.constraint(equalTo: backbutton.centerXAnchor, constant: 100).isActive = true
backbutton.centerYAnchor.constraint(equalTo: backbutton.centerYAnchor, constant: 300).isActive = true
As you want to align it center vertically and horizontally to its parent view so you should set the center constraints equal to parent view as below,
backbutton.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0).isActive = true
backbutton.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 0).isActive = true
Try this!!
self.view.setNeedsUpdateConstraints()
self.view.layoutIfNeeded()
Try using CGRect instead:
let screenWidth = UIScreen.main.bounds.width
let screenHeight = UIScreen.main.bounds.height
let xPostion:CGFloat = screenWidth - 150 //150 is half of view's width
let yPostion:CGFloat = screenHeight - 150 //150 is half of view's height
let buttonWidth:CGFloat = 300
let buttonHeight:CGFloat = 300
backbutton.frame = CGRect(x:xPostion, y:yPostion, width:buttonWidth, height:buttonHeight)
instead of screen width/height you can also try to get the width of your super view using:
self.view.frame.width
self.view.frame.height

Get height of uiview added by autolayout

I am added a view in viewDidAppear method using autolayout. In the end of viewDidAppear trying to find the height of view that I added , I am getting zero?
That view I am adding has a label , height of that label is dynamic
let viewToShowIn = self.view!
let bannerView = UIView()
bannerView.backgroundColor = UIColor.red
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
viewToShowIn.addSubview(bannerView)
bannerView.addSubview(label)
let margins = viewToShowIn.layoutMarginsGuide
bannerView.translatesAutoresizingMaskIntoConstraints = false
bannerView.leadingAnchor.constraint(equalTo: viewToShowIn.leadingAnchor, constant: 0).isActive = true
bannerView.trailingAnchor.constraint(equalTo: viewToShowIn.trailingAnchor, constant: 0).isActive = true
let bannerTopConstraint = bannerView.topAnchor.constraint(equalTo: margins.topAnchor, constant: 0)
bannerTopConstraint.isActive = true
label.font = UIFont.systemFont(ofSize: 20)
label.text = "kjafj kfj fk fjk dakjd k fdjakljf dkfjklsdjf dfsjlkj lkfsdjlkjl sjflksdjfljslf sdfljdslkjflsdjf sldfjlksj"
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
label.textAlignment = .center
label.leadingAnchor.constraint(equalTo: bannerView.leadingAnchor, constant: 20).isActive = true
label.trailingAnchor.constraint(equalTo: bannerView.trailingAnchor, constant: -20).isActive = true
label.topAnchor.constraint(equalTo: bannerView.topAnchor, constant: 10).isActive = true
bannerView.bottomAnchor.constraint(equalTo: label.bottomAnchor, constant: 10).isActive = true
Declare your label and view as global variable like
var viewToShowIn = UIView()
let bannerView = UIView()
let label = UILabel()
Then set constraints in viewDidAppear or viewDidLoad
viewToShowIn = self.view!
bannerView.backgroundColor = UIColor.red
label.translatesAutoresizingMaskIntoConstraints = false
viewToShowIn.addSubview(bannerView)
bannerView.addSubview(label)
let margins = viewToShowIn.layoutMarginsGuide
bannerView.translatesAutoresizingMaskIntoConstraints = false
bannerView.leadingAnchor.constraint(equalTo: viewToShowIn.leadingAnchor, constant: 0).isActive = true
bannerView.trailingAnchor.constraint(equalTo: viewToShowIn.trailingAnchor, constant: 0).isActive = true
let bannerTopConstraint = bannerView.topAnchor.constraint(equalTo: margins.topAnchor, constant: 0)
bannerTopConstraint.isActive = true
label.font = UIFont.systemFont(ofSize: 20)
label.text = "kjafj kfj fk fjk dakjd k fdjakljf dkfjklsdjf dfsjlkj lkfsdjlkjl sjflksdjfljslf sdfljdslkjflsdjf sldfjlksj"
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
label.textAlignment = .center
label.leadingAnchor.constraint(equalTo: bannerView.leadingAnchor, constant: 20).isActive = true
label.trailingAnchor.constraint(equalTo: bannerView.trailingAnchor, constant: -20).isActive = true
label.topAnchor.constraint(equalTo: bannerView.topAnchor, constant: 10).isActive = true
bannerView.bottomAnchor.constraint(equalTo: label.bottomAnchor, constant: 10).isActive = true
You will get height of your view or label in viewWillLayoutSubviews() or viewDidLayoutSubviews() method
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// GET YOUR HEIGHT HERE
}
After adding all constraints to view just call self.view.layoutIfNeeded()
It will force auto layout engine to calculate view size.

UIScrollView not able to scroll?

I have this UIScrollView set up with a UIImageView and a UILabel.
However, it's not scrolling like I want it to. It's like it's not even there... How can I fix this? I just want it to be scrollable (even though theres nothing at the bottom to scroll to yet)
Thx in advance!
Here's my code:
// scrollView
scrollView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView)
// scrollView constraints
scrollView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
scrollView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
// Initialize profileImageView
profileImageView.image = #imageLiteral(resourceName: "neil")
profileImageView.translatesAutoresizingMaskIntoConstraints = false
profileImageView.layer.cornerRadius = 125 / 2
profileImageView.clipsToBounds = true
scrollView.addSubview(profileImageView)
// Add profileImageView constraints
profileImageView.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 45).isActive = true
profileImageView.heightAnchor.constraint(equalToConstant: 125).isActive = true
profileImageView.widthAnchor.constraint(equalToConstant: 125).isActive = true
profileImageView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
// Add separator view
seperator.translatesAutoresizingMaskIntoConstraints = false
seperator.backgroundColor = UIColor.darkGray
scrollView.addSubview(seperator)
// seperator constraints
seperator.heightAnchor.constraint(equalToConstant: 2).isActive = true
seperator.widthAnchor.constraint(equalTo: self.view.widthAnchor, multiplier: 0.8).isActive = true
seperator.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
seperator.topAnchor.constraint(equalTo: profileImageView.bottomAnchor, constant: 20).isActive = true
//nameLabel
let nameLabelFont = UIFont.monospacedDigitSystemFont(ofSize: 36, weight: UIFont.Weight.heavy)
nameLabel.font = nameLabelFont
nameLabel.text = currentUser.name
nameLabel.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(nameLabel)
// nameLabel constraints
nameLabel.topAnchor.constraint(equalTo: seperator.bottomAnchor, constant: 10).isActive = true
nameLabel.leadingAnchor.constraint(equalTo: seperator.leadingAnchor).isActive = true
You can't scroll because scrollView.contentSize isn't bigger then your view.
If you want make your screen scrollable you should set scrollView.contentSize.
Put scrollView.contentSize = CGSize(width: 1000, height: 1000) before view.addSubview(scrollView) to set your scrollable area to 1000x1000.
Generally scrollView.contentSize should be calculated base on UI elements inside UIScrollView e.g. size of 10 images with space between them.

Resources