Add a button at the end of tableView - ios

I'm trying to add a button at the bottom of a table view to handle a loadMore() function.
What I tried to do is to programmatically define a button inside a custom cell and try to append it when the numberOfRows is superior than the tableView items count. It results in a fatal error "Index out of range".
Here is what I'm trying to achieve :
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return itemArray.count + 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let loadCell = itemList.dequeueReusableCell(withIdentifier: "loadMore", for: indexPath) as! LoadMore
let cell = itemList.dequeueReusableCell(withIdentifier: "normalCell", for: indexPath) as! ItemBox
if indexPath.row > itemArray.count {
return loadCell
}
return cell
}
I don't understand why I get this error as I specified one more row in the numberOfRowsInSection func.

Related

Expand the only tapped cell and collapse other cells

I am doing expand/collapse when tapped on tableview cell, but I have to close all other cells except the tapped one. Tried this solution Expand only the cell that has been tapped this solution is not working for me.
below code which I have written for expand/collapse
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return datasource.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView .dequeueReusableCell(withIdentifier: String(describing: ExpandingTableViewCell.self), for: indexPath) as! ExpandingTableViewCell
cell.set(content: datasource[indexPath.row])
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let content = datasource[indexPath.row]
content.expanded = !content.expanded
tableView.reloadRows(at: [indexPath], with: .automatic)
}
You need to collapse all cells and change the current clicked one state , then reload all the table
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let current = datasource[indexPath.row].expanded
datasource.forEach { $0.expanded = false }
let content = datasource[indexPath.row]
content.expanded = !current
tableView.reloadData()
}

how to move a table view cell from one table view to a new table view

Currently, I am trying to move a tableview cell from one table view to another. I can't seem to get the proper mechanics down and need help with this task.
Right now I have an array that is not filled with any goals for my progress table view cells.
var goals: [String] = []
Here is the setup for the rest of this progress table view.
extension ViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return goals[section].count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TodayGoalViewCell_1", for: indexPath) as? GoalTableViewCell
cell?.goalLabel.text = goals[indexPath.section][indexPath.row]
cell?.cellDelegate = self
cell?.index = indexPath
return cell!
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sections[section]
}
func numberOfSections(in tableView: UITableView) -> Int {
return goals.count
}
In a separate file I have another table view that is already filled with goals. Here is the code:
var goals = ["goal 1", "goal 2", "goal 3"]
extension GoalsViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Goals.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "GoalConversationsCell_1", for: indexPath)
cell.textLabel?.text = Goals[indexPath.row]
cell.textLabel?.lineBreakMode = NSLineBreakMode.byWordWrapping
cell.textLabel?.numberOfLines = 3
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.section == 0 {
Goals.remove(at: indexPath.row)
Goals.count != 0 {
showGoalSelected()
} else {
Goals.append(contentsOf: theEmptyModel)
}
tableView.reloadData()
}
}
}
I would like to make it so that when a user selects a goal from the table view which already had goals, that these goals are moved to the progress table view. How would I do this?
If there is a button inside your cell and you want to get news when this button is clicked, you need to look at the protocol-delegate pattern.
But you can also try to remove the button inside the cell and use a text label. To use the didSelectRowAt method to catch clicking on cell, tableView in delegete.
I do not know what kind of design and structure you have, I just offer you perspective.

Is it better to hide a tableviewcell or filter in data source? (performance issue)

