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.
Related
Why is it that when I set tableView.rowHeight = 100 in viewDidLoad() I always get the default height value of 44.0? I tried setting tableView.estimatedHeight =100 as well but no luck, I tried setting the delegate method tableView.heightForRowAt as well but that doesn't seem to have any effect as well what so ever. So the question is: how do you set the height for a tableView Cell?
override func viewDidLoad(){ // tableView viewDidLoad
super.viewDidLoad()
tableView.rowHeight = 100
tableView.register(TickerCell.self, forCellReuseIdentifier: "Cell")
}
// Custom Cell Init
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
backgroundColor = .clear
print(frame.height) // always prints 44.0
let symbolstack = UIStackView(arrangedSubviews: [symbolLabel,companyLabel])
let sectorstack = UIStackView(arrangedSubviews: [sectorLabel,exchangeLabel])
let mainStack = UIStackView(arrangedSubviews: [symbolstack,sectorstack])
sectorstack.axis = .vertical
symbolstack.axis = .vertical
sectorstack.alignment = .trailing
symbolstack.alignment = .leading
symbolstack.distribution = .fillEqually
sectorstack.distribution = .fillEqually
mainStack.distribution = .fillProportionally
addSubview(mainStack)
mainStack.translatesAutoresizingMaskIntoConstraints = false
mainStack.heightAnchor.constraint(equalTo: self.heightAnchor).isActive = true
mainStack.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -45).isActive = true
mainStack.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 15).isActive = true
addSubview(WatchlistStar)
WatchlistStar.translatesAutoresizingMaskIntoConstraints = false
WatchlistStar.widthAnchor.constraint(equalToConstant: 15).isActive = true
WatchlistStar.leadingAnchor.constraint(equalTo: mainStack.trailingAnchor, constant: 20).isActive = true
WatchlistStar.heightAnchor.constraint(equalToConstant: 15).isActive = true
WatchlistStar.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
}
So the problem appears to be in the fact then in the cell init method the rowHeight I set In viewDidLoad does not seem to register, only in prepareForReuse when I print frame.height do I get 100 printed,So in which method do I setup the cell layout then?
A cell is not born knowing its height, so init is a pointless place to look at the frame. In fact, it has no inherent height. Cells are reused. They take on a height only in relation to a particular row of the table where they are being used right at the moment. That height can change when the cell is used in a different row (because rows can be different heights). So your layout needs to cope with that.
In the code you have shown, you are using autolayout. The whole point of autolayout is that you don't care about the frame of things at any one moment. Everything adjusts automatically as the surrounding frame changes. Autolayout is about relationships.
So the solution in that case is: don't look at frame.height. You don't need to know it. Just lay out the relationships between the views and the cell, and everything will be correct when the cell appears, if you have used autolayout correctly.
On the other hand, as you now say in a comment "Im forced to [use autolayout] not because I want to" — okay, so if the goal is to do layout manually, like we did before there was autolayout, then the place to do it is in the data source's cellForRowAt:. Or you could try doing it in the cell's layoutSubviews if you want the cell to lay itself out. See the old edition of my book, online, for how we used to do this: http://www.apeth.com/iOSBook/ch21.html#_custom_cells
Be very careful to distinguish between adding subviews and resizing them. You don't want to make the mistake of adding subviews that you have already added. So add the subviews in init, sure, as it is called only once, but size them in a place where the actual size of the row has been communicated to the cell.
One more piece of advice. Your current code uses the phrase addSubview, meaning self.addSubview. That is totally wrong and illegal. Never never add a subview directly to a cell. Add it only to the cell's contentView, and size it in relation to that.
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.rowHeight = UITableView.automaticDimension;
self.tableView.estimatedRowHeight = 40
}
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.
In my app I have a collection view with cells autosizing horizontally.
Here's some code:
// called in viewDidLoad()
private func setupCollectionView() {
let cellNib = UINib(nibName: SomeCell.nibName, bundle: nil)
collectionView.register(cellNib, forCellWithReuseIdentifier: SomeCell.reuseIdentifier)
guard let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else { return }
flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
flowLayout.itemSize = UICollectionViewFlowLayout.automaticSize
}
The cell has 1 view, which has constraint for heigth. This view subviews a label, which is limited with 2 rows and is not limited by width. The idea here is to allow label to calculate its own width fitting text in 2 rows.
In order to make this work I've added the following code to the cell class:
override func awakeFromNib() {
super.awakeFromNib()
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
contentView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
contentView.topAnchor.constraint(equalTo: topAnchor).isActive = true
contentView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
}
Now, it works perfectly. Cells are autosizng, scrollView is scrolling horizontally etc. Until I call reloadData() at least once. Then cells have size 50x50 and never autosize any more until I leave the screen and come back again.
Do you have any ideas on why is it happening?
There's this large titles feature in iOS 11 that shows large title when the UITableViewController's table is scrolled to top, and gets collapsed to standard small title when the user scrolls the table away from top. This is standard behavior. I need the navigation controller to behave a bit differently - I need to always show the large title. How to achieve this?
Following code does not help, it still collapses when scrolled.
navigationController?.navigationBar.prefersLargeTitles = true
navigationItem.largeTitleDisplayMode = .always
I've achieved it unintentionally when embedded UITableViewController inside UIViewController.
I'm not sure whether it is an Apple's bug or intended behavior.
So stack is as simple as UINavigationController -> UIViewController(used as container) -> UITableViewController
Here is sample of view controller with embedded UITableViewController fullscreen
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var vc = UITableViewController(style: .plain)
var array: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
vc.view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(vc.view)
view.addConstraint(view.leadingAnchor.constraint(equalTo: vc.view.leadingAnchor))
view.addConstraint(view.rightAnchor.constraint(equalTo: vc.view.rightAnchor))
view.addConstraint(view.safeAreaLayoutGuide.topAnchor.constraint(equalTo: vc.view.topAnchor))
view.addConstraint(view.bottomAnchor.constraint(equalTo: vc.view.bottomAnchor))
vc.tableView.delegate = self
vc.tableView.dataSource = self
array = "0123456789".characters.map(String.init)
vc.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "identifier")
title = "Title"
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return array.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "identifier", for: indexPath)
cell.textLabel?.text = array[indexPath.row]
return cell
}
}
Here is the result
Hope it helps.
P.S. Surprisingly, my current problem is that I don't know how to get collapsing behavior with such architecture :)
What I did was to add another view between navigationBar and TableView with a height of 1.
let tableViewSeperator: UIView = {
let view = UIView()
// remove the color, so it wont be visible.
view.backgroundColor = UIColor.systemBlue
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
One thing which is important is add this seperator view as a subview of your viewcontroller's view before tableView, otherwise it won't work
view.addSubview(tableViewSeperator)
view.addSubview(tableView)
or if you want to save one line of code, you can also do it like this.
[tableViewSeperator, tableView].forEach({view.addSubview($0)})
Then set its constraints like this.
tableViewSeperator.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
tableViewSeperator.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive = true
tableViewSeperator.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
tableViewSeperator.heightAnchor.constraint(equalToConstant: 1).isActive = true
The last thing is change the tableView TopAnchor to be the BottomAnchor of sperator View.
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive = true
tableView.topAnchor.constraint(equalTo: tableViewSeperator.bottomAnchor, constant: 0).isActive = true
tableView.bottomAnchor.constraint(equalTo: createItemBtn.topAnchor, constant: 0).isActive = true
Now when you scroll the the NavigationBar will stay as Large.
You need to add UIView(it's can be width=0, height=0) before add UITableView.
example
Then this code will work
navigationController?.navigationBar.prefersLargeTitles = true
navigationItem.largeTitleDisplayMode = .always
I have a filterbar, which is a UIView as well as a tableView in my controller like so:
override func viewWillLayoutSubviews() {
sortSalonsByDistanceAndReload()
}
func setupViews() {
view.addSubview(filterBar)
view.addSubview(tableView)
filterBar.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
filterBar.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
filterBar.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
filterBarHeightLC = filterBar.heightAnchor.constraint(equalToConstant: 44)
filterBarHeightLC?.isActive = true
tableView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
tableView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
tableView.topAnchor.constraint(equalTo: filterBar.bottomAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
I want to make the filterBar dissapear when the user scrolls down on the tableView, and then reappear when the user scrolls up
You can use Search Controller. Take a look at How to implement UISearchController in UITableView - SWIFT
if it doesn't work out for you , you could add as a section or part of header view
The delegate of your tableView should implement the UIScrollViewDelegate method scrollViewDidScroll: and query the contentOffset.y property on the scrollView (your tableView). Then it depends on when you want the bar to appear: only on the top, or also whenever the user scroll up inside the tableView. You could sync the contentOffset.y difference with the filterBars top constraint's constant (ranging from -44 to 0). It might be better to inset your tableView and update that inset as well (from 0 to 44).
If your filterBar could be implemented with a navigation bar from a UINavigationController you might just do navigationController?.hidesBarsOnSwipe = true, but I don't know your view controller setup.