Resize UITableViewCell based on content availability in response - ios

I have a self sizing UITableViewCell that displays blog posts.
A post consists of text and an image. The challenge I now have is that some posts do not contain an image.
How do I size the cell using Auto Layout? Perhaps should I set the UIImageView.isHidden = true instead?
Thanks!

Considering you said you are using auto resizing cells, following are the steps you should follow:
1) First subclass your tableview cell with custom UITableViewCell class
2) Take an outlet for UIImageView in above class as:
#IBOutlet weak var myImageView: UIImageView
3) Set the auto layout constraints to this image view from storyboard as leading, trailing, top and height constraint
4) Take outlet for height constraint of imageview in the same UITableViewCell class:
#IBOutlet weak var heightConstraint: NSLayoutConstraint!
5) In your cellForRow method, check if your data contains image or not. If no image is present, then change the value of height constraint:
if myImage == nil {
if myCustomCell.heightConstraint != nil {
myCustomCell.heightConstraint.constant = 0
myCustomCell.updateConstraintsIfNeeded()
}
}
This should work as expected. If not, please post the constraints you have applied.

Related

Swift UITableViewCell fails to setup its constraint after cellForRowAtIndexPath

In my tableViewController i use a custom cell which contains an imageView, two labels and some other irrelevant elements. Also there's a constraint, of which the constant value shall be changed, if a certain condition is given. This works fine on the first sight but if i scroll down and get cells where the constraint constant is set to another value the some of the following cells kinda keep this constant from this previous cell and don't set it up at appearing.
This is the relevant part of my cellForRowAtIndexpath:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let identifier = whichCellToShow ? "ThisCell" : "TheOtherCellCell"
//...
let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath)
if let customCell = cell as? CustomCellProtocol,
indexPath.section < data.count,
indexPath.row < data[indexPath.section].count {
customCell.display(data: data[indexPath.section][indexPath.row])
}
return cell
}
These are the relevant parts of my custom cell class:
class CustomCell: UITableViewCell, CustomCellProtocol {
#IBOutlet weak var picture: UIImageView!
#IBOutlet weak var title: UILabel!
#IBOutlet weak var weblink: UILabel!
#IBOutlet weak var titleIndentationConstraint: NSLayoutConstraint!
//...
func display(data: Data) {
//...
title.text = data.title
//...
weblink.text = data.hasWeblink ? URLUtilities.hostOfURL(urlstr: data.sourceUrl) : nil
weblink.isHidden = !data.hasWeblink
//This is the spot where things seem to go wrong
titleIndentationConstraint.constant = data.hasWeblink ? weblink.frame.height : 0
setNeedsLayout()
layoutIfNeeded()
}
}
If data.hasWeblink == true the constraint constant shall be equal to the height of the weblink label, if it's false it shall be 0.
My first thought was, that the view recycling of UITableView could collide with titleIndentationConstraint.constant = data.hasWeblink ? weblink.frame.height : 0. That's why i called setNeedsLayout()and layoutIfNeeded()straight afterwards, but this doesn't seem to help. All the other elements of the cell do their job correctly.
Also i'm setting tableView.rowHeightand tableView.estimatedRowHeightinside the viewDidLoad() of my tableViewController.
Does anybody have an idea what in particular is going wrong there or what i forgot to do? Thanks in forward!
Fixed it myself, had to change some constraint priorities of the title label:
My title label has a bottom constraint which is always greater or equal than 8 pts. Also i limited the number of lines to 4 and told the label to truncate the text's tail, if it's too long to be displayed. Funnily always, if the text was truncated, i got this silly behavior described above, so it has absolutely nothing to do with my titleIndetantionConstraint. Seems like if the text has to be truncated the label aligned itself to the bottom constraint and kinda dragged the content to center left (so watch what content mode says in the property editor, it should be left by default). While changing the label's content mode seems like a bad idea to me, i lowered the priority of the title label bottom constraint by 1 so it aligns itself to the top of the picture, like i planned. Hopefully this helps people how have similar issues B).

Using a Custom xib in UISTackView

