I'm calling a TableView inside of a custom ViewController. In my UITableView, my cells overlap. Like so
In the storyboard editor, I have the Row Height set to Custom at 63pts, but they look like they're actually using the the default height that the cell would be if you unchecked the Custom box.
Like in some of the other solutions tried implementing the heightForRowAt function but it doesn't do anything. I've tried using obscenely large numbers, but the result is always exactly as it was before.
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
{
if(self.view.traitCollection.horizontalSizeClass == .compact ||
self.view.traitCollection.verticalSizeClass == .compact) {
cellHeight = 55
return 55
}
else{
return 63
}
}
The only thing in solutions I've seen so far that looks at all promising is this one. iOS UITableViewCell overlapping
I've tried implementing the prepareForReuse Function in my custom TableViewCell class file, but I didn't know what I was actually supposed to add to the function, so I just called the super function and added a print statement
--- Full TableView Code ---
extension SavingViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "WishlistTableViewCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? WishlistTableViewCell else {
fatalError("The dequeued cell is not an instance of WishlistTableViewCell.")
}
cell.selectionStyle = .none
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
{
if(self.view.traitCollection.horizontalSizeClass == .compact ||
self.view.traitCollection.verticalSizeClass == .compact) {
cellHeight = 55
return 55
}
else{
return 63
}
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
/*
let resultRemoving = bookmarks[indexPath.row]
if var bookmarkData = defaults.object(forKey:"bookmarkDict") as? [String: [String]] {
bookmarkData.removeValue(forKey: resultRemoving.resultURL)
defaults.set(bookmarkData, forKey:"bookmarkDict")
}
defaults.synchronize()
*/
items.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
} else if editingStyle == .insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
}
Currently heightForRowAt is not being called since heightForRowAt method is a UITableViewDelegate method so
Your extension should look like this:
Add UITableViewDelegate
extension SavingViewController: UITableViewDataSource, UITableViewDelegate {
}
Related
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()
Not sure why but the top row of my uitableview is not editable, all other rows function as normal and delete as expected. It's like caneditrowat indexPath: Indexpath isn't working for that one row. See images attached.
My code in tableView(_:commit:forRowAt:) looks like all the tutorials I can find, can't seem to find any other examples with this problem.
//MARK: Properties
var favouriteExercises = [FavouriteExercise]()
override func viewDidLoad() {
super.viewDidLoad()
//Load exercises from local DB
if let savedFavouriteExercises = loadFavouriteExercises()
{
//loading exercises in from the favourites
favouriteExercises += savedFavouriteExercises
}
// Use the edit button item provided by the table view controller.
navigationItem.rightBarButtonItem = editButtonItem
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//count number of rows in table
return favouriteExercises.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Table view cells are reused and should be dequeued using a cell identifier.
let cellIdentifier = "FavouriteTableViewCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? FavouriteTableViewCell else {
fatalError("The dequeued cell is not an instance of FavouriteTableViewCell.")
}
// Fetches the appropriate exercise for the data source layout.
let exercise = favouriteExercises[indexPath.row]
//setup the layout for the cell in the table view
cell.nameLabel.text = exercise.name
let url = URL(string: (exercise.iconUrl))!
cell.photoImageView.sd_setImage(with: url)
//cell.photoImageView.image = #imageLiteral(resourceName: "defaultPhoto")
cell.backgroundColor = UIColor.darkGray
return cell
}
// Override to support conditional editing of the table view.
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
// Override to support editing the table view.
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
// Delete the row from the data source
favouriteExercises.remove(at: indexPath.row)
saveFavouriteExercisess()
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
Thanks for the help
override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle
{
return UITableViewCellEditingStyle.delete
}
Answer adapted from #KumarReddy's solution
I am getting JSON data from an API and display it in a tableView. When user scrolls at the last row, the next page will be added to the current data.
But I am getting this error when next page data is added.
[__NSArray0 objectAtIndex:]: index 0 beyond bounds for empty NSArray
func loadUser(_ currentPage:Int=1){
APIService.loadUser(currentPage, size: 100, callback: { data in
if let data = data["user"].arrayValue as [JSON]?{
self.jsonData?.append(contentsOf: data
self.tableView.reloadData()
self.hideLoadingInView(view: self.view)
}
})
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.jsonData?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "UserCell") as! UserTableViewCell
cell.user = self.jsonData?[indexPath.row]
if !isLoading && (indexPath.row == self.jsonData!.count - 1){
currentPage += 1
loadUser(currentPage)
}
return cell
}
I would suggest few potential fixes:
Add Exception Breakpoint to your project, so you can better debug your particular issue. It's very useful tip: https://www.natashatherobot.com/xcode-debugging-trick/ You'll probably find out what's wrong with this.
Move the UI manipulation inside the API call response into the DispatchQueue.main.async { ... } block, to be sure the UI is manipulated on the main thread. Otherwise it can cause weird behavior and maybe also your issue.
Move the check if you need to fetch another page from API to the tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) function. Some people do recommend to do it in scrollViewDidScroll delegated method of UIScrollView.
Then it will look like this.
func loadUser(_ currentPage:Int=1){
APIService.loadUser(currentPage, size: 100, callback: { data in
if let data = data["user"].arrayValue as [JSON]?{
DispatchQueue.main.async {
self.jsonData?.append(contentsOf: data)
self.tableView.reloadData()
self.hideLoadingInView(view: self.view)
}
}
})
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.jsonData?.count ?? 0
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if !isLoading && (indexPath.row == self.jsonData!.count - 1){
currentPage += 1
loadUser(currentPage)
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "UserCell") as! UserTableViewCell
cell.user = self.jsonData?[indexPath.row]
return cell
}