Resizing UIStackView after changing UILabel number of lines - ios

My iOS app has a tableview with one UITableViewCell with the following layout (1 stackview containing 2 labels and 1 button)
When the user taps the button the number of lines of the central label goes from 0 to 2 and will look like this:
Now there are two problems here:
1) Resizing the UIStackView
2) Resizing the cell
I have found a not-optimal solution for problem 1, which consists of adding an empty view in the stack. (invalidateIntrinsicContentSize was not working).
let emptyView = UIView()
emptyView.frame = CGRect(x: 0, y: 0, width: 0, height: 0)
stackView.addArrangedSubview(emptyView)
As you can see in the second screenshot the cell doesn't resize and I'm not sure if this is due to the stackview or the cell itself.
I would like to point the fact that I'm writing code inside the UITableViewCell subclass as the button event is handled inside it.
For the records The Tableview is using dynamic sizing:
// dynamic cell sizing
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return CGFloat(cellEstimatedHeight)
}

UIStackView may or may not be your best bet... it's designed more for arranging views inside its own frame, rather than adjusting its own frame to the views.
For a layout as simple as you have there, it would probably be much better to just lay out the elements with normal constraints.
Take a look at this example: https://github.com/DonMag/DynamicCellHeight
Table B is one way of accomplishing your layout (Table A was for another layout I played around with).
I had to do a little trickery to get the main label to stay in place while the cell resized... could probably find a better way. (Re-Edit - yep, found a better way)
Let me know if it makes sense - no doubt you'll want to do some tweaking.

Related

How do I stop editable cells to be pushed to the left? Or how do I get the width of the editable marks?

If you make a tableVIew cell editable so that you can rearrange the order of the cells these hold-and-drag marks appear in the cells:
Great, but the downside is that they push the whole cell to the left, so the auto layout that is supposed to put things in the middle doesn't work the way I intend. Labels that I want to be in the middle are now a bit to the left. Even if I constrain things to the cell itself I get the same result since it appears that the whole cell is pushed.
Is there a way to get the width of the edit-marks (not sure about the technical term for it) so that I can take that into account when setting up the auto layout? Or is there another way to get around this?
Not widely published as far as I know. You could try setting the layout margins within the cell to offset the displacement from the reorder view?
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.layoutMargins = UIEdgeInsets(top: 0, left: 40, bottom: 0, right: 10)
return cell
}
40 seems to be the magic number but you could amend with a bit of trial and error.

How to show an image view only if the model object contains image url

So I am creating a facebook feed of sorts and I am fetching text, url for images/videos and username etc. The cell I have created has an image view, text view and label. I am sizing the cell according to the text view content size.
Now lets say the user has only posted text and there no image with it. I want the cell to be sized so that there is no image view in the cell. Only the text view and thats it.
Can this be done using just one cell/model/adapter or do I need custom cells and show them according to the model.
A little light on how this could be done would be appreciated.
You just need one cell and there are different ways of achieving this. A simple oen is using a UIStackView inside the cell.
When one element of the UIStackView is hidden, it adapts it size. If you place constraints from to the stack view to the cell, the cell with be smaller/larger depending if you show the image. You just have to work with constraints.
Also, using UIStackView or not, remember to specify on your tableView that the size of the cell will be dynamic by doing:
self.tableView.rowHeight = UITableViewAutomaticDimension
Create a outlet of image height constraint, and then check if your URL is blank or invalid then make height constraint constant 0 otherwise give it appropriate height.
And make sure you have implemented both the methods :
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 100; // Put estimated height
}
And another one is,
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}

Resize iOS tableviewcell contaning several views

