I am building a chat application using UITableView in Swift 4 programatically . This table view have dynamic cell height. So when I am insert a new row to table view and call
self.m_ChatTable.scrollToRow(at: lastIndex, at: .top, animated: true)
not working properly. For example when I am inserting two new rows to table view the 1st row become visible after inserting the 2nd row and 2nd row become visible after inserting 3rd row . The scrolling height in tableView not correct.
Code
override func viewDidLoad() {
super.viewDidLoad()
m_ChatTable.rowHeight = UITableViewAutomaticDimension
m_ChatTable.separatorStyle = .none
m_ChatTable.delegate = self
m_ChatTable.dataSource = self
m_MainScrollView.addSubview(m_ChatTable)
m_ChatTable.reloadData()
// Do any additional setup after loading the view.
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
{
let msg:Message = m_MessageList[indexPath.row] as Message
return msg.height + 10
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat
{
return 50 // for example
}
func numberOfSections(in tableView: UITableView) -> Int
{
return 1
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
let msg = m_MessageList[indexPath.row]
if (0 != msg.height)
{
return msg.height + 10
}
else
{
return UITableViewAutomaticDimension
}
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let height = cell.frame.height
m_MessageList[indexPath.row].height = height
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return self.m_MessageList.count
}
Related
Interface:
Interface in debugger:
Here is configuration of tableview
private func configureTableView(){
view.addSubview(tableView)
tableView.delegate = self
tableView.dataSource = self
tableView.separatorStyle = .none
// tableView.rowHeight = UITableView.automaticDimension
tableView.register(DishCell.self, forCellReuseIdentifier: "dishCell")
// tableView.sectionHeaderHeight = UITableView.automaticDimension
}
Here is tableView extensions
extension MenuViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.section == 0 {
return 380
}
return 48
}
func tableView(_ tableView: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat {
return 122
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return UITableView.automaticDimension
}
}
extension MenuViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return viewModel.dishes.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.dishes[section].count
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let dishType = viewModel.dishTypes[section]
if section == 0 {
let restaurant = viewModel.restaurant
return FirstMenuHeader(restaurant: restaurant, dishType: dishType)
}else{
return DefaultMenuHeader(dishType: dishType)
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "dishCell") as! DishCell
let dish = viewModel.dishes[indexPath.section][indexPath.row]
cell.set(dish: dish)
return cell
}
}
Just to clarify DishCell, FirstMenuHeader and DefaultMenuHeader look well if you put them outside of UITableview. And they have constraints that define their height. However their height is dynamic and depends on amount of line of text.
Problem was in dishCell.I made constraints with view, while I had to make constraints with contentView.
I have rewrote my constraints and problem was resolved.
I created UITableViewController to show reviews, shown in below picture.
It shows the layout correctly for the first build like below.
Correct Layout
But when I close the app and re-open it, It shows the layout like below.
Wrong Layout
I am using Xcode 12.0 and I tried on iOS 12,iOS 13 and iOS 14 same problem occurs.
Here is my code for the app: (It's a Table View Controller)
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func addTapped(_ sender: Any) {
self.present(AlertService().ReviewAlert(), animated: true, completion: nil)
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
tableView.layoutIfNeeded()
}
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 5
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ReviewCell", for: indexPath) as! reviewCell
// Configure the cell...
cell.ratingSetup(rating: (indexPath.row + 1))
cell.reviewLabel.text = String(repeating: "Lorem Ipsum ", count: ((indexPath.row + 1) * 5))
if indexPath.row == 1 || indexPath.row == 3 { cell.markedAsRead.isHidden = true }
cell.sizeToFit()
cell.reviewLabel.numberOfLines = 0
cell.layoutIfNeeded()
return cell
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return CGFloat(100)
} }
Here is my Table View Cell Content as well.
Remove methods:
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
tableView.layoutIfNeeded()
}
...
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return CGFloat(100)
} }
Remove:
cell.sizeToFit()
cell.reviewLabel.numberOfLines = 0
cell.layoutIfNeeded()
Set numberOfLines right in the storyboard for the label.
Check cell size field in the storyboard to be empty.
Check if you have all constraints - try to make the cell longer in the storyboard and check how label is rendered there (add long text to check if new lines are added).
Check if when launched you have no constraint warnings, if you have, then find "label to bottom" constraint and set priority from 1000 to 999.
I don't know why it happened but found a solution.
I added all other view to a Header.xib file and used basic cell type for review text.
Then It worked. Probably Cell can't handle auto dimension when there is bunch of stuff going on the prototype cell.
I am creating a static tableView. My goal is to have a single view that displays a tableView with 3 rows (get support, send feedback, participation terms), and a header + footer.
Right now, it all works fine EXCEPT the fact that there are two extra separators (one between the header and the first cell and the other between the last cell and the footer) that I cannot seem to get rid of.
Here is my code:
final class viewController: UITableViewController {
private let headerContainer = UIView()
private let footerContainer = UIView()
private let tableData = ["Get Support", "Send Feedback", "Participation Terms"]
override func viewDidLoad() {
super.viewDidLoad()
setupHeaderAndFooter()
setupTableView()
}
func setupHeaderAndFooter() {
/* setup code here (not relevant to this question) */
func setupTableView() {
// reinitializing tableView so that we can change its style to grouped
tableView = UITableView(frame: CGRect.zero, style: .grouped)
tableView.delegate = self
tableView.separatorStyle = .singleLine
}
//MARK: UITableView Methods
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
return headerContainer
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 150
}
override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
return footerContainer
}
override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 200
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tableData.count
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 70
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "cell")
cell.textLabel!.text = tableData[indexPath.row]
return cell
}
}
One way to achieve this is don't use default separator provided by UITableView. and place one UILabel or UIView and set its background color to light gray and set its height=1 (As per your requirement), width (full tableview width or as per your requirement) and position at bottom of all you content inside cell, header or footer.
Ok here you go.
The easy trick is actually a one-liner and a self-explanatory:
self.tableView.separatorColor = self.tableView.backgroundColor
AFTER:
I hope this helps!
You have to set clipsToBounds to true on your header view.
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = //Get your header here
header.clipsToBounds = true
return header
}
Before:
After:
My structure is the following:
UIViewController -> UIVIew -> UITableView
tableView(_ tableView: UITableView, numberOfRowsInSection section: Int)
and
tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
is called but
tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
is not called.
class ViewcontrollerA : UIViewController , UITableViewDelegate , UITableViewDataSource {
override func viewDidLoad() {
super.viewDidLoad()
self.doctortableView.delegate = self
self.doctortableView.dataSource = self
self.doctortableView.allowsSelection = true
}
func callTable() {
doctortableView.frame = CGRect(......)
view1.addSubview(doctortableView)
doctortableView.isUserInteractionEnabled = true
self.view.addSubview(view1)
}
// Tableview Functions
// Number of Sections in Table
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var count = Int()
return count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = UITableViewCell()
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//This function is not called
}
}
The problem seems to be in this datasource method at first glance.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var count = Int()
return count
}
Doesn't Int() return 0? That means your tableview doesn't have any cells.
As per discussion on Chat
You are creating TableView programatically and add to self.view
func callTable() {
doctortableView.frame = CGRect(......)
view1.addSubview(doctortableView)
doctortableView.isUserInteractionEnabled = true
self.view.addSubview(view1)
}
but your tableView is not on top, so you are not able to scroll it and that's why delegate are not working
following line is missing
self.view.bringSubview(toFront: doctortableView)
Here is Full code
func callTable() {
doctortableView.frame = CGRect(......)
view1.addSubview(doctortableView)
doctortableView.isUserInteractionEnabled = true
self.view.addSubview(view1)
self.view.bringSubview(toFront: doctortableView)
}
how to set background color of the cells which are selected on section tableview
every section or genre should have single selected cell only enter image description here
SEE Image description PLZ
I know how to select single cell in table view but here I wanted for every section only one select cell
this my code
var sections = [
Sections(genre: "🦁 Animation",
movies: ["The Lion King", "The Incredibles"],
expanded: false),
Sections(genre: "💥 Superhero",
movies: ["Guardians of the Galaxy", "The Flash", "The Avengers", "The Dark Knight"],
expanded: false),
Sections(genre:"👻 Horror",
movies: ["The Walking Dead", "Insidious", "Conjuring"],
expanded: false)
]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sections[section].movies.count
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 44
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if(sections[indexPath.section].expanded){
return 44
}else{
return 0
}
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 2
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = ExpandableHeaderView()
header.customInit(title: sections[section].genre, section: section, delegate: self)
return header
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "labelCell")!
cell.textLabel?.text = sections[indexPath.section].movies[indexPath.row]
return cell
}
////////////
func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, didHighlightRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)
//let cell = tableView.dequeueReusableCell(withIdentifier: "labelCell")!
cell?.contentView.backgroundColor = UIColor.red
//cell?.backgroundColor = UIColor.red
}
func tableView(_ tableView: UITableView, didUnhighlightRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)
//let cell = tableView.dequeueReusableCell(withIdentifier: "labelCell")!
cell?.contentView.backgroundColor = UIColor.white
//cell?.backgroundColor = UIColor.blue
}
////////////
func toggleSection(header: ExpandableHeaderView, section: Int) {
sections[section].expanded = !sections[section].expanded
tableView.beginUpdates()
for i in 0 ..< sections[section].movies.count{
tableView.reloadRows(at: [IndexPath(row: i , section: section)], with: .automatic)
}
tableView.endUpdates()
}
What you can do is a complete use of the delegate functions provided by the table view:
Inside func tableView(UITableView, didSelectRowAt: IndexPath) you can check the section and row that has been selected. You will also need to check a constant check on the var indexPathsForSelectedRows: [IndexPath]? array that this returns.
Now that you have a. the current selection b. the list of seelections,
you can go ahead and delete/add data into your final array as required.
So in your case, inside func tableView(UITableView, didSelectRowAt: IndexPath) you will check if the indexPathsForSelectedRows has the section+row count already. If it does, then simply play with the highlighting in the tableView: UITableView, didHighlightRowAt indexPath: IndexPath) and then change the array that stores the list of selected movies accordingly. If not simply add it.