Multiline UILabel on navigation bar title view - ios

I have a custom UIView which has 2 UILabel (same width) and one UIImageView (known width of 44pt). Width sizes are given as an example, they can change but It is exact that UILabels should has same width and UIImage has a 44 point width. I want to add this view to UINavigationBar' titleView BUT ImageView should be in the center of navigation bar.
(60 width) UILabel---UIImageView (44 width) ---UILabel (60 width)
I want is that UILabels to have maximum two line and adJustFontSizeToFitWidth true. I'm giving specific width and height to title view but labels get two line but their font size doesn't change even they don't fit the view.
How I add titleView:
navigationItem.titleView = myTitleView
let widthOfItem: CGFloat = 30.0
let pading: CGFloat = 40
let aWidth: CGFloat = (self.navigationController?.navigationBar.frame.width)! - CGFloat(1) * widthOfItem * 2.0 - pading
myTitleView { (make) in
make.width.equalTo(aWidth)
make.height.equalTo(44)
}
MyCustomView:
override func layoutSubviews() {
super.layoutSubviews()
let preferredWidth = (bounds.width / 2) - 56
firstLabel.preferredMaxLayoutWidth = preferredWidth
secondLabel.preferredMaxLayoutWidth = preferredWidth
}
private func setupViews() {
addSubview(firstLabel)
addSubview(myImageView)
addSubview(secondLabel)
firstLabel.font = .myFont(.bold, size: 36)
firstLabel.adjustsFontSizeToFitWidth = true
firstLabel.minimumScaleFactor = 0.5
firstLabel.textColor = .textPrimary
firstLabel.numberOfLines = 2
firstLabel.lineBreakMode = .byWordWrapping
firstLabel.textAlignment = .right
myImageView.contentMode = .scaleAspectFit
myImageView.clipsToBounds = true
myImageView.layer.minificationFilter = .trilinear
myImageView.layer.cornerRadius = currencyImageSize.height / 2
secondLabel.font = . myFont(.bold, size: 36)
secondLabel.translatesAutoresizingMaskIntoConstraints = false
secondLabel.textColor = .textPrimary
secondLabel.numberOfLines = 2
secondLabel.adjustsFontSizeToFitWidth = true
secondLabel.baselineAdjustment = .none
secondLabel.minimumScaleFactor = 0.5
secondLabel.lineBreakMode = .byWordWrapping
secondLabel.textAlignment = .left
firstLabel.snp.makeConstraints { (make) in
make.leading.equalToSuperview()
make.top.bottom.equalToSuperview()
make.trailing.equalTo(myImageView.snp.leading).offset(-12)
}
myImageView.snp.makeConstraints { (make) in
make.height.equalToSuperview()
make.width.equalTo(myImageView.snp.height)
make.centerX.equalToSuperview()
}
secondLabel.snp.makeConstraints { (make) in
make.top.bottom.equalToSuperview()
make.leading.equalTo(myImageView.snp.trailing).offset(12)
make.trailing.equalToSuperview()
}
}

