Center align stackView while maintaining fixed spacing - swift

I have a UITableViewCell that is supposed to show two labels out of which one can be either 1 or 2 lines in height. The labels are supposed to have a fixed spacing between each other. Together they are always expected to vertically centered within the cell.
I thought that a UIStackView would be a good solution for this but none of the distribution options allow for a fixed spacing between the items. What's a good solution to the scenario described? I've attached an illustration (gray = cell, line = cell's vertical center) to make this easier

You're images are not actually showing the labels centered vertically.
With one line in each label, they are both 20.5-pts tall (assuming default font/size), with a 12-pt gap. Fine. Easy enough to center on the Y axis.
With two lines in the top label, it becomes (20.5-pts + 0-gap + 20.5-pts), and then 12-pt gap, and then 20.5-pt single-line label. So it's not an even distribution of the lines of text, which is why vertical centering of the stack view doesn't place the center-line through the center-line of the second line of text.
If you examine your image (which I'm assuming you laid-out by hand), you have 201 pixels between the top of the view and the top of the top label, but only 198 pixels between the bottom of the bottom label and the bottom of the view.
If that's really how you want it to lay out, you can do this:
set a fixed row height
give your stack view a centerYAxis constraint
keep a reference to that constraint
Then, override willDisplay cell:
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if let c = cell as? ExampleCell {
cell.layoutIfNeeded()
let topHeight = c.topLabel.frame.height
let botHeight = c.bottomLabel.frame.height
if topHeight > botHeight {
c.stackViewCenterYConstraint.constant = 6.0
} else if botHeight > topHeight {
c.stackViewCenterYConstraint.constant = -6.0
} else {
c.stackViewCenterYConstraint.constant = 0.0
}
}
}
The value of 6.0 is 1/2 of the stack view spacing (12 * 0.5 == 6), so you'll need to change that if you change your spacing.
Here is the result (red line is vertical mid-line):

Related

Automatic height adjustment for static UITableViewCell doesn't work

I have a static UITableView and I want to set the row height for three of the cells dynamically. So in viewDidLoad() I implemented the following code:
tableView.estimatedRowHeight = 100
tableView.rowHeight = UITableView.automaticDimension
I also implemented the heightForRowAt method:
(The first two cells of the first section should have a fixed height)
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.section == 0 && indexPath.row == 0 {
return CGFloat(85)
} else if indexPath.section == 0 && indexPath.row == 1 {
return CGFloat(145)
}
return UITableView.automaticDimension
}
This the result which I'm currently getting:
I changed the lines of the labels to 0, too and the constraints of the labels inside the cells are 0, 12, 0, 12 (top, right, bottom, left).
Does anybody know, why the cell in section 3 doesn't display the data in the right way?
Edit:
(How it looks after the implementation of the suggestion above)
Because sizeToFit() did not work for you, we are going to try something a little more involved.
The cell in section 3 is displaying the data the right way. This is because UILabels don't automatically adjust their height to accommodate the text inside. Here's what you need to do:
1. Create a height constraint for your UILabel In your interface builder, add a constraint for the height of the UILabel in section 3's cell. Connect this height constraint to your view controller's class via an #IBOutlet:
class YourViewController: UIViewController {
#IBOutlet var cellLabel: UILabel!
#IBOutlet var cellLabelHeight: NSLayoutConstraint!
...
}
2. Add String extension that calculates height I am unsure of where/when you are setting the text of the UILabel in question, but I know you are doing this somewhere as you have described it as being "dynamic". Whenever you do set the text of the UILabel in question, you now also need to change the constant of the height constraint that we made in order to accommodate this text. So, we need to be able to calculate the height of the UILabel based on its width and font. We can add an extension to String in order to do this:
extension String {
func height(withConstrainedWidth width: CGFloat, font: UIFont) -> CGFloat {
let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [.font: font], context: nil)
return ceil(boundingBox.height)
}
}
3. Set the height constraint's constant based off the UILabel's text The final step is to set the height of the UILabel height constraint we made by using the extension we just created:
cellLabel.text = "DummyDataDummyDataDummyDataDummyDataDummyDataDummyDataDummyDataDummyData"
//This will be called immediately after you set the text for the UILabel in question
cellLabelHeight.constant = cellLabel.text.height(withConstrainedWidth: cellLabel.frame.width, font: cellLabel.font)
The cell in section 3 is displaying the data the right way. Unless you tell it otherwise, a UILabel will not automatically adjust to accommodate the text within it.
What I need you to do is select the UILabel in question, then in the attributes inspector, set the Number of Lines to 0.
You also said that this UILabel is dynamic, meaning you are setting it's text somewhere in your code. Immediately after you set this UILabel's text, you are going to want to call myLabel.sizeToFit(). This should adjust the label's height to accommodate the text within.
If this doesn't work, I have another, more involved solution that should work for you.
Please look at the below;
Select your cellLabel and set the Lines value to 0:
Also apple says Self-Sizing
Summary :
lay out your table view cell’s content within the cell’s content view. To define the cell’s height, you need an unbroken chain of constraints and views (with defined heights) to fill the area between the content view’s top edge and its bottom edge. If your views have intrinsic content heights, the system uses those values. If not, you must add the appropriate height constraints, either to the views or to the content view itself.
Change the bottom constraint of the AuthorLabel from equal to Greater than or equal

Setting the height: TableViewCells with custom Nib that are different sizes