I'm trying to make my life easier by only building one xib to reuse rather than building three views to put into a stackView
I have created my custom xib file and connected it its own custom class:
The labels, and images are placed by constraints. I want the labels to be able to auto resize, up to three lines, so the height constraints for the labels are >=, as well as the Content View which also can scale in height (>=)
The UserReview code:
class UserReview: UIView {
#IBOutlet weak var userImage: UIImageView!
#IBOutlet weak var username: UILabel!
#IBOutlet weak var userRating: UILabel!
#IBOutlet weak var reviewText: UILabel!
#IBOutlet weak var prosText: UILabel!
#IBOutlet weak var consText: UILabel!
class func instanceFromNib() -> UIView {
return UINib(nibName: "UserReview", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! UIView
}
func setUp(review: Review){
// TODO set userImage
username.text = review.username!
// TODO add rating + color
reviewText.text = review.content!
if let pros = review.positive_points {
prosText.text = pros
} else {
prosText.text = "No Positive Points"
}
if let cons = review.negative_points {
consText.text = cons
} else {
consText.text = "No Negative Points"
}
}
}
The StackView where i am trying to add subViews to also has a height constraint of >= 200 (min one card) but does not seem to resize with more cards added. The Alignment is Fill and Distribution: Equal Spacing
I have tried playing around with the constraints and alignment properties for the StackView containing reviews but the closest i can come to is overlapping Views..
The way i create and add views to the stack view:
let review1 = UserReview.instanceFromNib() as! UserReview
let review2 = UserReview.instanceFromNib() as! UserReview
let review3 = UserReview.instanceFromNib() as! UserReview
self.reviewStack.addArrangedSubview(review1)
self.reviewStack.addArrangedSubview(review2)
self.reviewStack.addArrangedSubview(review3)
From what i understand from this stack question
The StackView has issues with the view height?
I have tried most of the suggested solutions but encountering different issues..
The result i am trying to get:
(The images are gone for some reason, will check that later.)
First of all remove height constraint from UIStackView because StackView always expands it's height based on the views inside it.
Set all the 4 sides constraints of StackView to it's superview and set intrinsic size as place holder.
Now add your UIView to stack view in the same manner you are doing it now. Do not set height constraint to any UIView label and the UIView itself. Just make sure yo have set top constraint of top most element and bottom constraint of bottom element. This ways auto layout understand how to increase height of all the elements based on their constraints and content.
For detailed understanding you can follow my answer at https://stackoverflow.com/a/57954517/3339966

Make a UIView relayout itself when dependent constraint changes

I have a UIImageView and UIButton, UIButton is aligned to top of UIImageView. Now in code I am changing the top of UIImageView but the UIButton is not updated accordingly. I tried SetNeedsLayout, LayoutIfNeeded on UIButton but nothing works.
override func viewDidLayoutSubviews() {
profileImageView.frame.origin.y = arcView.bounds.height/1.8
editProfileButton.layer.setNeedsDisplay()
}
I have set the constraints in Storyboard. I would really appreciate any help here.
First: Are you sure that all your constraints (for both the UIImageview and UIButton) are added right?
Second: when working the constraints, you should change the origin.y of the UIImageview also by by a constraint, by modifying its constant's value:
Instead of directly changing profileImageView.frame.origin.y, you should change the constant of the constraint that tells what's the imageview origin.y (if the first point is applied, this constraint must be exist...); Add this constraint to the viewController as an IBOutlet and change value of its constant property (take a look at the comments in the code snippet, it's a part of the answer):
class ViewController: UIViewController {
// let's assume that this is the #IBOutlet of the constraint, I called it 'imageViewTopMargin':
#IBOutlet weak var imageViewTopMargin: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
// it's not necessary -for sure- to do this the in the viewDidLoad method
// but I'm doing this for demo purposes:
// let's say that you want to push the imageView to the bottom with extra 40 points:
imageViewTopMargin.constant += 40
}
}
Hope this helped.

Can UITableView scroll with UICollectionView inside it?