Maybe this code will work?
class CustomNavClass: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setup_view()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setup_view()
}
private func setup_view() {
backgroundColor = .lightGray
let image = UIImageView(image: .init())
addSubview(image)
image.translatesAutoresizingMaskIntoConstraints = false
image.backgroundColor = .green
NSLayoutConstraint.activate([
image.centerXAnchor.constraint(equalTo: centerXAnchor),
image.widthAnchor.constraint(equalToConstant: 44),
image.heightAnchor.constraint(equalToConstant: 44),
image.topAnchor.constraint(equalTo: topAnchor, constant: 3),
image.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -3)
])
let leftLabel = UILabel()
leftLabel.text = "Label label left long label will go here, naturally"
leftLabel.numberOfLines = 2
leftLabel.adjustsFontSizeToFitWidth = true
leftLabel.backgroundColor = .red
addSubview(leftLabel)
leftLabel.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
leftLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
leftLabel.centerYAnchor.constraint(equalTo: centerYAnchor),
leftLabel.trailingAnchor.constraint(equalTo: image.leadingAnchor, constant: -10)
])
let rightLabel = UILabel()
rightLabel.text = "Label label right long label will go here, naturally"
rightLabel.numberOfLines = 2
rightLabel.adjustsFontSizeToFitWidth = true
rightLabel.backgroundColor = .blue
addSubview(rightLabel)
rightLabel.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
rightLabel.trailingAnchor.constraint(equalTo: trailingAnchor),
rightLabel.centerYAnchor.constraint(equalTo: centerYAnchor),
rightLabel.leadingAnchor.constraint(equalTo: image.trailingAnchor, constant: 10)
])
}
}
The code does not use a UIStackView as its limited to its function (specifically, only one UIView can be stretched out on .fill distribution property.
This instead creates layout constraints:
UIImageView that is pegged at the center(with a 3 top and bottom constraint, optional, you can use centerY constraint or the like).
Leading UILabel is pegged to leading edge and trailing edge with UIImageView's leading edge.
Trailing UILabel is pegged to trailing edge and leading edge with UIImageView's trailing edge.

Related

Swift: UIView not resizing to fit content

I am building an app in which I have a list of Reviews. They look like the following screenshot
For some reason, I am failing to make the UIView (Gray box from the top light gray line to the bottom one) resize correctly. The white text inside it (Actual review) is longer than 1 line and should not get cut off, only when reaching a maximum of say 5 lines. The thing is, it works when I don't set width and height constraints for the user image you see on the left side. Removing those will make the view resize correctly, but it will completely distort the image. The image top and botton anchors seem to be glued to the anchors in its horizontal stackview, which again are stuck to the UIView's top and botton anchors with constants, as it should. But nowhere do I say that the UIView should always have the size of the image. I don't get why it wont go bigger than the image.
Here is a screenshot of my structure with the constraints, hope it is clear enough:
NSLayoutConstraint.activate([
//Main horizontal stackview (one Rating is the name of the UIView)
hStack.leadingAnchor.constraint(equalTo: oneRating.leadingAnchor, constant: 23),
hStack.trailingAnchor.constraint(equalTo: oneRating.trailingAnchor, constant: -18),
hStack.topAnchor.constraint(equalTo: oneRating.topAnchor, constant: 15),
hStack.bottomAnchor.constraint(equalTo: oneRating.bottomAnchor, constant: -13),
reviewerImage.heightAnchor.constraint(equalToConstant: 80),
reviewerImage.widthAnchor.constraint(equalToConstant: 80),
//Limit the size of the Review Text to make sure its always at the same spot
v2Stack.widthAnchor.constraint(equalToConstant: 220.0),
])
//Verified checkmark constraints
if isUserVerified == true {
reviewerVerified.bottomAnchor.constraint(equalTo: reviewerImage.bottomAnchor).isActive = true
reviewerVerified.trailingAnchor.constraint(equalTo: reviewerImage.trailingAnchor, constant: -2).isActive = true
}
I know it is hard to help like this but I have tried to fix this for a few days and no matter what I do, I can't get it to work.
EDIT:
As per request, here is the code I have to add the ImageView to my UIView().
//Add Image
let reviewerImage = UIImageView()
reviewerImage.contentMode = .scaleAspectFill
reviewerImage.layer.cornerRadius = 40 //= 1/2 of width, because we hard coded the size
reviewerImage.image = UIImage(named: "person-icon") //Placeholder. Download image here
reviewerImage.translatesAutoresizingMaskIntoConstraints = false
VStack1.addArrangedSubview(reviewerImage)
You will need to add a few more sizing constraints, but...
The "trick" is to embed your "reviewer image view" in a clear "container" view. Then constrain the image view to the Top of that container.
Here is some sample code that gets close to your layout:
class JanView: UIView {
let reviewerImageView: UIImageView = {
let v = UIImageView()
return v
}()
let starImageView: UIImageView = {
let v = UIImageView()
return v
}()
let chevronImageView: UIImageView = {
let v = UIImageView()
return v
}()
let nameLabel: UILabel = {
let v = UILabel()
return v
}()
let locLabel: UILabel = {
let v = UILabel()
return v
}()
let reviewTextLabel: UILabel = {
let v = UILabel()
v.numberOfLines = 5
return v
}()
let publishedLabel: UILabel = {
let v = UILabel()
return v
}()
let starValueLabel: UILabel = {
let v = UILabel()
v.textAlignment = .center
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
backgroundColor = .darkGray
let outerHStack: UIStackView = {
let v = UIStackView()
v.spacing = 10
return v
}()
let labelsVStack: UIStackView = {
let v = UIStackView()
v.axis = .vertical
v.spacing = 0
return v
}()
let starsAndChevronVStack: UIStackView = {
let v = UIStackView()
v.axis = .vertical
v.spacing = 0
return v
}()
let starsHStack: UIStackView = {
let v = UIStackView()
v.spacing = 0
v.alignment = .center
return v
}()
// review image container
let reviewerImageContainer: UIView = {
let v = UIView()
return v
}()
[nameLabel, reviewTextLabel].forEach { v in
v.textColor = .white
}
[locLabel, publishedLabel].forEach { v in
v.textColor = .lightGray
}
starValueLabel.textColor = .systemYellow
outerHStack.translatesAutoresizingMaskIntoConstraints = false
addSubview(outerHStack)
[nameLabel, locLabel, reviewTextLabel, publishedLabel].forEach { v in
labelsVStack.addArrangedSubview(v)
}
[starValueLabel, starImageView].forEach { v in
starsHStack.addArrangedSubview(v)
}
[starsHStack, chevronImageView].forEach { v in
starsAndChevronVStack.addArrangedSubview(v)
}
[reviewerImageContainer, labelsVStack, starsAndChevronVStack].forEach { v in
outerHStack.addArrangedSubview(v)
}
// add reviewer image view to container
reviewerImageContainer.addSubview(reviewerImageView)
reviewerImageView.translatesAutoresizingMaskIntoConstraints = false
// specific properties
reviewerImageView.contentMode = .scaleAspectFill
reviewerImageView.layer.cornerRadius = 40
reviewerImageView.layer.masksToBounds = true
let cfg = UIImage.SymbolConfiguration(pointSize: 12.0, weight: .bold)
if let img = UIImage(systemName: "star.fill", withConfiguration: cfg) {
starImageView.image = img
}
starImageView.tintColor = .systemYellow
starImageView.contentMode = .center
starValueLabel.text = "4"
if let img = UIImage(systemName: "chevron.right", withConfiguration: cfg) {
chevronImageView.image = img
}
chevronImageView.tintColor = .lightGray
chevronImageView.contentMode = .center
nameLabel.text = "Name Here"
locLabel.text = "Location Here"
reviewTextLabel.text = "Review Text Here"
publishedLabel.text = "Published Info Here"
let g = self
// to get the 2nd vertical stack view to fit (horizontally) to its content
let sacWidth = starsAndChevronVStack.widthAnchor.constraint(equalToConstant: 10.0)
sacWidth.priority = .defaultHigh
let vPadding: CGFloat = 12
let hPadding: CGFloat = 10
NSLayoutConstraint.activate([
outerHStack.topAnchor.constraint(equalTo: g.topAnchor, constant: vPadding),
outerHStack.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: hPadding),
outerHStack.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -hPadding),
outerHStack.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -vPadding),
reviewerImageView.widthAnchor.constraint(equalToConstant: 80.0),
reviewerImageView.heightAnchor.constraint(equalTo: reviewerImageView.widthAnchor),
// align the reviewer image view with the top of the container view
reviewerImageView.topAnchor.constraint(equalTo: reviewerImageContainer.topAnchor),
reviewerImageView.leadingAnchor.constraint(equalTo: reviewerImageContainer.leadingAnchor),
reviewerImageView.trailingAnchor.constraint(equalTo: reviewerImageContainer.trailingAnchor),
// give the stars value label a width, so it doesn't vary by text
// "5" is wider than "1" (or it may be "" ?)
starValueLabel.widthAnchor.constraint(equalToConstant: 16.0),
// make the star image view square
starImageView.widthAnchor.constraint(equalTo: starImageView.heightAnchor),
// make the stars HStack height equal to the stars label height
starsHStack.heightAnchor.constraint(equalTo: starValueLabel.heightAnchor),
sacWidth,
])
}
}
and an example controller:
class ReviewVC: UIViewController {
let scrollView: UIScrollView = {
let v = UIScrollView()
return v
}()
let reviewsStack: UIStackView = {
let v = UIStackView()
v.axis = .vertical
v.spacing = 0
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
[reviewsStack, scrollView].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
}
scrollView.addSubview(reviewsStack)
view.addSubview(scrollView)
let g = view.safeAreaLayoutGuide
let contentG = scrollView.contentLayoutGuide
let frameG = scrollView.frameLayoutGuide
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
scrollView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
reviewsStack.topAnchor.constraint(equalTo: contentG.topAnchor, constant: 0.0),
reviewsStack.leadingAnchor.constraint(equalTo: contentG.leadingAnchor, constant: 0.0),
reviewsStack.trailingAnchor.constraint(equalTo: contentG.trailingAnchor, constant: 0.0),
reviewsStack.bottomAnchor.constraint(equalTo: contentG.bottomAnchor, constant: 0.0),
reviewsStack.widthAnchor.constraint(equalTo: frameG.widthAnchor, constant: 0.0),
])
let sampleLocs: [String] = [
"Koblenz, Germany",
"Westerwald, Germany",
"Bonn, Germany",
"Saarbrüken, Germany",
]
let sampleRevs: [String] = [
"For some reason, I am failing to make the UIView (Gray box from the top light gray line to the bottom one) resize correctly.",
"A Single Line",
"The white text inside it (Actual review) is longer than 1 line and should not get cut off, only when reaching a maximum of say 5 lines.",
"The thing is, it works when I don't set width and height constraints for the user image you see on the left side. Removing those will make the view resize correctly, but it will completely distort the image.",
"Another Single Line",
"The image top and botton anchors seem to be glued to the anchors in its horizontal stackview, which again are stuck to the UIView's top and botton anchors with constants, as it should.",
]
let sampleStars: [String] = [
"5", "4", "3", "2", "1",
]
for i in 0..<sampleRevs.count {
let v = JanView()
v.nameLabel.text = "Clara R."
v.locLabel.text = sampleLocs[i % sampleLocs.count]
v.reviewTextLabel.text = sampleRevs[i]
v.publishedLabel.text = "Published less than 24h ago"
v.starValueLabel.text = sampleStars[i % sampleStars.count]
if let img = UIImage(named: "prof") {
v.reviewerImageView.image = img
}
reviewsStack.addArrangedSubview(v)
let sepView = UIView()
sepView.backgroundColor = .lightGray
sepView.heightAnchor.constraint(equalToConstant: 1.0).isActive = true
reviewsStack.addArrangedSubview(sepView)
}
}
}
Here is how it ends up looking:

