I'm trying to populate a tableView with 2 sections using two arrays of Firebase objects (called snapshots). I'm getting an error in my cellForRowAtIndexPath function when I try to load the tableView: fatal error: Index out of range.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("PersonCell", forIndexPath: indexPath) as! PersonCell
//set cell text
let guardianDict = guardians[indexPath.row].value as! [String : AnyObject] // error happens here
let dependentDict = dependents[indexPath.row].value as! [String : AnyObject]
cell.personName.text = "test"
return cell
}
Here is how I define my sections:
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
switch(section){
case 0: return "Dependents"
case 1: return "Guardians"
default: return ""
}
}
Any ideas?
Thanks!
EDIT: Adding the numberOfSections and numberOfRowsInSection:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 2
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
switch(section){
case 0: return self.dependents.count
case 1: return self.guardians.count
default: return 1
}
}
You have two sections in your table with each section coming from different sources. You need to add checking in your cellForRowIndexPath function to access the right array:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("PersonCell", forIndexPath: indexPath) as! PersonCell
if indexPath.section == 0
{
let dependentDict = dependents[indexPath.row].value as! [String : AnyObject]
}
else if indexPath.section == 1
{
let guardianDict = guardians[indexPath.row].value as! [String : AnyObject] // error happens here
}
cell.personName.text = "test"
return cell
}
Your two arrays may be of different sizes, so in cellForRowAtIndexPath you need to check which section you are returning a cell for and only access the appropriate array. Currently you are accessing both arrays for each call to this function, resulting in index out of range exceptions when one of the arrays is smaller than the other.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("PersonCell", forIndexPath: indexPath) as! PersonCell
if indexPath.section == 0 {
let dependentDict = dependents[indexPath.row].value as! [String : AnyObject]
cell.personName.text = dependentDict["name"] as! String //Or whatever element in the dictionary is needed
} else {
let guardianDict = guardians[indexPath.row].value as! [String : AnyObject]
cell.personName.text = guardianDict["name"] as! String //Or whatever element in the dictionary is needed
}
return cell
}
Related
I am trying to create a custom TableView that will output custom cells depending on the keys contained in a Dictionary. I have created classes and outlets for each custom cell, but when I build and run; the same custom cell is displayed multiple times. I have the correct number of cells being displayed (i.e. the same as number of keys present in the dictionary) but I can't seem to differentiate between outputted cells.
Here is my code:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.dataDict.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if (dataDict.indexForKey("Number") != nil) {
let firstcell:MyFirstCell = self.scanConfirmTable.dequeueReusableCellWithIdentifier("scanfirst", forIndexPath: indexPath) as! MyFirstCell
return firstcell
}
else if (dataDict.indexForKey("Social") != nil) {
let secondcell:MySecondCell = self.scanConfirmTable.dequeueReusableCellWithIdentifier("scansecond", forIndexPath: indexPath) as! MySecondCell
return secondcell
}
else {
let emptycell:ScanEmptyCell = self.scanConfirmTable.dequeueReusableCellWithIdentifier("scanemptycell", forIndexPath: indexPath) as! ScanEmptyCell
return emptycell
}
I have searched previous posts on here and found an option to use something like:
let currentTag = dataDict[indexPath.row]
But I am getting an error:
Cannot subscript a value of type '[String:String]' with an index type 'Int'.
Any help would be hugely appreciated!
Instead of using Dictionary try to use Array that contains your all keys with sorted and use that array with tableViewDataSource methods.
var keysArray = [String]()
keysArray = Array(dataDict.keys).sorted(<)
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.keysArray.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if (keysArray[indexPath.row] == "Number") {
let firstcell:MyFirstCell = self.scanConfirmTable.dequeueReusableCellWithIdentifier("scanfirst", forIndexPath: indexPath) as! MyFirstCell
return firstcell
}
else if (keysArray[indexPath.row] == "Social") {
let secondcell:MySecondCell = self.scanConfirmTable.dequeueReusableCellWithIdentifier("scansecond", forIndexPath: indexPath) as! MySecondCell
return secondcell
}
else {
let emptycell:ScanEmptyCell = self.scanConfirmTable.dequeueReusableCellWithIdentifier("scanemptycell", forIndexPath: indexPath) as! ScanEmptyCell
return emptycell
}
I am currently having a problem with displaying two different types of custom cells on the same uitableview.
What I have managed so far, is receiving the "updates" to the update cell, known as cell. I just cannot figure out how to also get numberOfRowsInSection to return two values, so both of my cells will show.
Let me explain through my code:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return updates.count
return updatesTask.count // I CANNOT DO THIS - what can I do instead?
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:updateTableViewCell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! updateTableViewCell
let cellTask:tasksTableViewCell = tableView.dequeueReusableCellWithIdentifier("TaskCell", forIndexPath: indexPath) as! tasksTableViewCell
let update = updates[indexPath.row]
let updateTask = updatesTask[indexPath.row]
// Example of the two different cells that need different data from firebase
cell.nameLabel.text = update.addedByUser
cellTask.nameLabel.text = updateTask.addedByUser
As you can probably see, the let updateTask is trying to get an indexPath.row but that is not possible, since I cannot have two return values in the numberOfRowsInSection, which is a problem because that number is referring to the place where the data is stored in my firebase database.. How can I modify this to make it work?
Hope you guys understand where I am going with this, otherwise let me know and I will try to explain better :-)
#Callam's answer is great if you want to put them in two sections.
This is the solution if you want all to be in one section.
First, in numberOfRowsInSection method you need to return the sum of those two array counts like this: return (updates.count + updatesTask.count)
Then you need to configure cellForRowAtIndexPath method like this:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.row < updates.count{
// Updates
let cell:updateTableViewCell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! updateTableViewCell
let update = updates[indexPath.row]
cell.nameLabel.text = update.addedByUser
return cell
} else {
// UpdatesTask
let cellTask:tasksTableViewCell = tableView.dequeueReusableCellWithIdentifier("TaskCell", forIndexPath: indexPath) as! tasksTableViewCell
let updateTask = updatesTask[indexPath.row-updates.count]
cellTask.nameLabel.text = updateTask.addedByUser
return cellTask
}
}
This will display all cells followed by all cellTasks.
If updates array and updatesTask array have equal number of items and you want to display them one by one you can use this:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.row % 2 == 0 {
// Updates
let cell:updateTableViewCell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! updateTableViewCell
let update = updates[indexPath.row/2]
cell.nameLabel.text = update.addedByUser
return cell
} else {
// UpdatesTask
let cellTask:tasksTableViewCell = tableView.dequeueReusableCellWithIdentifier("TaskCell", forIndexPath: indexPath) as! tasksTableViewCell
let updateTask = updatesTask[indexPath.row/2]
cellTask.nameLabel.text = updateTask.addedByUser
return cellTask
}
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 2
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 0:
return updates.count
case 1:
return updatesTask.count
default:
return 0
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
switch indexPath.section {
case 0:
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! updateTableViewCell
let update = updates[indexPath.row]
cell.nameLabel.text = update.addedByUser
return cell
case 1:
let cell = tableView.dequeueReusableCellWithIdentifier("TaskCell", forIndexPath: indexPath) as! tasksTableViewCell
let updateTask = updatesTask[indexPath.row]
cell.nameLabel.text = updateTask.addedByUser
return cell
default:
return UITableViewCell()
}
}
For each row you have to choose if you want to display one type of cell or the other but not both. You should have a flag in numberOfRowsInSection telling your method that you want to load Cell or CellTask and then return the correct number of rows.
You should return total number of rows in your numberOfRowsInSection method. so you can return summation of your both array's count something like,
return updates.count + updatesTask.count
now in your cellForRowAtIndexPath method you can differentiate your cell something like,
let cell:updateTableViewCell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! updateTableViewCell
let cellTask:tasksTableViewCell = tableView.dequeueReusableCellWithIdentifier("TaskCell", forIndexPath: indexPath) as! tasksTableViewCell
if indexPath.row % 2 == 1 {
//your second cell - configure and return
return cellTask
}
else
{
//your first cell - configured and return
return cell
}
I am not sure what you want to achieve. If you want to display the number of cells updates[] and updatesTask[] have elements you can do it like this
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return (updates.count + updatesTask.count)
}
then you can modify your cellForRowAtIndexPath method like this:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:updateTableViewCell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! updateTableViewCell
let cellTask:tasksTableViewCell = tableView.dequeueReusableCellWithIdentifier("TaskCell", forIndexPath: indexPath) as! tasksTableViewCell
if indexPath.row < updates.count{
//update
let update = updates[indexPath.row]
cell.nameLabel.text = update.addedByUser
}else{
let updateTask = updatesTask[indexPath.row]
cellTask.nameLabel.text = updateTask.addedByUser
}
return cell
}
with the if condition you can choose from which array you are taking data.
But be careful to name an array exactly the same as another constant like you did here
let updateTask = updatesTask[indexPath.row]
You can create a simple View Model, that will hold the multiple item types:
enum ViewModelItemType {
case nameAndPicture
case about
case email
case friend
case attribute
}
protocol ViewModelItem {
var type: ViewModelItemType { get }
var rowCount: Int { get }
var sectionTitle: String { get }
}
Then create a model item type for each section. For example:
class ViewModelNameAndPictureItem: ViewModelItem {
var type: ProfileViewModelItemType {
return .nameAndPicture
}
var sectionTitle: String {
return “Main Info”
}
var rowCount: Int {
return 1
}
var pictureUrl: String
var userName: String
init(pictureUrl: String, userName: String) {
self.pictureUrl = pictureUrl
self.userName = userName
}
}
Once you configure all your section items with, you can save them in ViewModel:
class ProfileViewModel {
var items = [ViewModelItem]()
}
And add to you TableViewController:
let viewModel = ViewModel()
In this case, NumberOfSections, NumberOfRows and CellForRowAt methods will be clean and simple:
override func numberOfSections(in tableView: UITableView) -> Int {
return viewModel.items.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.items[section].rowCount
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item = viewModel.items[indexPath.section]
switch item.type {
// configure celll for each type
}
}
Configuring the section title will also be very neat:
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return viewModel.items[section].sectionTitle
}
Please check my recent tutorial on this topic, that will answer your question with the details and examples:
https://medium.com/ios-os-x-development/ios-how-to-build-a-table-view-with-multiple-cell-types-2df91a206429
I have a PFQueryTableViewController, which downloads my images from parse.
I had it working all correctly, displaying the correct image in all the cells, but today I have added a header cell too and now I have the headerCell displaying the correct information and also changing(which is exactly how I want it to be) but now the cell that displays the imageView shows the latest image uploaded for all of the cells & not different image as it should do...?!
Here is the code which controlls my tableview..But I cant understand what is wrong!
object, is var object: PFObject?
override func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 46
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return objects!.count
}
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerCell = tableView.dequeueReusableCellWithIdentifier("PostHeader") as! PostHeaderTableViewCell
let object = objects![section]
let username = object.objectForKey("user")?.objectForKey("username") as? String
headerCell.usernameLabel.text = username!.capitalizedString
let dataFormatter: NSDate = NSDate(timeIntervalSinceNow: -4)
NSLog("Time Ago: %#", dataFormatter.shortTimeAgoSinceNow())
headerCell.timeLabel.text = dataFormatter.shortTimeAgoSinceDate((object.createdAt)!)
if (object.objectForKey("user")!.objectForKey("profilePicture") != nil)
{
let userImageFile:PFFile = object.objectForKey("user")!.objectForKey("profilePicture") as! PFFile
userImageFile.getDataInBackgroundWithBlock({ (imageData: NSData?, error: NSError?) -> Void in
headerCell.postProfilePicture.image = UIImage(data: imageData!)
headerCell.postProfilePicture.layer.cornerRadius = 0.1 * headerCell.postProfilePicture.bounds.size.width
headerCell.postProfilePicture.clipsToBounds = true
})
}
return headerCell
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> PFTableViewCell?
{
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! BaseTableViewCell
cell.titleLabel?.text = object?.objectForKey("title") as? String
let imageFile = object?.objectForKey("imageFile") as? PFFile
cell.cellImageView?.image = UIImage(named: "circle")
cell.cellImageView?.file = imageFile
cell.cellImageView.loadInBackground()
return cell
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
{
if indexPath.row + 1 > self.objects?.count
{
return 44
}
let height = super.tableView(tableView, heightForRowAtIndexPath: indexPath)
return height
}
If anyone can help, That would be amazing!!
I think your issue has to do with the override cellForRowAtIndexPath method that you're using. Because the generic PFQueryTableViewController is intended for only one section, when you use that method with the signature (tableView, indexPath, object) instead of the default one with signature (tableView, indexPath) the method will always pull the # of images that you returned in numberOfRowsInSection. What I suggest you do instead is use the regular cellForRowAtIndexPath(tableView: UITableView, indexPath: NSIndexPath) -> UITableViewCell! and then get the object based on the indexPath.
It would look like this:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell!
{
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! BaseTableViewCell
if let object = objects![indexPath.row] as? PFObject {
cell.titleLabel?.text = object?.objectForKey("title") as? String
let imageFile = object.objectForKey("imageFile") as? PFFile
cell.cellImageView?.image = UIImage(named: "circle")
cell.cellImageView?.file = imageFile
cell.cellImageView.loadInBackground()
}
return cell
}
This will then get you the image for the appropriate index path that you're looking for, rather than always using the first one.
You can also try printing the indexPath.row in that method when it's called so you can see if the indexPath is changing.
EIDT: Actually it seems like even that may not work. You can try to follow the tutorial here, but they explicitly mention that a PFQueryTableViewControlleris not meant to work with multiple sections. You're probably better off subclassing a regular UITableViewController
I have multiple sections in my TableView and I'm a bit stuck to display there names in the correct section. I'm new to xcode, so this an easy one for most but not for me :s
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 2
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
if section == 0 {
return areas.bars.count
} else {
return areas.clubs.count
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("barsandclubsIdentifier", forIndexPath: indexPath)
if section == 0 { // **This is where I'm stuck I can't put section -> UITableViewCell**
let bars = areas.bars
let bar = bars[indexPath.row]
cell.textLabel?.text = bar.name
return cell
} else {
let clubs = areas.clubs
let club = clubs[indexPath.row]
cell.textLabel?.text = club.name
}
}
Try this may help you :
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("barsandclubsIdentifier", forIndexPath: indexPath)
if indexPath.section == 0 { // **This is where I'm stuck I can't put section -> UITableViewCell**
let bars = areas.bars
let bar = bars[indexPath.row]
cell.textLabel?.text = bar.name
}else {
let clubs = areas.clubs
let club = clubs[indexPath.row]
cell.textLabel?.text = club.name
}
return cell
}
I have a table view with two cells, "electionInfo" and "candidates". Candidates returns an array of the candidates, while electionInfo gives a single text body (so just one cell). I'd like to divide them into two sections with their respective headers. Right now, it gives me one header for both cells. How do I fix it?
Thanks!!
My code...
#IBOutlet weak var table: UITableView!
var info: [PFObject] = []
var items: [PFObject] = []
let titles = ["Election Info", "Candidates"]
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items.count + 1
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return self.items.count
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return titles[section]
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.row == self.items.count {
let cell = table.dequeueReusableCellWithIdentifier("candidateCell") as! CandidateTableViewCell
let candidate = items[indexPath.row]
cell.candidateImage.file = candidate["image"] as! PFFile
cell.candidateImage.loadInBackground()
cell.candidateName?.text = candidate["name"] as! String
cell.candidateParty?.text = candidate["partyAffiliation"] as! String
return cell
} else {
let cell = table.dequeueReusableCellWithIdentifier("electionCell") as! ElectionInfoTableViewCell
cell.electionInfo.text = "hey there"
}
}
}
internal func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 2 //or the number of sections you have
}
internal func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let cell = UITableViewCell()
if indexPath.section == 0 {
let candidateCell = table.dequeueReusableCellWithIdentifier("candidateCell") as! CandidateTableViewCell
let candidate = items[indexPath.row]
cell.candidateImage.file = candidate["image"] as! PFFile
cell.candidateImage.loadInBackground()
cell.candidateName?.text = candidate["name"] as! String
cell.candidateParty?.text = candidate["partyAffiliation"] as! String
return candidateCell
} if indexPath.section == 1 {
let electionCell = table.dequeueReusableCellWithIdentifier("electionCell") as! ElectionInfoTableViewCell
cell.electionInfo.text = "hey there"
return electionCell
}
return cell
}
then you have also to change your numberOfRowsInSection
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
if section == 0 {
return self.items.count
}
if section == 1 {
return self.items.count // or the number of rows you have in that section
}
}
A section and a cell is different thing in tableview. Right now your code gives you section instead of cell because you return self.items.count + 1 in numberOfSectionsInTableView. A fix for this;
Since your tableview should contain two section only you must return 2 or titles.count in numberOfSectionsInTableView
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return self.titles.count
}
Next return corresponding array count in numberOfRowsInSection
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0{
return self.info.count
}else{
return self.items.count
}
}
And finally in cell for row at index path return cell based on a section:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.section == 1 {
let cell = table.dequeueReusableCellWithIdentifier("candidateCell") as! CandidateTableViewCell
let candidate = items[indexPath.row]
cell.candidateImage.file = candidate["image"] as! PFFile
cell.candidateImage.loadInBackground()
cell.candidateName?.text = candidate["name"] as! String
cell.candidateParty?.text = candidate["partyAffiliation"] as! String
return cell
} else {
let cell = table.dequeueReusableCellWithIdentifier("electionCell") as! ElectionInfoTableViewCell
cell.electionInfo.text = "hey there"
}
}