I encountered with a problem when was trying to build a table view inside a cell of another table view. I thought it’s pretty straightforward task...
Structure of app:
Collection view with workout items.
After tapping on each, user will be moved to static table view, where problem’s occurred.
I noticed in Reveal app that table view appears in the cell and it doesn’t shout that constraints are not correct, everything seems fine. But table view has height of its frame 0.333.
Reveal app screenshot and the fact that table view exists
But user would see this:
I tried methods(with different values) as tough: estimatedHeightForRowAt, heightForRowAt, but they do nothing, by the way UITableView.automaticDimension returns -1.
But when I set explicitly height for the row for outermost table view everything is good, except the fact that the size will be different in distinct elements, and table view is too big, or too small. I made this constraint, but it seems work only for one case when element has 3 splits:
self.heightAnchor.constraint(equalToConstant: 40 + titleLabel.frame.height + CGFloat(splits.count) * 44.0)
With 3 splits in element
Less than 3
I read that adding subviews of custom cell to contentView instead of cell itself helped somebody, but in my case I got this message:
Warning once only: Detected a case where constraints ambiguously suggest a height of zero for a table view cell's content view. We're considering the collapse unintentional and using standard height instead. Cell: <projectClimber.SplitCell: 0x140670050; baseClass = UITableViewCell; frame = (0 0; 355 44); autoresize = W; layer = <CALayer: 0x600003797700>>
I was able to dispose of this warning actually by removing contentView and just add sub view to cell itself. But it doesn’t solve anything.
I don’t really know how to set proper value to table view's height, maybe there’s an approach without using table view or maybe in different way. I want to hear your opinion about this, thank you.
Code
Main table view
class WorkoutStatisticsTableViewController: UITableViewController {
....
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//...
//Switch statement and other cases
//...
case 3:
let cell = FourthTableViewCell()
cell.configure(with: workout)
cell.selectionStyle = .none
// Here I added
return cell
}
//I tried these methods, but nothing changed
// override func tableView(_ tableView: UITableView, estimatedHeightForRowAt
indexPath: IndexPath) -> CGFloat {
// return 250
// }
//
// override func tableView(_ tableView: UITableView, heightForRowAt indexPath:
IndexPath) -> CGFloat {
//
// return UITableView.automaticDimension
// }
....
}
Table view cell class, where lay title label and table view
class FourthTableViewCell: UITableViewCell, UITableViewDelegate, UITableViewDataSource {
//...
//Methods for tableview delegate and data source and implementation of title label
//...
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) ->
UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: SplitCell.reuseIdentifier, for: indexPath) as! SplitCell
cell.configure(cellWithNumber: indexPath.row + 1, with: splits[indexPath.row])
cell.selectionStyle = .none
return cell
}
//Number of splits, i.e. rows in table view in this cell
var splits: [Int] = []
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
self.tableView.dataSource = self
self.tableView.delegate = self
//just for making table view visible
self.tableView.backgroundColor = .orange
tableView.register(SplitCell.self, forCellReuseIdentifier: SplitCell.reuseIdentifier)
addSubview(titleLabel)
addSubview(tableView)
titleLabel.translatesAutoresizingMaskIntoConstraints = false
tableView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
titleLabel.topAnchor.constraint(equalTo: topAnchor, constant: 20),
titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20),
titleLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20),
tableView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 10),
tableView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 10),
tableView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -10),
tableView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -10),
// Tried this line for calculating height of cell
// self.heightAnchor.constraint(equalToConstant: 40 + titleLabel.frame.height + CGFloat(splits.count) * 44.0)
])
}
//Configure cell with workout element, which is used for taking splits
func configure(with workout: Workout) {
print("Configure func, workout's splits \(workout.splits.count)")
//Need to populate splits this way, because in workout I use List<Int>
for item in workout.splits {
self.splits.append(item)
}
}
}
Split cell class. Where is located only two labels
class SplitCell: UITableViewCell {
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
addSubview(titleLabel)
addSubview(timeLabel)
titleLabel.translatesAutoresizingMaskIntoConstraints = false
timeLabel.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
titleLabel.topAnchor.constraint(equalTo: topAnchor, constant: 10),
titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 15),
titleLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -10),
timeLabel.topAnchor.constraint(equalTo: topAnchor, constant: 10),
timeLabel.leadingAnchor.constraint(equalTo: titleLabel.trailingAnchor, constant: 50),
timeLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -10)
])
}
func configure(cellWithNumber number: Int, with time: Int) {
titleLabel.text = "Split #\(number)"
timeLabel.text = String.makeTimeString(seconds: time)
}
}
After dozen of tries I decided to use this constraint for table view, that is located inside a cell of static table view:
tableView.heightAnchor.constraint(equalToConstant: CGFloat(splits.count) * 27)
But there's still a problem - magic value. 27 is a sum of 17, which is a height of label and 10 is a sum of constant of bottom and top constraints of label in an inner table view's cell.
I'm convinced there's a better solution somewhere, but for now it's better than nothing.
I am building a project without storyboard. Everything is working fine but I can't seem to figure why I can't add tableView programmatically. I have tried the same code for adding tableView in another empty project and its working fine but inside my project the tableview is not showing up. My view hierarchy is like below.
I have a BaseClass like this:
class BaseController: UIViewController{
override func viewDidLoad() {
setupViews()
}
func setupViews(){
}
}
Then I have firstViewController class inherited from Base Class:
class firstViewController: BaseController
Inside my firstController, I am declaring and initializing my tableView:
var tableView:UITableView = {
let tbl = UITableView()
tbl.translatesAutoresizingMaskIntoConstraints = false
tbl.backgroundColor = .blue
return tbl
}()
Then I am overriding setupView() inside firstViewController here like below:
override setupView() {
view.addSubview(tableView)
NSLayoutConstraint.activate([
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0),
tableView.trailingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0),
tableView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0)
])
}
It should show empty tableView cells but it is not showing up. I guess there is something to do with the base and derive class thing but I can't figure out the exact problem.
You need to set trailing constraint for tableview correctly.
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0)
You were setting view.leading to tableview.trailing that makes your tableview invisible from current view.
I am trying to get my UITableView to be positioned within the safe area but it doesn't seem to be working and I do not know why. I trying to do this programatically.
class MenuTableViewController: UITableViewController{
var margin: UILayoutGuide!
var tableDataSource: [userFolderObject]!
let cellId = "cellId"
override func viewDidLoad() {
super.viewDidLoad()
setup()
}
private func setup(){
margin = view.layoutMarginsGuide
tableDataSource = MockData.UITableDateSource
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.topAnchor.constraint(equalTo: margin.topAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: margin.bottomAnchor).isActive = true
tableView.leadingAnchor.constraint(equalTo: margin.leadingAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: margin.trailingAnchor).isActive = true
/* I have also tried the below code
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
tableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
*/
tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellId)
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let label = UILabel()
label.backgroundColor = UIColor.lightGray
if section == 0{
label.text = "Search PubMed"
}else{
label.text = "My Folders"
}
return label
}
}
First, remove all of your setup code that attempts to mess with the margins of the table view. That is all done for you by default in a UITableViewController.
Since your issue is only with the layout of your custom section header views, you need to fix how you have implemented those views.
Like cells, you should use reusable header/footer views and your header/footer view should extend UITableViewHeaderFooterView. This will ensure proper margins by defaults and it already provides a standard textLabel you can set. No need to create your own UILabel.
As shown in the documentation for UITableViewHeaderFooterView you should register a class. Then in viewForHeader you should dequeque the header view and then set its textLabel as needed.
If you don't actually need anything but a plain old section label, then don't implement viewForHeader. Instead, implement titleForHeader. Much simpler.
I have a tableView:
tableView = UITableView()
view.addSubview(tableView)
tableView.translatesAutoresizingMaskIntoConstraints = false
let constraints = [tableView.topAnchor.constraint(equalTo: view.topAnchor), tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), tableView.heightAnchor.constraint(equalToConstant: view.frame.height * 0.6)]
NSLayoutConstraint.activate(constraints)
And my searchController looks like:
func setupSearchController() {
searchController = UISearchController(searchResultsController: nil)
searchController.searchResultsUpdater = self
searchController.obscuresBackgroundDuringPresentation = false
searchController.searchBar.placeholder = "Search for a candy"
navigationItem.searchController = searchController
definesPresentationContext = true
}
When it loads for the first time everything is fine. But when I tap on the search bar to type there, my search bar moves to the top, navigation bar becomes smaller and it breaks my layout and white space appears between tableView and searchBar:
How can I improve my constraints to move up and down my tableView when the navigation bar sizes are changed?
You have to change your top constraint of the tableview.
Try replacing this 2 constraint
tableView.topAnchor.constraint(equalTo: searchController.searchBar.bottomAnchor),
tableView.topAnchor.constraint(equalTo: view.topAnchor)
With this
tableView.topAnchor.constraint(equalTo: view.topAnchor)
The automatic content inset adjustment will do the rest.
Note also that in your table view constraints list you doesn't have a leading/horizontal position constraint. I suggest to add also this constraint
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor)
When my MSMessagesAppViewController changes the presentationStyle from compact to expanded and back to compact, my UITableView is messed up regarding its scrolling.
I am using AutoLayout to setup a UITableView inside of a View called contentView.
// Inside MSMessagesAppViewController
func createTableView() {
let tableViewController = MyTableViewController()
self.addChildViewController(tableViewController)
tableViewController.tableView.backgroundColor = UIColor.clear
tableViewController.tableView.translatesAutoresizingMaskIntoConstraints = false
self.contentView.addSubview(tableViewController.tableView)
NSLayoutConstraint.activate([
tableViewController.tableView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 0),
tableViewController.tableView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 0),
tableViewController.tableView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: 0),
tableViewController.tableView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 0)
])
}
After expanding and collapsing the MSMessagesAppViewController, I can suddenly scroll past my last cell in the tableView...
Everything is working perfectly fine in the initial state. I noticed, that the scrollbar is visible on startup, but is not present after the size change...
This is how I setup my tableView in the controller:
// Inside MyTableViewController
func setupTableView() {
tableView.dataSource = self
tableView.register(QuickStandardTableViewCell.self, forCellReuseIdentifier: "standardCell")
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 64
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.separatorColor = .clear
tableView.sectionHeaderHeight = UITableViewAutomaticDimension;
tableView.estimatedSectionHeaderHeight = 8.0
self.extendedLayoutIncludesOpaqueBars = false
}
What I checked:
The contentView frame changes correctly
The tableView frame changes correctly
The contentInset does not change
The contentSize does not change
Does anybody know what I am missing?
Thanks in advance!
Try using a UIViewController with a UITableView inside instead of using UITableViewController.
I had the same problem with an UICollectionViewController and switching to UIViewController solved this problem.