How to set the corner radius to 50% [duplicate]

This question already has answers here:
Set UIView's corner radius to half of the view's width automatically
(3 answers)
Closed 11 months ago.
I have an image view inside a collection view cell. I would like to set the corner radius of the image to 50% of its width (so it's a circle). How can I do this?
Here's my code so far
//
// CategoryCell.swift
// UICollectionViewDemo
//
import UIKit
final class Category3Cell: UICollectionViewCell {
private enum Constants {
// MARK: contentView layout constants
static let contentViewCornerRadius: CGFloat = 0.0
// MARK: imageView layout constants
static let imageWidth: CGFloat = 90.0
static let imageHeight: CGFloat = 90.0
// MARK: Generic layout constants
static let verticalSpacing: CGFloat = 10.0
static let horizontalPadding: CGFloat = 16.0
static let nameImagePadding: CGFloat = 20.0
}
public var categoryKey : String = "";
private let imageView: UIImageView = {
let imageView = UIImageView(frame: .zero)
imageView.contentMode = .scaleAspectFit
imageView.layer.cornerRadius = 45
imageView.layer.masksToBounds = true
return imageView
}()
private let name: UILabel = {
let label = UILabel(frame: .zero)
label.textAlignment = .center
label.numberOfLines = 0
label.font = UIFont(name: "CeraPro-Regular", size: 17);
return label
}()
override init(frame: CGRect) {
super.init(frame: .zero)
setupViews()
setupLayouts()
}
private func setupViews() {
contentView.clipsToBounds = true
contentView.layer.cornerRadius = Constants.contentViewCornerRadius
contentView.backgroundColor = .clear
contentView.isUserInteractionEnabled = true
contentView.addSubview(imageView)
contentView.addSubview(name)
}
private func setupLayouts() {
imageView.translatesAutoresizingMaskIntoConstraints = false
name.translatesAutoresizingMaskIntoConstraints = false
// Layout constraints for `imageView`
NSLayoutConstraint.activate([
imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
imageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
imageView.topAnchor.constraint(equalTo: contentView.topAnchor),
imageView.heightAnchor.constraint(equalToConstant: Constants.imageWidth),
imageView.heightAnchor.constraint(equalToConstant: Constants.imageHeight)
])
// Layout constraints for `usernameLabel`
NSLayoutConstraint.activate([
name.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: Constants.horizontalPadding),
name.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -Constants.horizontalPadding),
name.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: Constants.nameImagePadding)
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setup(image: String, nameOf: String, key: String) {
imageView.image = UIImage.init(named: image)
name.text = nameOf
categoryKey = key
}
}
extension Category3Cell: ReusableView {
static var identifier: String {
return String(describing: self)
}
}
you need first the clipsToBounds set to true and then if you know the image size, you can set its layer.cornerRadius to half of that size.
Alternatively you can use the layoutSubviews method, and in its override access the imageView bounds.height and use half of this for the corner radius.
Try this code:
imageView.layer.cornerRadius = imageView.frame.height / 2
Set width and height at first, then set imageView.layer.cornerRadius = imageView.frame.height / 2.
class ViewController: UIViewController {
private let imageView: UIImageView = {
let imageView = UIImageView(frame: .zero)
imageView.contentMode = .scaleAspectFit
return imageView
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
imageView.frame = CGRect(x: 100, y: 100, width: 100, height: 100);
imageView.layer.cornerRadius = 45
imageView.layer.masksToBounds = true
self.view.addSubview(imageView)
imageView.image = UIImage.init(named: "+")
}
}

Cannot move UILabel position programatically

I have a file for the View section of app, where i have all the labels and images that i intent to use, this is what i have in my DetailViewTableCell class, which inherits from UIView.
class DetailViewTableCell: UIView {
var detailMainImage: UIImageView = UIImageView()
var detailName: UILabel = UILabel()
var detailType: UILabel = UILabel()
var detailHeart: UIImageView = UIImageView()
}
Now i move to my DetailViewController class, here i try and add the label, the label is added but it appears always at top left corner at 0,0 coordinate, when i try and add constraints for position, i always get error, now i can try
detailMain.detailName.frame.origin.x = 30
but i get error:
Constraint items must each be a view or layout guide.
In any case i do not wish to use this approach but more something like this
NSLayoutConstraint(item: detailMain.detailName, attribute: .leading, relatedBy: .equal, toItem: detailMain.detailName.superview, attribute: .leading, multiplier: 1, constant: 20).isActive = true
but i get the same above error, my over all code is this:
self.view.addSubview(detailMain.detailName)
detailMain.detailName.translatesAutoresizingMaskIntoConstraints = false
detailMain.detailName.heightAnchor.constraint(equalToConstant: 25).isActive = true
detailMain.detailName.widthAnchor.constraint(greaterThanOrEqualToConstant: 100).isActive = true
detailMain.detailName.font = UIFont(name: "Rubik-Medium", size: 30)
detailMain.detailName.backgroundColor = UIColor.white
detailMain.detailName.textColor = UIColor.black
Which works perfectly fine but the moment i try and constraints, the error come up, this is how the app shows up with out constraints and name at top most left corner
////////UPDATE
So here is my new DetailViewTableCell,
import UIKit
class DetailViewTableCell: UIView {
var detailMainImage: UIImageView = UIImageView()
var detailName: UILabel = UILabel()
var detailType: UILabel = UILabel()
var detailHeart: UIImageView = UIImageView()
override init(frame: CGRect) {
super.init(frame: frame)
[detailMainImage, detailName, detailType, detailHeart].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
addSubview($0)
}
NSLayoutConstraint.activate([
// constrain main image to all 4 sides
detailMainImage.topAnchor.constraint(equalTo: topAnchor),
detailMainImage.leadingAnchor.constraint(equalTo: leadingAnchor),
detailMainImage.trailingAnchor.constraint(equalTo: trailingAnchor),
detailMainImage.bottomAnchor.constraint(equalTo: bottomAnchor),
// activate the height contraint
// constrain detailType label
// 30-pts from Leading
// 12-pts from Top
detailType.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 30.0),
detailType.topAnchor.constraint(equalTo: topAnchor, constant: 12.0),
// constrain detailName label
// 30-pts from Leading
// 12-pts from Bottom
detailName.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 30.0),
detailName.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -12.0),
// constrain detailHeart image
// 12-pts from Trailing
// 12-pts from Bottom
// width: 24 height: equal to width (1:1 square)
detailHeart.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12.0),
detailHeart.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -12),
detailHeart.widthAnchor.constraint(equalToConstant: 24),
detailHeart.heightAnchor.constraint(equalTo: detailHeart.widthAnchor)
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
and this 2 lines is what i add to my Detail view controller in viewDidLoad
let v = DetailViewTableCell()
detailTableView.tableHeaderView = v
Also i add this function
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// this is needed to allow the header view's content
// to determine its height
guard let headerView = detailTableView.tableHeaderView else {
return
}
let size = headerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
if headerView.frame.size.height != size.height {
headerView.frame.size.height = size.height
detailTableView.tableHeaderView = headerView
detailTableView.layoutIfNeeded()
}
}
then in my viewForHeaderInSection inbuilt function i add this
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView()
tableView.rowHeight = 80
headerView.translatesAutoresizingMaskIntoConstraints = false
headerView.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width).isActive = true
headerView.heightAnchor.constraint(equalToConstant: 400).isActive = true
detailMain.detailMainImage.translatesAutoresizingMaskIntoConstraints = false
detailMain.detailMainImage.heightAnchor.constraint(equalToConstant: 400).isActive = true
detailMain.detailMainImage.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width).isActive = true
detailMain.detailMainImage.image = UIImage(named: restaurant.image)
detailMain.detailMainImage.contentMode = .scaleAspectFill
detailMain.detailMainImage.clipsToBounds = true
headerView.addSubview(detailMain.detailMainImage)
//Add the name
detailMain.detailName.text = restaurant.name
headerView.addSubview(detailMain.detailName)
return headerView
}
but still same position , is there any thing i add to add or remove from my code
You didn't show where you *want the labels, but this should get you going...
In your "header view" class:
add your elements: detailMainImage, detailName, etc...
set their properties and constraints as desired
You can get auto-layout to use the constraints you've setup in the header view to automatically determine its height:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// this is needed to allow the header view's content
// to determine its height
guard let headerView = tableView.tableHeaderView else {
return
}
let size = headerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
if headerView.frame.size.height != size.height {
headerView.frame.size.height = size.height
tableView.tableHeaderView = headerView
tableView.layoutIfNeeded()
}
}
So, here's a complete example:
class TestHeaderTableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
// instantiate the header view
let v = DetailTableHeaderView()
// set it as the tableHeaderView
tableView.tableHeaderView = v
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// this is needed to allow the header view's content
// to determine its height
guard let headerView = tableView.tableHeaderView else {
return
}
let size = headerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
if headerView.frame.size.height != size.height {
headerView.frame.size.height = size.height
tableView.tableHeaderView = headerView
tableView.layoutIfNeeded()
}
}
}
class DetailTableHeaderView: UIView {
var detailMainImage: UIImageView = UIImageView()
var detailName: UILabel = UILabel()
var detailType: UILabel = UILabel()
var detailHeart: UIImageView = UIImageView()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
backgroundColor = .white
// give the heart image view a background color so we can see its frame
detailHeart.backgroundColor = .red
if let img = UIImage(named: "teacup") {
detailMainImage.image = img
}
// give labels some text so we can see them
detailType.text = "Detail Type"
detailName.text = "Detail Name"
// setup fonts for labels as desired
//detailName.font = UIFont(name: "Rubik-Medium", size: 30)
// I don't have "Rubik" so this is with the system font
detailName.font = UIFont.systemFont(ofSize: 30, weight: .bold)
detailName.backgroundColor = UIColor.white
detailName.textColor = UIColor.black
detailType.textColor = .white
[detailMainImage, detailName, detailType, detailHeart].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
addSubview($0)
}
// give main image a height
// set its Priority to 999 to prevent layout constraint conflict warnings
let mainImageHeightAnchor = detailMainImage.heightAnchor.constraint(equalToConstant: 300.0)
mainImageHeightAnchor.priority = UILayoutPriority(rawValue: 999)
NSLayoutConstraint.activate([
// constrain main image to all 4 sides
detailMainImage.topAnchor.constraint(equalTo: topAnchor),
detailMainImage.leadingAnchor.constraint(equalTo: leadingAnchor),
detailMainImage.trailingAnchor.constraint(equalTo: trailingAnchor),
detailMainImage.bottomAnchor.constraint(equalTo: bottomAnchor),
// activate the height contraint
mainImageHeightAnchor,
// constrain detailType label
// 30-pts from Leading
// 12-pts from Top
detailType.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 30.0),
detailType.topAnchor.constraint(equalTo: topAnchor, constant: 12.0),
// constrain detailName label
// 30-pts from Leading
// 12-pts from Bottom
detailName.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 30.0),
detailName.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -12.0),
// constrain detailHeart image
// 12-pts from Trailing
// 12-pts from Bottom
// width: 24 height: equal to width (1:1 square)
detailHeart.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12.0),
detailHeart.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -12),
detailHeart.widthAnchor.constraint(equalToConstant: 24),
detailHeart.heightAnchor.constraint(equalTo: detailHeart.widthAnchor),
])
}
}
For this example, I just set the background color of the "heart" image view to red, and I clipped the teacup out of your image:
And this is the result:
Edit - to use the custom view as a Section header view...
Use the same DetailTableHeaderView class from above, but change the table view controller as follows:
class TestSectionHeaderTableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.sectionHeaderHeight = UITableView.automaticDimension
tableView.estimatedSectionHeaderHeight = 300
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if section == 0 {
let v = DetailTableHeaderView()
// for example implementation...
if let img = UIImage(named: "teacup") {
v.detailMainImage.image = img
}
v.detailName.text = "Testing the Name"
// for your implementation...
//if let img = UIImage(named: restaurant.image) {
// v.detailMainImage.image = img
//}
//v.detailName.text = restaurant.name
return v
}
return nil;
}
}

