Swift - Table View - Repeat order of different sections for multiple data - swift

I have a table with four different sections. After I fetch data from the database and store them in an array, I want to display them in the table view. This means, for each object, I will extract information and put it into the correct section of the table view.
To describe it more simple:
Object 1
TableViewCell 1
TableViewCell 2
TableViewCell 3
TableViewCell 4
Object 2
TableViewCell 1
TableViewCell 2 ..
Currently, my approach looks like
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) ->
UITableViewCell {
let participationObject = Participation(json: (hitsSource?.hit(atIndex: indexPath.row))!)
checkIfUserAlreadyRatedForParticipationContent(participationObject: participationObject)
if (indexPath.section == 0 || indexPath.section % 4 == 0) {
let userProfileCell = tableView.dequeueReusableCell(withIdentifier: "userProfileCell", for: indexPath) as! ChallengeInfoUserTableViewCell
self.dataAccessService.fetchUserById(completionHandler: { (challengeOrganizer) in
let cell = tableView.cellForRow(at: indexPath) as! ChallengeInfoUserTableViewCell
userProfileCell.fullNameLabel.text = challengeOrganizer.firstName + " " + challengeOrganizer.lastName
userProfileCell.fetchProfileImageOfUser(userObject: challengeOrganizer)
userProfileCell.fetchNumberOfWonChallengesByOrganizerId(challengeOrganizer: challengeOrganizer)
userProfileCell.starNumberLabel.text = challengeOrganizer.stars
userProfileCell.delegate = self
}, uid: participationObject.userId)
return userProfileCell
}
else if (indexPath.section % 4 == 1) {
let challengeImageCell = tableView.dequeueReusableCell(withIdentifier: "challengeImageCell", for: indexPath) as! ChallengeInfoImageTableViewCell
dataAccessService.fetchParticipationImageByParticipantIdChallengeId(completionHandler: { (participationImage) in
let cell = tableView.cellForRow(at: indexPath) as! ChallengeInfoImageTableViewCell
challengeImageCell.checkIfToAddOrRemovePlayIcon(participationObject: participationObject)
challengeImageCell.setChallengeImage(challengeImage: participationImage)
challengeImageCell.delegate = self
challengeImageCell.moreButton.tag = indexPath.row
}, participantId: participationObject.userId, challengeId: challengeObject.id)
return challengeImageCell
}
else if (indexPath.section % 4 == 2) {
let commentCell = tableView.dequeueReusableCell(withIdentifier: "commentCell", for: indexPath) as! ChallengeContentDetailCommentTableViewCell
commentCell.fetchCommentsByParticipationId(participationObject: participationObject)
return commentCell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func numberOfSections(in tableView: UITableView) -> Int {
return (hitsSource?.numberOfHits())! * 4
}
I have two issues:
1.) I get always the same object from my data source
2.) When calling let cell = tableView.cellForRow(at: indexPath) as! ChallengeInfoImageTableViewCell It crashes at some point.
Is my approach even legit? Is it clean?

This depends on the data array and you are ignoring the section parameter in numberOfSections(in:)
Never retrieve data asynchronously in cellForRow and update the cell by calling tableView.cellForRow(at:. Cells can be moved off screen immediately so there is no cellForRow(at.
Note:
The first expression in (indexPath.section == 0 || indexPath.section % 4 == 0) is redundant. indexPath.section % 4 is 0 if section is 0.

Related

Display three reusbale cells in tableview - Swift 5

I have 3 UITableViewCell with XIB files but on my tableview I can't display my three UITableViewCell, only can load the first two
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if (indexPath.item % 2 == 0) {
let cell = tableView.dequeueReusableCell(withIdentifier: "CaptureNameTableViewCell", for: indexPath) as! CaptureNameTableViewCell
cell.nameTextField.text = ""
cell.nameTextField.tag = indexPath.row
cell.nameTextField.delegate = self
return cell
} else if indexPath.section == 2 {
let cell = tableView.dequeueReusableCell(withIdentifier: "CapturePhotoTableViewCell", for: indexPath) as! CapturePhotoTableViewCell
cell.displayImage = imageView
cell.cellDelegate = self
cell.selectImage.tag = indexPath.row
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "PieChartTableViewCell", for: indexPath) as! PieChartTableViewCell
return cell
}
}
You need to register the xib tableViewCells inside of the tableViewController:
First create an id property inside of the cells xib class, to use as the cell’s reuse identifier (make the name of this the same as your reuse identifier for the cell which you set in the xib file):
let id = "cellIdentifier" // Name this appropriately
Then create a nib method inside of the cell's xib class:
func nib() -> UINib {
return UINib(named: "cellIdentifier", bundle: nil)
}
Then inside of a lifecycle method such as viewDidLoad():
tableView.register(nib: cell.nib, forCellReuseIdentifier: cell.id)
Finally, specify which cells you want to be where in the table view at which indexPath.row. (Which row in the table view):
switch indexPath.row {
case 0: // First cell
let cell = tableView.dequeueReusableCell(withIdentifier: "CaptureNameTableViewCell", for: indexPath) as! CaptureNameTableViewCell
// Configure cell as before
return cell
case 1: // Second cell
let cell = tableView.dequeueReusableCell(withIdentifier: "CapturePhotoTableViewCell", for: indexPath) as! CapturePhotoTableViewCell
// Configure cell as before
return cell
case 2: // Third cell
let cell = tableView.dequeueReusableCell(withIdentifier: "PieChartTableViewCell", for: indexPath) as! PieChartTableViewCell
// Configure cell as before
return cell
default:
return UITableViewCell() // This should not be reached but a switch statement must cover all possibilities.
}

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.

Thread 1: Fatal error: Index out of range, return array.count + 1

