When I scroll down in my TableView, the cell data changes - Swift 4 - swift

When I scroll down in my Table View, the cell data that has disappeared then changes. How can I solve this?
class TableViewController: UITableViewController {
var number = 1
let finishNumber = 10
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return finishNumber
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = "\(number)"
number = number + 1
return cell
}
}

You update number every time the table view asks for a cell. That has no direct relation to the row being displayed.
It's unclear why you even have the number property.
If you just want to show the corresponding row number in each cell, get rid of the number property and update:
cell.textLabel?.text = "\(number)"
with:
cell.textLabel?.text = "\(indexPath.row)"

Unclear question but I think you want to scroll to bottom when tableview reload right ?
extension UITableView {
func scrollToBottom(animated: Bool = false) {
let section = self.numberOfSections
if (section > 0) {
let row = self.numberOfRows(inSection: section - 1)
if (row > 0) {
self.scrollToRow(at: IndexPath(row: row - 1, section: section - 1), at: .bottom, animated: animated)
}
}
}
}
When calling, you need to use "async"
DispatchQueue.main.async(execute: {
yourTableView.reloadData()
yourTableView.scrollToBottom()
}
Good luck.

Related

Swift UITableView numberOfRows Error When Added or Removing Rows in Section

I've been experiencing a very annoying issue this week. I have a UITableView that generates a number of sections (with section headers), each section always having 3 rows.
I've implemented a hiding/showing section functionality when a section header is tapped, where all the rows in that section are either deleted or inserted accordingly. Everything works perfectly fine and as expected... 70% of the time. I find that once I start dragging around the UITableView a bit while tapping on headers and just being all around random, it crashes unexpectedly with the error:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (3) must be equal to the number of rows contained in that section before the update (3), plus or minus the number of rows inserted or deleted from that section (3 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
Below is my relevant code:
#IBOutlet var pastWinnersTableView: UITableView!
var previousWeeks : [PreviousWeekData] = []
var hiddenSections = Set<Int>()
var firstTimeLoading = true ///This is needed to allow all the past week cells to start off as minimized
//This function runs in response to a section header being tapped in the table view.
#objc func expandCollapseSection(section: UIButton){
pastWinnersTableView.isUserInteractionEnabled = false
func indexPathsForSection() -> [IndexPath] {
var indexPaths = [IndexPath]()
for row in 0..<3 {
indexPaths.append(IndexPath(row: row, section: section.tag))
}
return indexPaths
}
//If the section was previously hidden...
if hiddenSections.contains(section.tag) {
hiddenSections.remove(section.tag)
pastWinnersTableView.insertRows(at: indexPathsForSection(), with: .fade)
//If the section was previously showing...
} else {
hiddenSections.insert(section.tag)
pastWinnersTableView.deleteRows(at: indexPathsForSection(), with: .fade)
}
}
//MARK: - REQUIRED METHODS
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if firstTimeLoading == true || self.hiddenSections.contains(section) { return 0 }
else { return 3 }
}
func numberOfSections(in tableView: UITableView) -> Int {
let numberOfPreviousWeeks = previousWeeks.count
if numberOfPreviousWeeks > 0 {return numberOfPreviousWeeks} else {return 0}
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let weekNumber = previousWeeks[section].weekNumber
let headerButton = UIButton(type: .custom)
headerButton.layer.borderWidth = 1
headerButton.layer.borderColor = UIColor.black.cgColor
headerButton.layer.cornerRadius = 10
headerButton.layer.masksToBounds = true
headerButton.titleLabel?.font = UIFont(name: "Mouse Memoirs", size: 30)
headerButton.backgroundColor = task.hexStringToUIColor(hex: "5B3F22")
headerButton.setTitleColor(.white, for: .normal)
headerButton.titleLabel?.shadowColor = .black
headerButton.titleLabel?.shadowOffset = CGSize(width: 1, height: 1)
headerButton.addTarget(self, action: #selector(expandCollapseSection), for: .touchUpInside)
headerButton.tag = section
hiddenSections.insert(section)
headerButton.setTitle("Week \(String(weekNumber))", for: .normal)
return headerButton
}
//This footer is essentially just a small blank space.
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let footer = tableView.dequeueReusableCell(withIdentifier: "pastWinnersWeekFooterCell") as! PastWinnersWeekFooterCell
return footer
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 45 }
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return 5 }
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return UITableView.automaticDimension }
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { return 44 }
//PreviousWeekData[(weekNumber, firstPlacePlayers[(username, score)], secondPlacePlayers[(username, score)], thirdPlacePlayers[(username, score)]), (weekNumber,...)]
//This defines the tableViewCell that holds all 3 placement sections with all the winners for a given week
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let weekSection = indexPath.section
let weekData = previousWeeks[weekSection]
let cell = tableView.dequeueReusableCell(withIdentifier: "pastWinnersTableViewCell", for: indexPath) as! PastWinnersTableViewCell
cell.thisWeeksData = weekData
cell.winnersCollectionView.tag = indexPath.row
cell.frame = tableView.bounds;
cell.layoutIfNeeded()
cell.winnersCollectionView.reloadData()
cell.collectionViewHeight.constant = cell.winnersCollectionView.collectionViewLayout.collectionViewContentSize.height;
return cell
}
Any ideas why it would be crashing on me with that error only sometimes? (ie. once I start dragging it around a bit and THEN tapping on headers? Tapping on my section headers is always a 3 in, 3 out deal so I'm not sure why it's getting confused.
Thanks for the help!

Multiple prototype cells in UITableView

So I am trying to set up my UITableViewController that has a headerview, footerview, and then multiple prototype cells in between them. When I go to run my code on my simulator, only the hearderview and footerview are displaying and the prototype cells are not. Here is my code:
import UIKit
class UploadTrackTableViewController: UITableViewController {
var headerView: UploadHeader!
var footerView: UploadFooter!
override func viewDidLoad() {
super.viewDidLoad()
headerView = tableView.tableHeaderView as! UploadHeader
footerView = tableView.tableFooterView as! UploadFooter
}
}
extension UploadTrackTableViewController {
override func numberOfSections(in tableView: UITableView) -> Int {
return 4
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return 1
} else if section == 1 {
return 1
} else if section == 2 {
return 1
} else {
return 1
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "trackTitleCell", for: indexPath) as! TrackTitleTableViewCell
return cell
} else if indexPath.section == 1 {
let cell = tableView.dequeueReusableCell(withIdentifier: "genreCell", for: indexPath) as! SelectGenreTableViewCell
return cell
} else if indexPath.section == 2 {
let cell = tableView.dequeueReusableCell(withIdentifier: "featuredArtistCell", for: indexPath) as! AddFeaturedArtistTableViewCell
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "audioCell", for: indexPath) as! SelectAudioFileTableViewCell
return cell
}
}
}
And here is a screenshot of how my controller looks on interface builder.
And here is a screenshot of the simulator.
Not sure if setting this up as a tableview is the best option maybe I should go for a scrollview with this UI but I am not sure what would work best.
here is what the view controller looks like now.
You can use only 1 section and UITableView has delegate functions of
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
return 'your custom view'
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
return 'your custom view'
}
You can return you own custom view of header and footer here.
Happy coding. :)

