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
Related
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.
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()
}
I have some problems with reloading section in tableView. If the cell is hiding a bit on the top, it becomes flicking. If the cell is fully visible, everything works fine. The height of cell is fixed, and it's not automaticDimension. All of cells are standard UITableViewCell with different background color. I know that tableView is a very common UI element in ios, and I hope someone had faced the same problem before. Thanks!
func numberOfSections(in tableView: UITableView) -> Int {
return contactsForSections.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let contact = contactsForSections[section]
return contact.isExpanded ? contact.numbers.count : 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = UITableViewCell()
cell.contentView.backgroundColor = .green
return cell
} else {
let cell = UITableViewCell()
cell.contentView.backgroundColor = .red
return cell
}
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
guard indexPath.row == 0 else { return }
contactsForSections[indexPath.section].isExpanded.toggle()
tableView.reloadSections([indexPath.section], with: .none)
}
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.
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()