I have a tableview with at all times one cell. If there is data to download from Firebase it will put it in an array called posts. When there's for example, two "in my case" servers that the user will download, it will only display one cell instead of two. I thought I could fix this by changing return posts.count to return posts.count + 1 because of the one cell that will be shown at all times. But if I use return posts.count + 1 I will get a
Thread 1: Fatal error: Index out of range
error on line let post = posts[indexPath.row]. I have read about this error, but I can't seem to fix it.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if posts.count == 0 {
self.tableView.setEmptyMessage("No Servers uploaded!")
return 1
} else {
self.tableView.restore()
return posts.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "ProfileCell", for: indexPath) as! ProfileCellTableViewCell
cell.delegate = self
return cell
}else {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! TableViewCellMYposts
cell.Map_image.image = nil
let post = posts[indexPath.row]
cell.post = post
cell.delegate = self
return cell
}
}
Assuming you have some piece of data in posts[0], you are never actually displaying it. For indexPath.row = 0, you are displaying a profile cell, and then you start displaying the data from posts[1] and on. Change your problem line to:
let post = posts[indexPath.row - 1]

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
}
}

TableView With 3 Sections and cells crashing

I have a simple UITableViewController with 3 sections and each section has 3 rows. I have already allocated each cell it's reuse identifier.
When I run the code, I get an EXE_BAD_INSTRUCTION error at return cell.
Here is my code:
import UIKit
class settingsViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 3
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 3
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell : UITableViewCell!
if indexPath.section == 1
{
if indexPath.row == 1
{
cell = tableView.dequeueReusableCellWithIdentifier("draftCell", forIndexPath: indexPath) as UITableViewCell
}
else if indexPath.row == 0
{
cell = tableView.dequeueReusableCellWithIdentifier("profileCell", forIndexPath: indexPath) as UITableViewCell
}
else if indexPath.row == 2
{
cell = tableView.dequeueReusableCellWithIdentifier("logoutCell", forIndexPath: indexPath) as UITableViewCell
}
}
else if indexPath.section == 2{
if indexPath.row == 0
{
cell = tableView.dequeueReusableCellWithIdentifier("facebookCell", forIndexPath: indexPath) as UITableViewCell
}
else if indexPath.row == 1
{
cell = tableView.dequeueReusableCellWithIdentifier("twitterCell", forIndexPath: indexPath) as UITableViewCell
}
else if indexPath.row == 2
{
cell = tableView.dequeueReusableCellWithIdentifier("rateusCell", forIndexPath: indexPath) as UITableViewCell
}
}
else if indexPath.section == 3
{
if indexPath.row == 0
{
cell = tableView.dequeueReusableCellWithIdentifier("creditsCell", forIndexPath: indexPath) as UITableViewCell
}
else if indexPath.row == 1
{
cell = tableView.dequeueReusableCellWithIdentifier("contactusCell", forIndexPath: indexPath) as UITableViewCell
}
else if indexPath.row == 2
{
cell = tableView.dequeueReusableCellWithIdentifier("termsandconditionsCell", forIndexPath: indexPath) as UITableViewCell
}
}
return cell
}
For section number 0 you haven't written cellForRowAtIndexPath. You have given number of sections = 3, so its index starts from 0,
Correct your indexpath.section comparison i.e do it for indexpath.section == 0, indexpath.section == 1 and indexpath.section == 2.
Also you haven't registered class for tableview cell like
self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
You would be better off using a static tableView in storyboard, instead of trying to programmatically code static cells in an dynamic tableView.
Apple's Table View Programming Guide recommends:
Use static cells to design a table with a fixed number of rows, each with its own layout. Use static cells when you know what the table looks like at design time, regardless of the specific information it displays.
If you are determined to dynamically handle this, your code would be easier to read and maintain if you used a switch statement, instead of nested ifs.
You have 3 sections but the index should start from 0 (0,1,2,3,...). So modify your code starting from indexPath.section == 0 (which is the first section of your table). Furthermore I agree with PetahChristian that you may use static cell to design your table.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell : UITableViewCell!
if indexPath.section == 0
{
if indexPath.row == 1
{
cell = tableView.dequeueReusableCellWithIdentifier("draftCell", forIndexPath: indexPath) as UITableViewCell
}
else if indexPath.row == 0
{
cell = tableView.dequeueReusableCellWithIdentifier("profileCell", forIndexPath: indexPath) as UITableViewCell
}
else if indexPath.row == 2
{
cell = tableView.dequeueReusableCellWithIdentifier("logoutCell", forIndexPath: indexPath) as UITableViewCell
}
}
else if indexPath.section == 1{
if indexPath.row == 0
{
cell = tableView.dequeueReusableCellWithIdentifier("facebookCell", forIndexPath: indexPath) as UITableViewCell
}
else if indexPath.row == 1
{
cell = tableView.dequeueReusableCellWithIdentifier("twitterCell", forIndexPath: indexPath) as UITableViewCell
}
else if indexPath.row == 2
{
cell = tableView.dequeueReusableCellWithIdentifier("rateusCell", forIndexPath: indexPath) as UITableViewCell
}
}
else if indexPath.section == 2
{
if indexPath.row == 0
{
cell = tableView.dequeueReusableCellWithIdentifier("creditsCell", forIndexPath: indexPath) as UITableViewCell
}
else if indexPath.row == 1
{
cell = tableView.dequeueReusableCellWithIdentifier("contactusCell", forIndexPath: indexPath) as UITableViewCell
}
else if indexPath.row == 2
{
cell = tableView.dequeueReusableCellWithIdentifier("termsandconditionsCell", forIndexPath: indexPath) as UITableViewCell
}
}
return cell
}