I have a UITableViewController where I have cells that I want to hide.
What I'm currently doing is hiding the cells with heightForRowAt returning 0 and cellForRowAt returning a cell with isHidden = false. But since I am using this solution, I noticed the app was slower when I'm scrolling in my tableView.
// Currently returning a height of 0 for hidden cells
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if let post = timeline?.postObjects?[indexPath.row], post.hidden ?? false {
return 0.0
}
return UITableView.automaticDimension
}
// And a cell with cell.isHidden = false (corresponding to identifier "hiddenCell")
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let post = timeline?.postObjects?[indexPath.row] {
if post.hidden ?? false {
return tableView.dequeueReusableCell(withIdentifier: "hiddenCell", for: indexPath)
} else {
return (tableView.dequeueReusableCell(withIdentifier: "postCell", for: indexPath) as! PostTableViewCell).with(post: post, timelineController: self, darkMode: isDarkMode())
}
}
}
I was thinking about why not apply a filter on the array to totally remove hidden cells of the tableView, but I don't know if filtering them each time is great for performances...
// Returning only the number of visible cells
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return timeline?.postObjects?.filter{!($0.hidden ?? false)}.count
}
// And creating cells for only visible rows
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let post = timeline?.postObjects?.filter{!($0.hidden ?? false)}[indexPath.row] {
return (tableView.dequeueReusableCell(withIdentifier: "postCell", for: indexPath) as! PostTableViewCell).with(post: post, timelineController: self, darkMode: isDarkMode())
}
}
What is the best option? Hiding cells when generating them (first) or exclude them of the list (second)?
I would recommend to let the table view data source methods to deal with a filtered version of timeline. However, do not do this in cellForRowAt method because we need to do it one time but not for each cell drawing.
So, what you could do is to declare filteredTimeline and do the filter one time in the viewDidLoad method (for instance):
class TableViewController: UIViewController {
// ...
var filteredTimeline // as the same type of `timeline`
override func viewDidLoad() {
// ...
filteredTimeline = timeline?.postObjects?.filter{!($0.hidden ?? false)}
// ...
}
// Returning only the number of visible cells
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredTimeline.count ?? 0
}
// And creating cells for only visible rows
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let post = filteredTimeline?.postObjects?.filter{!($0.hidden ?? false)}[indexPath.row] {
return (tableView.dequeueReusableCell(withIdentifier: "postCell", for: indexPath) as! PostTableViewCell).with(post: post, timelineController: self, darkMode: isDarkMode())
}
}
// ...
}
In case of there is a better place to filteredTimeline = timeline?.postObjects?.filter{!($0.hidden ?? false)} rather than viewDidLoad, you might need to call tableView.reloadData().
An alternative you could do:
if you think that you don't need the original timeline you could filter it itself:
timeline = timeline?.postObjects?.filter{!($0.hidden ?? false)}
tableView.reloadData()
and you will not need an extra filtered array.
Extra tip:
In case of returning 0.0 value in heightForRowAt method for a certain row, cellForRowAt will not even get called; For example:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return indexPath.row == 0 ?? 0.0 : 100.0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// ...
}
At this point, cellForRowAt should get called only one time because the height for the first row is 0.0.
There is no point of having cells with a size of 0. Your best bet is to filter your datasource, but my suggestion would be to keep two arrays at the same time.
But handle the filtering elsewhere then in the numberOfRowsInSection.
var filteredObjects = []
func filterObjects() {
filteredObjects = timeline?.postObjects?.filter{!($0.hidden ?? false)}
}
// Returning only the number of visible cells
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredObjects.count
}
// And creating cells for only visible rows
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let post = filteredObjects[indexPath.row] {
return (tableView.dequeueReusableCell(withIdentifier: "postCell", for: indexPath) as! PostTableViewCell).with(post: post, timelineController: self, darkMode: isDarkMode())
}
}
I don't know how you handle the filtering, but whenever you want to apply your filter you simply
filterObjects()
tableView.reloadData()

Custom cell don't appear in tableView

My custom cell don't appear in my table View and I didn't find anything to answer that.
Here's my storyboard that contains the TableView :
This is my listController :
extension MatchListViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return matchArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "MatchCell", for: indexPath) as? MatchTableViewCell else {
return UITableViewCell()
}
let match = matchArray[indexPath.row]
cell.configure(nomDuMatch: match.matchName, scoreFinal: match.finalScore)
return cell
}
}
(I've configured the dataSourceDelegate by storyboard)
the customCell identifier is correct and I really don't understand why nothing appears at launch..
Feel free to ask me more pictures / infos !
Edit :
This is the result :
You need to implement
func tableView(_ tableView: UITableView,
heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100 // or any value
}
or use automatic cell and set constraints properly in IB as it seems that you have a constraints problem and set this in viewDidLoad
tableView.estimatedRowHeight = 100
tableView.rowHeight = UITableViewAutomaticDimension

Tableview inside another table view with dynamic height for inner tableview

im new to iOS development.The thing im trying to achieve is to have a tableView inside another tableView and must have selection for items in that inner tableView. There are some problems facing when i implemented this.
i need to have a dynamic table view height (ie,for the first time loading ,inner table view should only have a height to show 3 cells and on clicking a show more button in the outer tableview cell ,the outer table view cell will expand and show the remaining cells in the inner table view)
On selecting a cell in the inner tableView, the same cell in the inner table view cell all other cells are also getting selected.
Im attaching the code i did and image of what im trying to achieve.
Please click here to see the desing i want to achieve
Outer Main Table View methods
extension Explore:UITableViewDelegate,UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 13
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = exploreTableView.dequeueReusableCell(withIdentifier: "exploreCell", for: indexPath) as! ExploreAlbumPosting
if isExpanded{
cell.showMoreBtn.setTitle("Show Less", for: .normal)
cell.showMoreBtn.setImage(#imageLiteral(resourceName: "ic_arrow_drop_up"), for: .normal)
}
else{
cell.showMoreBtn.setTitle("Show More", for: .normal)
cell.showMoreBtn.setImage(#imageLiteral(resourceName: "ic_arrow_drop_down"), for: .normal)
}
cell.showMoreBtn.addTarget(self, action: #selector(showMoreBtnTapped(sender:)), for: .touchUpInside)
cell.showMoreBtn.tag = indexPath.row
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if isExpanded && selectedIndex == indexPath{
return 180 * numberOfRowsInInnerTableView
}
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 215
}
}
show More Btn Tapp action
func showMoreBtnTapped(sender:UIButton){
let row = sender.tag
self.selectedIndex = IndexPath(item: row, section: 0)
isExpanded = !isExpanded
self.exploreTableView.reloadRows(at: [selectedIndex!], with: .automatic)
self.exploreTableView.scrollToRow(at: selectedIndex!, at:UITableViewScrollPosition.middle, animated: true)
}
inner Table view Methods ( This is extended to the customcell class - UITableViewCell of outer table view)
extension ExploreAlbumPosting:UITableViewDelegate,UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 8
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = innerAlbumTableView.dequeueReusableCell(withIdentifier: "ExploreAlbumInnerCell", for: indexPath)
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 92
}
}
Please help me.Thank you

Resources