I have a tableview controller, that I am designing using Storyboard. The prototype cell is very basic, it just has two labels. A name label, and a date label.
When I run my program, the style is there (font, etc.), but it seems like the constraints aren't working? Also, the cell color doesn't show (only the tableview background shows), and the cell dividers are missing. The labels overlap each other, and when I set the cell height to 75, it doesn't appear that tall.
This is what my cell looks like compiled:
See tableview here
I made a model for the table view cell as shown here (IdeaCell is properly set on the prototype cell in storyboard):
class IdeaCell: UITableViewCell {
#IBOutlet weak var ideaNameLbl: UILabel!
#IBOutlet weak var ideaDateLbl: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
if selected {
self.layer.backgroundColor = UIColor(white: 1, alpha: 0.2).cgColor
} else {
self.layer.backgroundColor = UIColor.clear.cgColor
}
}
func configureCell(idea : CoreIdea) {
let name = idea.name
ideaNameLbl.text = name
let date = idea.date
ideaDateLbl.text = date
}
}
And my tableview view controller relevant code is here (reuse identifier (ideaCell) is properly set on the prototype cell in storyboard):
class IdeaVC: UITableViewController {
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "ideaCell", for: indexPath) as? IdeaCell {
let idea = ideas[indexPath.row]
cell.configureCell(idea: idea)
return cell
} else {
return UITableViewCell()
}
}
}
UPDATE:
After adding the heightForRowAt function, my cells are the proper height, and constraints are working as they should be. For some reason the cell background color is missing, and the cell divider is not appearing (the font, and font color work). They were all set in the storyboard.
Here is a screenshot of my cells:
missing cell style
UPDATE 2:
I set the divider and background like this:
self.tableView.backgroundColor = UIColor.black
self.tableview.separatorColor = UIColor.white
Hope this helps someone! I honestly don't know why my values from storyboard didn't work, but setting them in the viewDidLoad worked great.
Have you told your tableview the height of the tableviewcell? If not try this. Its one of your tableviews delegate methods.
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 75.0 //Set your cell height that you want to appear in your tableview
}
i think you must override the number of Item , and height for row at function. and set the constraint on UITableViewCell between name and date label.
Related
I have a UITableView which has a UITableViewCell which contains a UIImageView.
The constraints are setup such that the UIImageView has padding 20 points at the top and sides, and a size ratio of 1:1, so the UIImageView will always be square regardless of the device width.
I apply a cornerRadius to the UIImageView so the image is circular.
However.... the autolayout doesn't seem to work on the first load. But after the first load, it works perfectly.
I have tried every known combination of setNeedsLayout or layoutIfNeeded - both inside the UITableViewCell and in the UITableView code. Nothing works the first time it loads.
Please help!
Code looks like this:
class CircularProfileCell: UITableViewCell {
#IBOutlet weak var circularView: UIView!
func setup() {
circularView.layer.cornerRadius = circularView.bounds.height / 2
}
}
class CircularProfileVC: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.separatorStyle = .none
self.tableView.register(UINib(nibName: "CircularProfileCell", bundle: nil), forCellReuseIdentifier: "CircularProfileCell")
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CircularProfileCell", for: indexPath) as! CircularProfileCell
cell.setup()
return cell
}
}
Setup looks like this:
Because corner radius is a layer property it does not always play well with auto layout. In addition, I guess you set it up with frame properties of the view (i.e imageView.layer.cornerRadius = imageView.bounds.height/2).
Hence you should try and set the corner radius on the layoutSubviews() function of the cell. This will make sure to render the correct size
override func layoutSubviews() {
super.layoutSubviews()
imageView.layer.cornerRadius = imageView.bounds.height/2
...
}
This only happens when the tableView.separatorStyle = .none
So to fix it I simply leave the separator on, but set the separator color to clear
self.tableView.separatorStyle = .singleLine
self.tableView.separatorColor = UIColor.clear
Thanks to #CloudBalacing for the help. More info about this problem here
Let's say I have hierarchy like this:
*TableViewCell
**TableView
***TableViewCell
and all of them should be resizable. Did someone face this kind of problem? In past I've used many workarounds like systemLayoutSizeFitting or precalculation of height in heightForRowAt, but it always breaks some constraints, because TableViewCell has height constraint equal to estimated row height and there appear some kinds of magic behavior. Any ways to make this live?
Current workaround:
class SportCenterReviewsTableCell: UITableViewCell, MVVMView {
var tableView: SelfSizedTableView = {
let view = SelfSizedTableView(frame: .zero)
view.clipsToBounds = true
view.tableFooterView = UIView()
view.separatorStyle = .none
view.isScrollEnabled = false
view.showsVerticalScrollIndicator = false
view.estimatedRowHeight = 0
if #available(iOS 11.0, *) {
view.contentInsetAdjustmentBehavior = .never
} else {
// Fallback on earlier versions
}
return view
}()
private func markup() {
contentView.addSubview(tableView)
tableView.delegate = self
tableView.dataSource = self
tableView.register(ReviewsTableViewCell.self, forCellReuseIdentifier: "Cell")
tableView.snp.makeConstraints() { make in
make.top.equalTo(seeAllButton.snp.bottom).offset(12)
make.left.equalTo(contentView.snp.left)
make.right.equalTo(contentView.snp.right)
make.bottom.lessThanOrEqualTo(contentView.snp.bottom)
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! ReviewsTableViewCell
cell.viewModel = viewModel.cellViewModels[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! ReviewsTableViewCell
cell.viewModel = viewModel.cellViewModels[indexPath.row]
cell.setNeedsLayout()
cell.layoutIfNeeded()
let size = cell.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize, withHorizontalFittingPriority: .defaultHigh, verticalFittingPriority: .defaultLow)
return size.height
}
}
Self sizing tableView class:
class SelfSizedTableView: UITableView {
override func reloadData() {
super.reloadData()
self.invalidateIntrinsicContentSize()
self.layoutIfNeeded()
}
override var intrinsicContentSize: CGSize {
self.setNeedsLayout()
self.layoutIfNeeded()
return contentSize
}
}
This is actually not an answer to the question, but just an explanation.
(Wrote here because of the character count limitation for the comments).
The thing is that you're trying to insert a vertically scrollable view inside another vertically scrollable view. If you don't disable the nested tableview's scroll ability, you will have a glitch while scrolling, because the system wouldn't know to whom pass the scroll event (to the nested tableview, or to the parent tableview).
So in our case, you'll have to disable the "scrollable" property for the nested tableviews, hence you'll have to set the height of the nested tableview to be equal to its content size. But this way you will lose the advantages of tableview (i.e. cell reusing advantage) and it will be the same as using an actual UIScrollView. But, on the other hand, as you'll have to set the height to be equal to its content size, then there is no reason to use UIScrollView at all, you can add your nested cells to a UIStackView, and you tableview will have this hierarchy:
*TableView
**TableViewCell
***StackView
****Items
****Items
****Items
****Items
But again, the right solution is using multi-sectional tableview. Let your cells be section headers of the tableview, and let inner cells be the rows of the tableview.
here is an example of how to make a tableview inside a table view cell with automatic height for the cells.
You should use the 'ContentSizedTableView' class for the inner tableViews.
class ViewController: UIViewController {
#IBOutlet weak var outerTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
outerTableView.rowHeight = UITableView.automaticDimension
outerTableView.estimatedRowHeight = UITableView.automaticDimension
outerTableView.delegate = self
outerTableView.dataSource = self
}
}
final class ContentSizedTableView: UITableView {
override var contentSize:CGSize {
didSet {
invalidateIntrinsicContentSize()
}
}
override var intrinsicContentSize: CGSize {
layoutIfNeeded()
sizeToFit()
return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height)
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? TableTableViewCell
return cell!
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
}
Use xib files to simplify the hierarchy.
Get a tableView on your storyboard, and create a nib file for your tableViewCell(say CustomTableViewCell). Inside it create a tableView and again create one more tableViewCell xib file. Now, no need of setting labels into your xib file,(if you want only labels in cells and nothing else, if not, there is another way of adding constraints)
Say you have an array of text, some strings are long and some are short.
register nib file in CustomTableViewCell and extend it to use Delegate and DataSource.
register this CustomTableViewCell in ViewController.
While declaring a cell in CustomTableViewCell, just do=
cell.textLabel?.text = content
cell.textLabel?.numberOfLines = 0
Use heightForRowAt to set outer tableViewCell's height, and let the inner tableView to scroll inside.
The Issue
I am attempting to use a collection view in a view controller for cards. When a user taps on a card, it expands. At all times, I have a tableview in each card, whether it is expanded or not. I have the data loading in the table views, but only when I tap on a card to expand it or if I scroll collection view cards offscreen and back onscreen. What is a cleaner workflow to doing this that puts tableviews in each collection view cell?
This is in my main view controller:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "productionBinderCell", for: indexPath) as? ProductionBinderCollectionViewCell
let suck = cell?.detailsTableView.cellForRow(at: IndexPath(row: 0, section: 0)) as? DescriptionTableViewCell
suck?.descriptionText.text = productions[indexPath.row].productionDescription
cell?.detailsTableView.reloadData()
cell?.layer.shouldRasterize = true
cell?.layer.rasterizationScale = UIScreen.main.scale
let title = productions[indexPath.row].productionTitle
let luck = productions[indexPath.row].productionImage
let zuck = UIImage(data:luck!)
cell?.productionTitle.text = title
cell?.productionFeaturedImage.image = zuck
cell?.productionColorTint.tintColor = UIColor.blue
let backbtn = cell?.viewWithTag(1)
backbtn?.isHidden = true
return cell!
}
This is in a subclass of the tableview:
override func layoutSubviews() {
super.layoutSubviews()
self.dataSource = self
self.delegate = self
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "productionDescription", for: indexPath) as? DescriptionTableViewCell
return cell!
}
This is in a subclass of the tableview cell I am concerned with. It only shows the uilabel text when scrolling offscreen or tapping on the collection view cell:
class DescriptionTableViewCell: UITableViewCell {
#IBOutlet var descriptionText: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Maybe I shouldn't be using a tableview at all? Thank you very much to all who help. I am very open to criticism and love learning from my mistakes (a little too often). :)
What you need, first of all, is clear definition of views and cells.
UICollectionview is a collection of UICollectionViewCell objects
UITableView is a collection of UITableViewCell objects
Do you need to embed entire table view within each card? There you have your answer. You have parent-child relationship between your UICollectionViewCell and UITableView derived classes:
Derive from UICollectionViewCell - MyCollectionViewCell - this step is mandatory.
Inside xib for MyCollectionViewCell, insert a tableview, with horizontal scrolling enabled. (this is an option inside xib attribute editor)
If needed to customize, also derive from UITableViewCell - MyTableViewCell. This step is optional.
Inside MyCollectionViewCell class file, have implementation for table view datasource and delegate methods. That way, each card will have all its table view cell children in one place
If present, define custom logic inside MyTableViewCell class file
There is no need to subclass UITableView here. Everything that needs to be customized can be done using subclassing cells.
I created a custom UITableViewCell with textfield, but I would like to use the imageview of cell, instead insert one in my custom cell.
This is my code:
import UIKit
class TextFieldTableViewCell: UITableViewCell, UITextFieldDelegate {
#IBOutlet weak var textField : UITextField!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
This is the xib
In View Controller:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell : TextFieldTableViewCell! = tableView.dequeueReusableCell(withIdentifier: "textFieldCell", for: indexPath) as! TextFieldTableViewCell
cell!.textField.text = "William Stevenson"
cell!.textField.tag = indexPath.row
cell!.imageView?.image = UIImage.init(icon: .FAUser, size: CGSize(width: 35, height: 35), textColor : UIColor.gray)
return cell
}
}
But the textfield goes under the picture
I try insert imageView in my custom cell
but the first separator line has a different width
How can I fix the problem?
Thank you!
The reason for having separator lines with two different widths is, when you put imageView outside of custom cell initially, it's on top of the image has covered the separator line. When you put imageview inside the separator is visible. Looks like images are having white background so that they are masking separator line. If my guess is correct, please check your icons background. If it's white in colour change them to transparent. Otherwise give some padding between imageview and cell bottom
I solved using my imageview ad set "Separator", in IB of my xib, with "Custom insets" left value equals to 65.
I put a UICollectionView into the UITableViewCell by following this tutorial and in my UICollectionViewCell, there's a Image View. So when I run my app, the collection view is not resizing itself at the same time in my cell I put a Text View which is resizing itself according to content, see the below images:
In this first image, I have a text view at the top which have some text in it, and below it with (pink background) is my collection view and inside of that with greenBackground is my image view, as you can see that collection view is taking the extra space instead of reducing itself as Text View Did.
in this second image you can see that my textView haves more content then before so its resized itself now overlapping the CollectionView
this is my TableViewCell:
class TableViewCell: UITableViewCell {
#IBOutlet var txtView: UITextView!
#IBOutlet private weak var collectionView: UICollectionView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
// collectionView.frame = self.bounds;
// collectionView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func setCollectionViewDataSourceDelegate
<D: protocol<UICollectionViewDataSource, UICollectionViewDelegate>>
(dataSourceDelegate: D, forRow row: Int) {
collectionView.delegate = dataSourceDelegate
collectionView.dataSource = dataSourceDelegate
collectionView.tag = row
collectionView.reloadData()
}
var collectionViewOffset: CGFloat {
get {
return collectionView.contentOffset.x
}
set {
collectionView.contentOffset.x = newValue
}
}
}
this is my collectionViewCell
class CollectionViewCell: UICollectionViewCell {
#IBOutlet var imgView: UIImageView!
override func awakeFromNib() {
self.setNeedsLayout()
//
// self.contentView.frame = self.bounds;
// self.contentView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
}
}
and this is my TableviewController
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return imageModel.count
}
override func tableView(tableView: UITableView,
cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell",
forIndexPath: indexPath) as! TableViewCell
cell.txtView.text = txtArray[indexPath.row]
return cell
}
override func tableView(tableView: UITableView,
willDisplayCell cell: UITableViewCell,
forRowAtIndexPath indexPath: NSIndexPath) {
guard let tableViewCell = cell as? TableViewCell else { return }
tableViewCell.setCollectionViewDataSourceDelegate(self, forRow: indexPath.row)
tableViewCell.collectionViewOffset = storedOffsets[indexPath.row] ?? 0
}
override func tableView(tableView: UITableView,
didEndDisplayingCell cell: UITableViewCell,
forRowAtIndexPath indexPath: NSIndexPath) {
guard let tableViewCell = cell as? TableViewCell else { return }
storedOffsets[indexPath.row] = tableViewCell.collectionViewOffset
}
}
extension TableViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
return imageModel[collectionView.tag].count
}
func collectionView(collectionView: UICollectionView,
cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell",
forIndexPath: indexPath) as! CollectionViewCell
cell.imgView.frame = CGRectMake(0, 0, collectionView.frame.width, collectionView.frame.height)
cell.imgView.contentMode = .ScaleAspectFit
//cell.addSubview(imageView)
cell.imgView.image = ResizeImage(UIImage(named: imageModel[collectionView.tag][indexPath.item])!, targetSize: CGSizeMake( cell.imgView.frame.width , cell.imgView.frame.height))
//imageView.image = UIImage(named: imageModel[collectionView.tag][indexPath.item])
return cell
}
}
How can I make this collection view to AutoLayout itself according to the content in it? I also tried this:
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 100;
but didn't worked (my collection view got disappear) if anybody knows how to do this, then please guide me..
I faced a similar issue when i used collection view inside a table view cell. No matter what i did i couldn't get the table view to resize automatically but the collection view did. Soo instead of autolayout i did it using code.
I ended up having to calculate the size of the label in the collection view numberOfSections in collection view and passing this height using a delegate to the view controller that has the tableView's delegate and dataSource and reloading the appropriate row of the table view.
As it happens, the numberOfSections in collectionview data source gets called everytime and the delegate resizes the table view height.
Some thing like this-
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{
[self.delegate setViewHeight:[self getHeightForCellAtIndexPath:[NSIndexPath indexPathForRow:currentSelected inSection:0]]];
return 1;
}
This ought to give you a general idea.
EDIT: Sorry i misunderstood, your question before. Here is something that should work for you:
As per my understanding, you have a table view cell with a label view and collection view inside of it.
Now, inside your table view cell, you should add top, leading and trailing constraints space to the label. Now inside your collection view position your image vertically in the center and add an appropriate top and bottom to the cell superview. Your collection view itself should have a CONSTANT value in leading, trailing, top to label and bottom to superview. Also add a fixed height constraint to your collection View (assuming you want the image sizes to remain the same).
Now lets says View Controller A is the data source for your table view and the table view cell is the data source for your collection view.
In your viewController A, you should write your height for row at indexPath as-
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
CGSize stringSize = [yourStringArray[indexPath.row] boundingRectWithSize:CGSizeMake(_yourCollectionView.frame.size.width, FLT_MAX)
options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
attributes:#{NSFontAttributeName:[UIFont yourFont size:yourFontSize]} context:nil].
return stringSize.height + 35; //magic number 35, play around with it to suit your need. Did this to always have a minimum fixed height.
}
This will allow your tableViewRowForHeight for that particular index to have the height of your label added to it and the constraints ought to do the rest.