I have the structures below...
I wrap two of collection views into tableview
One is in tableview header(Collection1), another is in tableview 1st row(Collection2).
All the functions are good (Both collection view).
just...
When I scroll up in Collection2, Collection1 will Not scroll up together, because I'm only scrolling the collectionViews not the tableview.
It only scroll together when I scroll in Collection1.
Is it possible to make the header view scroll with user just like app store's index carousel header?
Or I just went to the wrong place, I should use other ways to approach.
Solution
When you keep CollectionView1 as a TableViewHeader, CollectionView1 will always on the top of TableView after it reaches top. If you want Collection1 and Collection2 scroll up together, you need to keep CollectionView1 in a cell, not a header.
Make sure CollectionView2 content height smaller or equal to TableViewCell's height. As I checked on App Store, they always make SubCollectionView content height equal to TableViewCell's height (If they use TableView).
Result
For more detail, you can take a look at my sample project
https://github.com/trungducc/stackoverflow/tree/app-store-header
Problem
1) Your tableview cell Collectionview (let's say collection2) , Collection 2 is scrollable. So when you scroll up tableview won't scroll up
Solution
1) Just simple and working solution would be height constant , You have to give height constant to the collection2 with >= relationship and 0 Constant value and 750 priority !!
Now the question is how to use this
You need to take IBOutlet of Height constant to your custom tableview cell and need to manage the collectionview height from there.
Here is example
class FooTableViewCell: UITableViewCell {
static let singleCellHeight = 88;
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var descriptionLabel: UILabel!
#IBOutlet weak var iconsCollectionView: IconsCollectionView!
#IBOutlet weak var const_Height_CollectionView: NSLayoutConstraint!
var delegateCollection : TableViewDelegate?
var bars:[Bar] = [] {
didSet {
self.iconsCollectionView.reloadData()
iconsCollectionView.setNeedsLayout()
self.layoutIfNeeded()
let items:CGFloat = CGFloat(bars.count + 1)
let value = (items / 3.0).rounded(.awayFromZero)
const_Height_CollectionView.constant = CGFloat((iconsCollectionView.collectionViewLayout as! UICollectionViewFlowLayout).itemSize.height * value)
self.layoutIfNeeded()
}
}
override func awakeFromNib() {
iconsCollectionView.translatesAutoresizingMaskIntoConstraints = false
iconsCollectionView.initFlowLayout(superviewWidth: self.frame.width)
iconsCollectionView.setNeedsLayout()
iconsCollectionView.dataSource = self
iconsCollectionView.delegate = self
const_Height_CollectionView.constant = iconsCollectionView.contentSize.height
self.layoutIfNeeded()
self.setNeedsLayout()
}
}
You can check my answer Making UITableView with embedded UICollectionView using UITableViewAutomaticDimension Very similar and 100% working
Another solution You can also take UICollectionView with sections which contain another horizontal collectionview , with this solution you don't need to manage contentsize for every cell
Hope it is helpful

How to change subview of uitableviewcell programmatically?

I'm working on app that allows user to share photo like instagram. User can post a photo with or without caption, so the caption could be empty and the photo still posted to their feed. My client want to remove the view when the caption is empty and I'm using uitableview cell with autolayout for the post. When I tried to connect the captionview height constraint to my viewcontroller it gives me an error it says that the constraint outlet can't be connected to repeated content. So, I tried to do it inside my code , but it doesn't change anything.
let captionString = object["caption"] as? String
let captionView = cell.contentView.viewWithTag(111)
if captionString == ""{
captionView?.frame = CGRectMake(0 , 0, captionView!.frame.width, 0)
}else{
(cell.contentView.viewWithTag(12) as! UILabel).text = captionString
}
I put above code inside tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell and I'm giving constraint in every view inside uitableviewcell so if I can change the captionView height other view will automatically resizing. How can I change the height of the captionView?
Related info here:
Outlets cannot be connected to repeating content iOS
You can't set the frame, when you're using autolayout that's invalid and won't work properly.
Either have 2 different styles of cell and choose which one to dequeue based on data availability, or add constraints so that you can collapse the label height to zero.
you can create outlet of height constraint and set the height 0
like as:
#IBOutlet var captionViewHeightConstraint: NSLayoutConstraint!
captionViewHeightConstraint.constant = 0

Resources