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.
Related
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. :)
I have a tableview in my app.
I want to divide my cells into sections and give headlines.
Below I attach the picture (animals, birds headlines):
How do I do that in dynamic prototype cells?
My section data:
var sectionsData = [
"header",
"description",
"diagnoses",
"perscription",
"notes",
"addFaxHeadline",
"addFax",
"addEmailHeadline",
"addEmails",
"givePermissionHeadline",
"select answer"
]
Here is my cell at row function:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print ("indexPath: ", indexPath)
print ("indexPath: ", indexPath[0])
print ("-------")
if (sectionsData[indexPath[0]] == "header") {
let cell = tableView.dequeueReusableCell(withIdentifier: "headerCell", for: indexPath)
return cell
} else if (sectionsData[indexPath[0]] == "description") {
let cell = tableView.dequeueReusableCell(withIdentifier: "headerInfoCell", for: indexPath)
return cell
} else if (sectionsData[indexPath[0]] == "diagnoses") {
let cell = tableView.dequeueReusableCell(withIdentifier: "diagnosisCell", for: indexPath)
return cell
} else if (sectionsData[indexPath[0]] == "perscription") {
let cell = tableView.dequeueReusableCell(withIdentifier: "perscriptionCell", for: indexPath)
return cell
} else if (sectionsData[indexPath[0]] == "notes") {
let cell = tableView.dequeueReusableCell(withIdentifier: "notesCell", for: indexPath)
return cell
} else if (sectionsData[indexPath[0]] == "addFaxHeadline") {
let cell = tableView.dequeueReusableCell(withIdentifier: "addFaxCell", for: indexPath)
return cell
} else if (sectionsData[indexPath[0]] == "addFax") {
let cell = tableView.dequeueReusableCell(withIdentifier: "emailNameCell", for: indexPath)
return cell
} else if (sectionsData[indexPath[0]] == "addEmailHeadline") {
let cell = tableView.dequeueReusableCell(withIdentifier: "addEmailCell", for: indexPath)
return cell
} else if (sectionsData[indexPath[0]] == "addEmails") {
let cell = tableView.dequeueReusableCell(withIdentifier: "emailNameCell", for: indexPath)
return cell
} else if (sectionsData[indexPath[0]] == "givePermissionHeadline") {
let cell = tableView.dequeueReusableCell(withIdentifier: "permisionCell", for: indexPath)
return cell
} else if (sectionsData[indexPath[0]] == "select answer") {
let cell = tableView.dequeueReusableCell(withIdentifier: "selectAnswerCell", for: indexPath)
return cell
}
let cell = tableView.dequeueReusableCell(withIdentifier: "addFaxCell", for: indexPath)
// <<<< ???
return cell
}
and my numbers in row per section function:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
if (sectionsData[section] == "header") {
print ("returning 1 ?")
return 1
} else if (sectionsData[section] == "description") {
return 1
} else if (sectionsData[section] == "diagnoses") {
//return visitSummary.diagnoses.count
return 2
} else if (sectionsData[section] == "perscription") {
//return visitSummary.prescriptions.count
return 2
} else if (sectionsData[section] == "notes") {
return 1
} else if (sectionsData[section] == "addFaxHeadline") {
return 1
} else if (sectionsData[section] == "addFax") {
return faxAdded.count
} else if (sectionsData[section] == "addEmailHeadline") {
return 1
} else if (sectionsData[section] == "addEmails") {
return emailsAdded.count
} else if (sectionsData[section] == "givePermissionHeadline") {
return 1
} else if (sectionsData[section] == "select answer") {
return 1
} else {
return 0
}
return 0
}
and my number of sections:
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
print ("sectionsData.count: ", sectionsData.count)
return sectionsData.count
}
All of the tutorials I find is for one section and I have a very amount of sections to show.
How would I divide it and give them headlines?
Let's clean up your code first:
enum Section: Int, CaseIterable {
case header = 0, description, diagnoses, prescription, notes, addFaxHeadline, addFax, addEmailHeadline, addEmails, givePermissionHeadline, selectAnswer
}
var sectionData: [Section] = [
.header,
.description,
.diagnoses
...
]
override func numberOfSections(in tableView: UITableView) -> Int {
return sectionsData.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection sectionIndex: Int) -> Int {
let section = sectionData[sectionIndex]
switch section {
case .header:
return 1
case .description:
return 1
// ...
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let section = sectionData[sectionIndex]
switch section {
case .header:
let cell = tableView.dequeueReusableCell(withIdentifier: "headerCell", for: indexPath)
return cell
// ... etc
}
}
Now, we can return section title just using a String:
override func tableView(_ tableView: UITableView, titleForHeaderInSection sectionIndex: Int) -> String? {
let section = sectionData[sectionIndex]
switch section {
case .header:
return "Header title"
case .description:
return "Description title"
default:
return nil
}
}
Another option is to use tableView(:viewForHeaderInSection:) and tableView(:heightForHeaderInSection:):
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 44
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let view = UIView()
let label = UILabel()
view.addSubview(label)
// setup constraints accordingly
// setup title of the label
return view
}
You can also use a custom reusable header which would be registered and dequeued in the same way cells are dequeued.
var sectionsData = [
"header",
"description",
"diagnoses",
"prescription",
"notes",
"addFaxHeadline",
"addFax",
"addEmailHeadline",
"addEmails",
"givePermissionHeadline",
"select answer"
] // Your Array
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? //*Data Source method for header title*
{
// Safe cast
if let sectionArray = sectionsData as? [String]
{
return sectionArray[section]
}
// A fail safe
return "No Header"
}
Now I see. You are misunderstanding how these methods work. They are called for every cell in every section.
So, if you return 4 in numberOfSections, the method numberOfRowsInSection will be called 4 times, and sectionIndex (now called section in latest versions) will have the current section (0 to 3 in our example).
Therefore, if you want to call the second section "Birds" and let all the others nil, your code will look like this:
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if section == 1 {
return "Birds"
}
// Otherwise
return nil
}
Same thing for how many rows each section will have:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
// 1 row for the first section
return 1
} else if section == 1 {
// 3 rows for the second section
return 3
}
// 2 rows for every other section
return 2
}
And, finally, what cell will be used in each indexPath:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Figure out which row and section you are talking about
let row = indexPath.row
let section = indexPath.section
// Now you know where you are in the TableView, create the corresponding cell according to your project
// Start by dequeueing a custom cell using the identifier and forcing the cell to become your custom cell
let cell = tableView.dequeueReusableCell(withIdentifier: customCellID) as! CustomTableViewCell
// Do aditional setup to this cell
cell.textLabel?.text = "This is cell \(row) in section \(section)"
// Return the cell when you are ready
return cell
}
Remember Sulthan's suggestion to create an enum for your sections.
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.
I have the following code thus far.
var someData = [SomeData]()
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell1", for: indexPath) as! Cell1
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell2", for: indexPath) as? Cell2
let someData = [indexPath.row]
//Set up labels etc.
return cell!
}
}
I need Cell1 which is a static cell and will always remain at indexPath 0 to be in a section called "Section1" for example & all of the Cell2's to be in a section called "Section2"
Other DataSource & Delegate Methods;
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return 1
} else {
return someData.count
}
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if section == 0 {
return "Section1" }
else {
return "Section2"
}
}
This returns me everything I need for the first section, however, when it comes to the second section (because of the code inside cellForRowAtIndex somewhere) section 2 contains Cell2 at indexPath 0.
Any help greatly appreciated.
Root cause:
In cellForRowAtIndexPath check for indexPath.section instead of indexPath.row
Fix:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell1", for: indexPath) as! Cell1
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell2", for: indexPath) as? Cell2
let someData = [indexPath.row]
//Set up labels etc.
return cell!
}
}
SWIFT 3 and Firebase - I'm trying to use the UITable inEditing mode to insert cells into firebase and have a firebase listener that updates my arraty. My insertions to fire base are fine, but I keep getting an index out of range when I try to do the insertion with the new add button i created. Has anyone used Firebase with UITable inEditing mode to insert into a table and update firebase.
This is what I looking for:
enter image description here
The problem maybe be how I wrote the cellForAt:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("zzz indexPath index path row: \(indexPath.row)")
print("zzz indexPath name \(myPins[indexPath.row].pinName)")
print("ZZZ myPins Count \(myPins.count)")
let cell = tableView.dequeueReusableCell(withIdentifier: "PinCell", for: indexPath)
if indexPath.row == 0 && isEditing {
//this means we are in editing mode an we to add the cell that will be addPlace
//let indexPathOfFirstRow = IndexPath(item: 0, section: 0)
cell.textLabel?.text = "Add Place"
print("add place was called index.row is : \(indexPath.row)")
} else {
//let cell = tableView.dequeueReusableCell(withIdentifier: "PinCell", for: indexPath)
let pin = myPins[indexPath.row]
cell.textLabel?.text = pin.pinName
}
return cell
}
i get this error message: fatal error: Index out of range
Here is the rest of the table set up code:
//When the use press the editing button it will make the table enditable
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
//if the viw is in editing mode then we will set the table to be in enditing mode.
if isEditing {
//This is adding the addtional cell in for add place
tableView.beginUpdates()
let indexPath = IndexPath(row: 0, section: 0)
tableView.insertRows(at: [indexPath], with: .automatic)
// self.tableView.insertRows(at: [indexPathOfFirstRow], with: UITableViewRowAnimation.automatic)
tableView.endUpdates()
tableView.setEditing(true, animated: true)
} else {
//when the 'Done' is pressed this will take away the add button
tableView.beginUpdates()
let indexPathOfFirstRow = IndexPath(item: 0, section: 0)
self.tableView.deleteRows(at: [indexPathOfFirstRow], with: UITableViewRowAnimation.automatic)
tableView.endUpdates()
//otherwise the table will not be in editing mode.
tableView.setEditing(false, animated: false)
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//STEP 1 for adding a row that will fuction as add place
//If we are in editing mode a 1 will be return and the extra cell will be added
let adjustment = isEditing ? 1 : 0
//add the extra cell if we are in editing mode
return myPins.count + adjustment
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "PinCell", for: indexPath)
if indexPath.row == 0 && isEditing {
//this means we are in editing mode an we to add the cell that will be addPlace
//let indexPathOfFirstRow = IndexPath(item: 0, section: 0)
cell.textLabel?.text = "Add Place"
print("add place was called index.row is : \(indexPath.row)")
//addCell.addButton.tag = indexPath.row
//addCell.addButton.addTarget(self, action: "addPlace", for: .touchUpInside)
} else {
// let cell = tableView.dequeueReusableCell(withIdentifier: "PinCell", for: indexPath)
//this is the info I want to dispaly in the cell
let pin = myPins[indexPath.row]
cell.textLabel?.text = pin.pinName
return cell
}
//add the green plus button icon for the extra row added
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
// let indexRow = IndexPath(item: 0, section: 0)
if indexPath.row == 0 {
return .insert
}
return .delete
}
//delete with a with a swipe
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
print("pins count before deleate\(myPins.count)")
if editingStyle == .delete {
let pin = myPins[indexPath.row]
guard let currentUser = FIRAuth.auth()?.currentUser?.uid else {return}
rootRef.child(child.users.rawValue).child(currentUser).child(users.myPins.rawValue).child(plotToEdit!.plotID).child(pin.placeID).removeValue()
}
//When you press on the green plus button as appose to the row.
else if editingStyle == .insert {
let autocompleteController = GMSAutocompleteViewController()
autocompleteController.delegate = self
present(autocompleteController, animated: true, completion: nil)
}
}
//When in ending more I can slect the first row that is add places
func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
// let indexPathOfFirstRow = IndexPath(item: 0, section: 0)
if isEditing && (indexPath.row > 0) {
return nil
}
return indexPath
}