Inserting cell in specific section

I am trying to find a way that allows me to insert a new cell under a specific section on my tableview. There is a button that is pressed called "add song" and once a user presses on that it should insert a new cell that is built by with a prototype cell. That prototype cell will allow a user to click on it and edit certain information on that cell. I have been trying to code a way to insert the cell below the cell that is currently in that section which is section "3". I'm sure it is something simple that I am messing up since I'm not very use to doing tableviews. Here is my code:
import UIKit
class MultipleSongsTableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func addSong(_ sender: Any) {
insertNewSongCell()
}
func insertNewSongCell() {
let indexPath = IndexPath(row: -1, section: 3)
tableView.beginUpdates()
tableView.insertRows(at: [indexPath], with: .automatic)
tableView.endUpdates()
}
}
extension MultipleSongsTableViewController {
override func numberOfSections(in tableView: UITableView) -> Int {
return 5
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return 1
} else if section == 1 {
return 1
} else if section == 2 {
return 1
} else if section == 3 {
return 1
} else {
return 1
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "trackTitleCell", for: indexPath) as! ReleaseTitleTableViewCell
return cell
} else if indexPath.section == 1 {
let cell = tableView.dequeueReusableCell(withIdentifier: "genreCell", for: indexPath) as! Genre2TableViewCell
return cell
} else if indexPath.section == 2 {
let cell = tableView.dequeueReusableCell(withIdentifier: "TrackListCell", for: indexPath) as! TrackListTableViewCell
return cell
} else if indexPath.section == 3 {
let cell = tableView.dequeueReusableCell(withIdentifier: "TrackListSongCells", for: indexPath) as! TrackListSongsTableViewCell
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "AddSongButtonCell", for: indexPath) as! AddSongTableViewCell
return cell
}
}
}
Also here is a screenshot of how my viewcontroller looks and where I expect the new cell to populate.
I would like the new cell to be inserted after the cell that says "Song Name", I would like the inserted cell to be the same prototype cell that is currently there because the user can click on that cell and fill out information and change the current "Song Name" label to what ever they want.
First of all you need a data source array for the section for example
var songs = [String]()
Then you have to modify numberOfRowsInSection to return the number of songs for section 3. This method can be simplified anyway
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 3: return songs.count
default: return 1
}
}
Now you can add a new song to the array and insert the row
func insertNewSongCell() {
let insertionIndex = songs.count
songs.append("New Song")
let indexPath = IndexPath(row: insertionIndex, section: 3)
tableView.insertRows(at: [indexPath], with: .automatic)
}
beginUpdates and endUpdates have no effect in this case, you can omit the lines.
Calling UITableView.insertRows(at:with:) will insert cells into your UITableView. You can insert exactly 1 cell by passing an indexPaths argument containing 1 IndexPath:
self.tableView.insertRows(at: [IndexPath(row: 1, section: 3)], with: .automatic)
You may need to also update your UITableViewDataSource to return expected values, e.g. from its tableView(_:numberOfRowsInSection:), to account for the additional row(s). Otherwise, your app will throw an unhandled exception and crash.