I have a TableViewController that has a custom TableViewCell under the identifier "customCell". Heres an image of the configuration of the cell along with the IBOulets connected to it:
The cell takes information from my backend and presents it. The description text view (just noticed that I accidentally named it descriptionLabel) doesn't allow scrolling so it expands based off of the content that it's holding. The database is being processed correctly from the database, and it's displaying on the app. The only problem is that the cell is not its correct height. On the TableViewControl that's registering this cell through its identifier, I automatically set the height of the cell using UITableViewAutomaticDimension in heightForRow:
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
but that's not working either. *The only thing that works is when I set the height of each cell to a standard value such as 200. That doesn't work for me because each cell will be a different height because of the length of the textView.
How do I set the height of a custom nib (tableViewCell) so that it adjusts based off of the content within it instead of setting the height to a specific value?
1-Remove both textViews and replace them with labels
2- Title lbl with theses constraints
top,leading,trailing , .lines = 0
3- Description lbl with theses constraints
bottom ,leading,trailing to contentView,top to bottom of title lbl , .lines = 0
Off course you can leave the 2 textviews , but you have to give each one an initial height and do this in
override func layoutSubviews() {
super.layoutSubviews()
self.titleTvHeight.constant = self.titleTv.contentSize.height
self.desTVheight.constant = self.desTv.contentSize.height
}
//
Don't forget to set this in viewDidLoad of the VC
tableView.estimatedRowHeight = 200
tableView.rowHeight = UITableViewAutomaticDimension
Anyway you can remove heightForRowAt

UICollectionView Cell with a thick border

I am attempting to create a round collection view cell.
In the cell I have a red UIView that is 0 from top, leading, trailing, and bottom. Inside of that cell I have another view which is white and it is 4 from the top, leading, trailing, and bottom. Within that view is a UILabel and a UIImageView.
The goal would be to have 2 cells per column and it is has a red ring around a white circle and text and an image.
To create the round UIViews I have an extension for UIView like this
extension UIView {
func createRoundView() {
layer.cornerRadius = frame.size.width/2
clipsToBounds = true
}
}
Inside of my cellForItemAt I say
cell.whiteBackgroundView.createRoundView()
cell.colorStatusView.createRoundView()
The goal is on the left, but what is happening is on the right.
Here is my Storyboard
The constraints are all blue, nothing red.
And to get the 2 cell per column I use this delegate method
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let padding: CGFloat = 25
let collectionCellSize = collectionView.frame.size.width - padding
return CGSize(width: collectionCellSize/2, height: collectionCellSize/2)
}
Any idea what I might be doing wrong?
In the end it doesn't matter if I have a view within a view. I change the ring or border color based on other data....so I just need to be able to easily change that border color.
This code is not enough, to tell what's going on there... try to set all
backgroundColors to .clear
then set those properties to your view
customView.layer.cornerRadius = half of your width
customView.layer.borderWidth = 5
customView.layer.borderColor = UIColor.red.cgColor
Using #blinkmeoff answer I figured it out mostly (although follow up question to come)
Inside the cell class I added the following
self.backgroundViewContainer.layoutIfNeeded()
self.backgroundViewContainer.layer.cornerRadius = min(backgroundViewContainer.frame.size.width, backgroundViewContainer.frame.size.height)/2
self.backgroundViewContainer.clipsToBounds = true
I kept getting weird shapes by just doing the /2 so I added the min(ba...
and that got me perfect circles

Making UITableViewCell a square based on screen width

I have a table view controller displaying square videos, and I want each table view cell to be a square as well. Is it possible to have each UITableViewCell dynamically resize according to the width of the screen (for example 320x320 for iPhone5 and 375x375 for iPhone6)?
In my view controller's viewDidLoad function I have:
frameWidth = self.view.frame.size.width
self.videosView.rowHeight = UITableViewAutomaticDimension
self.videosView.estimatedRowHeight = frameWidth
The constraints for the view that displays the video inside the UITableViewCell are:
Leading and trailing space to superview, top space to superview, 1:1 aspect ratio
I still can't get the cell to be a square though. Any help would be appreciated!
You can use this tableview method:
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UIScreen.main.bounds.width
}
Note: Method in Swift 3
There is a good reason that this is hard to do. Cells inside UITableView is reused so it can be allocated once. (for performance)
Resizing view is performance wise costly, that's way you only put one number as height of cell.
What you can do is to have if statement and return accordingly to phone version.
try to use it with bounds instead of frame
frameWidth = view.bounds.size.width

UITableView: automatically set height for the row and fix it with max height constant

I need automatically set the height for the row. I use
theTable.estimatedRowHeight = 60
theTable.rowHeight = UITableViewAutomaticDimension
but the height is not fixed. I need fixed height something like MAX 60 height, by default height is 40. With estimatedRowHeight the height is not fixed and when the string is long the cell takes all screen. How to fix it?
You will need to put all the content of table view cell in a single view (lets call it MainView) and set MainView top, leading and trailing edge constrains to container view. Also set a height constraint less than or equal to 60 with required priority. After that put your label in MainView set top, leading and trailing constrains to MainView and a bottom space to MainView constraint with high priority. Also on your label set number of lines to 0 and preferred width to explicit. This way if label content height is less than 60, MainView will shrink and that will still match height constraint of less than or equal to 60. Otherwise if content height is greater then 60 the bottom space to MainView constraint will break without causing problems since its priority is less then MainView height constraint.
Finally override tableview delegate methods as follow
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 40
}