I'm looking for a way to resize a tableView cell with a more complex layout.
To simplify the description of the problem the tableview cell consists of three views. One view that defines the "background" of the cell and two additional views (each of them with a height 45% of the background cell.
One of these additional views is tagged to the top of the background view the other one to be bottom.
If the user taps on the tableview cell it should shrink in a way that only the top view is visible and after an additional user tap it should resize to its full size again.
The user tap is handled by the function
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
and the resizing is done by
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
Unfortunately, after shrinking the tableview cell, both addition views are displayed with half of their original hight.
var selectedCell = -1
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
tableView.deselectRow(at: indexPath, animated: true)
if (selectedCell != indexPath.row)
{
selectedCell = indexPath.row
}
else {
selectedCell = -1
}
tableView.beginUpdates()
tableView.endUpdates()
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if (indexPath.row == selectedCell)
{
return 65
}
else {
return UITableView.automaticDimension
}
}
I'm now looking for a way to change the code in a way, that after shrinking only the upper view is visible.
Example picture of the fully visible cell
In the example picture, after shrinking the tableview cell should only display the red view.
Thanks in advance
Patrick
The reason is that when the cell is shrunk, your views (since their sizes are defined relative to parent's size) also gets shrunk and are visible with half size. They are not really gone/hidden from the view. What you need is to hide/remove them from the view.
It looks from your description that you are using plain constraints to achieve this. This can be done by just using constraints but its a lot more work. So I will mention two ways to get this done:
Using just constraints
When the user taps your cell, you need to make the height of the bottom view 0. Also, if you do not want the middle 10% part to show when the cell shrinks, you would need to create an additional constraint from the bottom of the top view to the bottom of the contentView of the cell. Similarly, your bottom view will also have two height constraints, one for 45% and one for 0.
The idea is to have both these constraints at the same time but with different priorities. The initial constraint will have higher priority and the other one will have a lower priority. Although these constraints will be contradictory, iOS will take the higher priority one to render the view.
Next, you would need to toggle the active property of the higher priority constraint on user tap which will in turn let iOS use the lower priority constraint to render the view.
Use stackview:
iOS 9 introduced stack view which is a helper view. It basically, handles constraints for you, at least some part of it. When you hide one view from the stack view's children, stack view will automatically make the height of that view to be 0. read up more about vertical stack view and you will get the idea.

Why UITableViewCell shrinks contentView?

Here is the UITableView that I want to make:
In order to achieve this, I divided contentView of cell into three parts.
1 - fromView: contains start time and class type. Height will be 30% of contentView
2 - infoView: contains clock image, class name and professor's name. Height will be 40% of contentView.
3 - toView: contains end time and location. Height will be space that left(30%).
Firstly, before going to details, I decided to show this containers only. For understanding a problem, I painted them(green, red, blue respectively).
After adding them into the contentView, here are the constraints I gave to those containers:
fromView.easy.layout(Top(), Left(), Right(), Height(*0.3).like(contentView))
infoView.easy.layout(Top().to(fromView), Left(), Right(), Height(*0.4).like(contentView))
toView.easy.layout(Top().to(infoView), Left(), Right(), Bottom())
Seems like everything is right. After launching, I thought that I will see them, but here is what happened:
I thought that cell does not know what size it needs to be, so I decided to implement following function:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 150
}
After this, UITableView showed containers pretty correctly than before. But, I plan to add some UILabels and other views into that containers(look at first picture). In other words, I don't know what size a cell needs to have. It needs to be kind of dynamic. Anyway, I tried to add those labels into the containers, maybe after that, a cell will figure out the the height. Here is how I did it:
func setupViews() {
fromView.addSubviews(fromTime, classType)
toView.addSubviews(toTime, room)
infoView.addSubviews(clockImage, teacherName, subjectName)
contentView.addSubviews(toView, fromView, infoView)
}
func setupConstraints() {
fromView.easy.layout(Top(), Left(), Right(), Height(*0.3).like(contentView))
infoView.easy.layout(Top().to(fromView), Left(), Right(), Height(*0.4).like(contentView))
toView.easy.layout(Top().to(infoView), Left(), Right(), Bottom())
fromTime.easy.layout(CenterY(), Left(8))
fromTime.setContentHuggingPriority(.required, for: .horizontal)
fromTime.setContentCompressionResistancePriority(.required, for: .horizontal)
classType.easy.layout(CenterY(), Left(8).to(fromTime))
clockImage.easy.layout(CenterY(), Left(16), Width(24), Height(24))
subjectName.easy.layout(CenterY(-8), Left().to(classType, .left), Right())
teacherName.easy.layout(Top(8).to(subjectName), Left().to(subjectName, .left), Right())
toTime.easy.layout(CenterY(), Left(8))
toTime.setContentHuggingPriority(.required, for: .horizontal)
toTime.setContentCompressionResistancePriority(.required, for: .horizontal)
room.easy.layout(CenterY(), Left(8).to(toTime))
}
But anyway, problem appeared again like this:
I think, the problem is that I give height to containers according to height of contentView, but contentView does not know its height. But I also don't know what height it needs to have, cause it needs to depend according to size of labels. So, how to solve this problem?
Don't set heights for the containers. You have to make sure the top and bottom of the cell are connected through constraints. My recommendation is to just get rid of the containers, add all the labels and images to the cell directly, and create constraints like this.
Then set tableView.rowHeight = UITableView.automaticDimension, or return UITableView.automaticDimension from tableView(_:heightForRowAt:)
Try by giving height constraint to your main view inside your cell.
Look like what you want is a dynamic height calculated based on the content's AutoLayout. Update the delegates to return the UITableViewAutomaticDimension for the heightForRowAt and some good estimation, e.g., 150 for the estimatedHeightForRowAt:
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 150
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
Then you can just set the content's size using autolayout, and the table will calculate the height of the cells for you dynamically.
Estimated height is used for scrolling purposes - a good estimate will provide you a good scrolling experience (dynamic height is calculated only when the cell is being displayed, thus for the yet undisplayed cells the tableView will use estimation when scrolling).

Add UIView Above All Section in UITableView Swift

i am not talking about a section i want put a uiview on the above of every section
This is the layout
here Menu and LeftOver are uiView
Staters and new are sections
What you're looking for is a section header. You can easily configure your own custom view by implementing the UITableViewDataSource method
For example:
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView()
// configure view, note the method gives you the section to help this process
return headerView
}
you may also want to implement this function
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 50 // or whatever it is
}
Do keep in mind that header are sticky be default
in the past I've gotten around this fairly easily doing something like this:
let dummyViewHeight = CGFloat(49) // height of header (set in storyboard)
self.tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: self.tableView.bounds.size.width, height: dummyViewHeight))
self.tableView.contentInset = UIEdgeInsets(top: -dummyViewHeight, left: 0, bottom: 0, right: 0)
which basically just makes the table header "stick" to this inset on top of the table. (unless you really do want it hovering over the section at the top)
Edit:
Given the OP considers this "not satisfying" I'm like 99% confident when I say the ONLY other reasonable option (if you don't wan't to use section header or footer views) is to then make a new UITableViewCell subclass. Then that new subclass would appears at the top of each section.
So in the cellForIndexPath: method you'd check if indexPath.row == 0and if so dequeue a cell with a different identifier. That way the header just becomes a cell that is built differently.
If you're doing it programmatically just be sure to register that cell (tableView.register(MyNewHeaderCellSubclass.self, forCellReuseIdentifier: "MyNewHeaderCellSubclass")) or otherwise go to the table view on the storyboard and toggle the option for two types of dynamic cell classes.
The section header is exactly what this use case is for however. I cant think of any other solution without just abandoning UITableView completely which I think would be a big mistake. Good luck to you otherwise if so.

Resources