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).
Related
I'm making an iOS app which uses a table, and each cell contains two labels and a custom view I made called ColourCircle. ColourCircle has a property called fill: UIColor and I've overridden the draw(_ rect: CGRect) function to draw a circle of that colour.
The cells' data are loaded from an array: data: [(String, Double)], using the view controller as both the UITableViewDelegate and UITableViewDataSource.
In viewWillAppear I've written:
guard let fetchedData = getArtistData() else {
performSegue(withIdentifier: "data-error", sender: nil)
return
}
chart.load(data: fetchedData)
data = fetchedData
table.reloadData()
table.setNeedsLayout()
table.isHidden = false
chart.isHidden = false
activityIndicator.stopAnimating()
And, in tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) I've written:
let cell = table.dequeueReusableCell(withIdentifier: "artist cell") as! ArtistCell
let name = data[indexPath.row].0
if let proportion = chart.getPercentage(of: name) {
let percentage = proportion * 100
cell.name.text = name
cell.percentage.text = "\(round(percentage * 10) / 10)%" // rounded to 2 d.p.
cell.colour.fill = chart.colour(for: name)!
}
return cell
When I run my app, the visible cells (i.e. which are on the screen) work perfectly, and the ColourCircle views display the correct colour. When I scroll down, bringing new cells into view, the new cells don't have the correct colour, but instead use the colours from the previously loaded cells, in order.
For example, say the first two cells in the table are red and blue, then the first two which load when I scroll down will also be red and blue, in that order. The same thing happens when I scroll up, re-loading the previous cells.
When I select these cells, however, they change to the correct colour. Then, when unloaded and loaded again, they will stay the correct colour unless I've fixed one of the cells further up (by selecting them), in which case they will change to the colours of the ones I fixed.
It seems like it's something to do with reusing the cells, although I can't quite work out how to fix it.
EDIT: Here's the code for my ArtistCell class:
class ArtistCell: UITableViewCell {
#IBOutlet weak var colour: ColourCircle!
#IBOutlet weak var name: UILabel!
#IBOutlet weak var percentage: UILabel!
}
according this: let cell = table.dequeueReusableCell(withIdentifier: "artist cell") as! ArtistCell - all cells are reusable. Use prepareForReuse() method, to prepare cell to reuse and clean up they content.
I am experiencing a very strange phenomenon. I am using the AutomaticDimension function on my tableview and for some odd reason it works on all of my cells except for 3. All of my cells have the exact same constraints which is the weird part. I have tried changing the constraints on the cells but it appears that they are being ignored. Any insight would be great. I make sure to call the auto dimension function in viewdidload.
override func viewDidLoad() {
super.viewDidLoad()
self.TableView.dataSource = self
TableView.estimatedRowHeight = 200.0
TableView.rowHeight = UITableViewAutomaticDimension
}
Here is one of the classes that is not working:
class foursleepCell: UITableViewCell {
#IBOutlet weak var foursleepLabel: UILabel!
#IBOutlet weak var foursleepScale: UISegmentedControl!
var delegate: foursleepCellToController?
#IBAction func foursleepValueChanged(_ sender: UISegmentedControl) {
if let delegate = delegate {
let indexPath = delegate.indexOfChangedfoursleepCell(at: self)
delegate.savefoursleepCellResponse(at: indexPath, response: foursleepScale.selectedSegmentIndex)
}
}
}
Attached are images of constraints and output:
Label Constraints
Segmented Control Constraints
Actual Output
Working Output
I don't think there is anything wrong with the way you are trying to set the cell to automatically resize, it is just the fact the the top-to-bottom constraints that are required for that resizing to happen are not present at runtime. Apparently this is something that can happen sometimes in Xcode - try creating a new table view cell and setting up all the views and constraints again from scratch. Copying and pasting the table view cell into a new cell and working from the copy did not fix it for me - it behaved exactly the same. But setting it up fresh did the trick.
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
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
Here's an interesting problem I'm trying to solve.
I want to use a single cell that is configured in storyboard for different cell configurations by removing stuff I don't need from the cell.
Here's how to cell is constrained.
Basically, if I don't need image for the step (or the red view) I just remove it from cell, and everything aligns dynamically just perfect, and I do not have to calculate cell height or do any coding. However, since I have to reuse the cells, when I get the cell for the next row I do not have that view. I can add it again to cell, but I loses all of the constraints.
In case you need it for ideas, here's some code:
class RecipeStepTableViewCell: UITableViewCell {
#IBOutlet weak var preparationLabel: UILabel!
#IBOutlet weak var stepNumberLabel: UILabel!
#IBOutlet weak var stepImageView: UIImageView!
#IBOutlet weak var stepTextLabel: UILabel!
#IBOutlet weak var stepTimerView: UIView!
override func awakeFromNib() {
super.awakeFromNib()
}
}
And this is how I handle it it cellForRowAtIndexPath:
let cell = tableView.dequeueReusableCellWithIdentifier("RecipeStepCell", forIndexPath: indexPath) as RecipeStepTableViewCell
let step = stepForIndexPath(indexPath)
if step.image == nil {
cell.stepImageView.removeFromSuperview()
}
// etc.
I really like this approach since I don't have to build 10 cell configurations, and all I need is to force it to create a new cell for each row.
By the way, the view will only have 5, 6 cells so performance shouldn't be an issue.
It seems like removing the image is deleting all the constraints associated with it.
I have an idea. Instead of removing the image, you can just adjust constraints when step.image == nil, since the image will be invisible anyway.
This would be done by using storyboard to connect IBOutlets to the constraints. It would look like this:
#IBOutlet weak var constraintToTop: NSLayoutConstraint!
And then later to adjust it,
let cell = tableView.dequeueReusableCellWithIdentifier("RecipeStepCell", forIndexPath: indexPath) as RecipeStepTableViewCell
let step = stepForIndexPath(indexPath)
if step.image == nil { //Set constraint(s) so imageView takes up no space
cell.constraintToTop.constant = 0
}
else { //Go back to original constraints
cell.constraintToTop.constant = 50
}
This may require creating many IBOutlets for your constraints to get the desired result, but this is the general idea.
I hope this helps you.