Why is the width of cell in UItableview not same as width of screen in simulator?

The table cell is running shorter than it is supposed to be. (I use different background colour which shows difference width) The setting of cell is custom (only row height is change to 140). The playerCell class has nothing but some IBoutlets. Thanks.
(image: storyboard
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var count: Int
if playerBank.count == 0
{
count = 1
}
else{
count = playerBank.count
}
return count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! playerCell
if playerBank.isEmpty{
let p = Player()
playerBank = setInvalidPlayer(pBank: p, userInput: userInputPlayerName)
}
let playerInBank: Player = playerBank[indexPath.row]
passImageUrl = cell.setPlayerCell(p: playerInBank)
urlBank.append(passImageUrl)
let bgColorView = UIView()
bgColorView.backgroundColor = UIColor.red
cell.selectedBackgroundView = bgColorView
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
myIndex = indexPath.row
DispatchQueue.main.async {
self.performSegue(withIdentifier: "ShowPlayerInfo", sender: self)
}
}
My first guess is that, while you have a custom cell with custom outlets, that there are no layout constraints in place. So the items that you put into the storyboard are retaining their intrinsic values and not adapting to the device screen size.
If you add constraints to those outlets, especially enough to where the width of the cell can be determined based on the rest of the elements, then your cell should display full width.

Displaying two reusable cells in tableview - Swift 3

I have two custom reusable table view cells in my table view. The first cell, I would like it to be present at all times. The second cell and beyond, are returning a count that is being passed from mysql database.
// return the amount of cell numbers
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count
}
// cell config
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row < 1 {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! InfoCell
//set the data here
return cell
} else {
let Postcell = tableView.dequeueReusableCell(withIdentifier: "PostCell", for: indexPath) as! PostCell
let post = posts[indexPath.row]
let image = images[indexPath.row]
let username = post["user_username"] as? String
let text = post["post_text"] as? String
// assigning shortcuts to ui obj
Postcell.usernameLbl.text = username
Postcell.textLbl.text = text
Postcell.pictureImg.image = image
return Postcell
}
} // end of function
My first cell is there and so are the post.count, but for some reason the posts.count is missing one post and I believe this is because of the first cell. Can anybody help me with this? thanks in advance.
You need to adjust the value returned from numberOfRowsInSection to account for the extra row. And you would need to adjust the index used to access values from your posts array to deal with the extra row.
But a much better solution is to use two sections. The first section should be your extra row and the second section would be your posts.
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return 1
} else {
return posts.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! InfoCell
//set the data here
return cell
} else {
let Postcell = tableView.dequeueReusableCell(withIdentifier: "PostCell", for: indexPath) as! PostCell
let post = posts[indexPath.row]
let image = images[indexPath.row]
let username = post["user_username"] as? String
let text = post["post_text"] as? String
// assigning shortcuts to ui obj
Postcell.usernameLbl.text = username
Postcell.textLbl.text = text
Postcell.pictureImg.image = image
return Postcell
}
}