UIStackView setting layoutMargins in code breaks alignment

I'm trying to add layoutMargins to some elements in a UIStackView I'm creating in code.
I've tried the same thing in IB and it works perfectly, but when I add layoutMargins and I set isLayoutMarginsRelativeArrangement = true then my stack view's alignment breaks.
The code of my stack view is the following:
#objc lazy var buttonsStackView: UIStackView = {
let stack = UIStackView(arrangedSubviews: [doneButton, separatorView, giftButton])
stack.layoutMargins = UIEdgeInsets(top: 0, left: 4, bottom: 0, right: 4)
stack.isLayoutMarginsRelativeArrangement = true
stack.axis = .horizontal
stack.frame = CGRect(x: 0, y: 0, width: 150, height: 44)
stack.spacing = 4
stack.distribution = .equalCentering
stack.alignment = .center
let bgView = UIView(frame: stack.bounds)
stackViewBackground = bgView
bgView.backgroundColor = ColorManager.shared.grayColor
bgView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
stack.insertSubview(bgView, at: 0)
separatorView.heightAnchor.constraint(equalTo: stack.heightAnchor, multiplier: 0.8).isActive = true
return stack
}()
SeparatorView only has a 1pt width constraint, while the two buttons are left unconstrained to keep theirintrinsicContentSize.
Here's how my stackView looks when isLayoutMarginsRelativeArrangement is false:
But obviously, the left and right margins are needed, so when setting isLayoutMarginsRelativeArrangement to true, my stackview's alignment breaks:
Unfortunately, I cannot use IB for this particular view and I need to initialise it from code. Any idea on how to fix this is greatly appreciated. Thank you!
Here is an example of making that a custom view, with the separator centered horizontally, and the buttons centered in each "side":
protocol DoneGiftDelegate: class {
func doneButtonTapped()
func giftButtonTapped()
}
class DoneGiftView: UIView {
weak var delegate: DoneGiftDelegate?
let doneButton: UIButton = {
let v = UIButton(type: .system)
v.setTitle("Done", for: [])
v.tintColor = .white
return v
}()
let giftButton: UIButton = {
let v = UIButton(type: .system)
v.setImage(UIImage(systemName: "gift.fill"), for: [])
v.tintColor = .white
return v
}()
let separatorView: UIView = {
let v = UIView()
v.backgroundColor = .white
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
backgroundColor = .lightGray // ColorManager.shared.grayColor
[doneButton, separatorView, giftButton].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
addSubview($0)
}
// width and height constraints: 150 x 44
// set Priority to 999 so it can be overriden by controller if desired
let widthConstraint = widthAnchor.constraint(equalToConstant: 150.0)
widthConstraint.priority = UILayoutPriority(rawValue: 999)
let heightConstraint = heightAnchor.constraint(equalToConstant: 44.0)
heightConstraint.priority = UILayoutPriority(rawValue: 999)
NSLayoutConstraint.activate([
// doneButton Leading to Leading
doneButton.leadingAnchor.constraint(equalTo: leadingAnchor),
// separator Leading to doneButton Trailing
separatorView.leadingAnchor.constraint(equalTo: doneButton.trailingAnchor),
// giftButton Leading to separator Trailing
giftButton.leadingAnchor.constraint(equalTo: separatorView.trailingAnchor),
// giftButton Trailing to Trailing
giftButton.trailingAnchor.constraint(equalTo: trailingAnchor),
// all centered vertically
doneButton.centerYAnchor.constraint(equalTo: centerYAnchor),
separatorView.centerYAnchor.constraint(equalTo: centerYAnchor),
giftButton.centerYAnchor.constraint(equalTo: centerYAnchor),
// doneButton Height = Height
doneButton.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 1.0),
// separator Height = 80% of Height
separatorView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.8),
// giftButton Height = Height
giftButton.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 1.0),
// separator Width = 1
separatorView.widthAnchor.constraint(equalToConstant: 1.0),
// doneButton Width = giftButton Width
doneButton.widthAnchor.constraint(equalTo: giftButton.widthAnchor, multiplier: 1.0),
// self Width and Height
widthConstraint,
heightConstraint,
])
// add target actions for buttons
doneButton.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
giftButton.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
}
#objc func buttonTapped(_ sender: UIButton) -> Void {
if sender == doneButton {
delegate?.doneButtonTapped()
} else {
delegate?.giftButtonTapped()
}
}
}
class DemoViewController: UIViewController, DoneGiftDelegate {
let doneGiftView: DoneGiftView = DoneGiftView()
let testView: UIView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
doneGiftView.translatesAutoresizingMaskIntoConstraints = false
testView.translatesAutoresizingMaskIntoConstraints = false
testView.addSubview(doneGiftView)
view.addSubview(testView)
// so we can see the view frame
testView.backgroundColor = .cyan
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// testView Top: 100
// Leading / Trailing with 20-pts "padding"
// Height: 80
testView.topAnchor.constraint(equalTo: g.topAnchor, constant: 100.0),
testView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
testView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
testView.heightAnchor.constraint(equalToConstant: 80.0),
// doneGiftView Trailing to testView Trailing
doneGiftView.trailingAnchor.constraint(equalTo: testView.trailingAnchor),
// doneGiftView centered vertically in testView
doneGiftView.centerYAnchor.constraint(equalTo: testView.centerYAnchor),
])
doneGiftView.delegate = self
}
func doneButtonTapped() {
print("Done button tapped!")
// do what we want when Done button tapped
}
func giftButtonTapped() {
print("Gift button tapped")
// do what we want when Gift button tapped
}
}
Example result:

How to center two views in super view with greater than or equal to constraints

I made an example ViewController with two Labels to highlight my issue. The goal is to vertically separate the labels by 10, and then center them vertically using greater than or equal to constraints. I'm using visual format, but this should apply if I setup my constraints like view.topAnchor.constraint(greaterThan.... I also have two constraints to horizontally layout the labels
My ViewController:
class myVC: UIViewController {
lazy var titleLabel: UILabel = {
let l = UILabel(frame: .zero)
l.translatesAutoresizingMaskIntoConstraints = false
l.text = "Hello World"
l.font = .systemFont(ofSize: 50)
l.textColor = .black
return l
}()
lazy var descLabel: UILabel = {
let l = UILabel(frame: .zero)
l.translatesAutoresizingMaskIntoConstraints = false
l.text = "description"
l.font = .systemFont(ofSize: 35)
l.textColor = .gray
return l
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .yellow
view.addSubview(titleLabel)
view.addSubview(descLabel)
titleLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
descLabel.leadingAnchor.constraint(equalTo: titleLabel.leadingAnchor).isActive = true
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "V:|-(<=50)-[titleLabel]-(10)-[descLabel]-(<=50)-|", options: .init(), metrics: nil, views: ["titleLabel": titleLabel, "descLabel": descLabel]))
}
}
This results in . From my understanding, this SHOULD separate the views by 10 pts, and center the labels vertically because in the format "V:|-(<=50)-[titleLabel]-(10)-[descLabel]-(<=50)-|" I say that the distance between the Title Label's top and the superView's top should be at least (greaterThanOrEqualTo) 50, and the distance between the description Label's bottom and the superView's bottom should be at least 50. What should my top and bottom constraints look like if I want to center the two labels vertically?
Yes, I realize I can just set vertical and horizontal centers, but this is an example I made for a problem I can't use those for. I need to be able to center the View with greater(or less) than or equal to constraints.
It's very difficult to center elements using VFL.
It's also difficult to center two elements unless they are embedded in a UIView or a UIStackView.
Here is one option by embedding the labels in a "container" UIView:
class MyVC: UIViewController {
lazy var titleLabel: UILabel = {
let l = UILabel(frame: .zero)
l.translatesAutoresizingMaskIntoConstraints = false
l.text = "Hello World"
l.font = .systemFont(ofSize: 50)
l.textColor = .black
// center the text in the label - change to .left if desired
l.textAlignment = .center
return l
}()
lazy var descLabel: UILabel = {
let l = UILabel(frame: .zero)
l.translatesAutoresizingMaskIntoConstraints = false
l.text = "description"
l.font = .systemFont(ofSize: 35)
l.textColor = .gray
// center the text in the label - change to .left if desired
l.textAlignment = .center
return l
}()
lazy var containerView: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .yellow
// give the labels and containerView background colors to make it easy to see the layout
titleLabel.backgroundColor = .green
descLabel.backgroundColor = .cyan
containerView.backgroundColor = .blue
// add containerView to view
view.addSubview(containerView)
// add labels to containerView
containerView.addSubview(titleLabel)
containerView.addSubview(descLabel)
NSLayoutConstraint.activate([
// constrain titleLabel Top to containerView Top
titleLabel.topAnchor.constraint(equalTo: containerView.topAnchor),
// constrain titleLabel Leading and Trailing to containerView Leading and Trailing
titleLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
titleLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
// constrain descLabel Leading and Trailing to containerView Leading and Trailing
descLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
descLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
// constrain descLabel Bottom to containerView Bottom
descLabel.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
// constrain descLabel Top 10-pts from titleLabel Bottom
descLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 10.0),
// constrain containerView centered horizontally and vertically
containerView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
containerView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
}
}
Result:
This can be achieved easily by using stackview. Add both the labels in stackview and center it vertically in the superview with all other constraints(top, leading, bottom, trailing).
Here is the sample code of view controller for your use-case.
class ViewController: UIViewController {
lazy var titleLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "Hello \nWorld"
label.font = .systemFont(ofSize: 50)
label.backgroundColor = .orange
label.numberOfLines = 0
label.textColor = .black
return label
}()
lazy var descLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "a\n b\n c\n"
label.font = .systemFont(ofSize: 35)
label.backgroundColor = .green
label.numberOfLines = 0
label.textColor = .gray
return label
}()
lazy var contentView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.spacing = 10
stackView.distribution = .fill
return stackView
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.white
contentView.addArrangedSubview(titleLabel)
contentView.addArrangedSubview(descLabel)
self.view.addSubview(contentView)
let constraints = [
contentView.topAnchor.constraint(greaterThanOrEqualTo: view.safeAreaLayoutGuide.topAnchor),
contentView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
contentView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
contentView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
contentView.bottomAnchor.constraint(lessThanOrEqualTo: view.safeAreaLayoutGuide.bottomAnchor)
]
NSLayoutConstraint.activate(constraints)
}
}
The above code will result this view and it goes on to take the top and buttom space until it meets the safeArea. Moreover you can set the vertical content hugging and compression resistance priority to control which label to expand or shrink.

Resources