In my app I created two custom tableview cells.
Problem I am facing now the second tableview cell update with last element of the array only.
In cellForRowAtIndexpath array elements are displaying fine.
Consider [ "Value1", "Value2" ] is my array. In tableView only value2 is displaying in two cells.
var title = ["value1","value2"]
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let row = indexPath.row
let x = Id[indexPath.row]
if x == 0{
let cell1 = tableView.dequeueReusableCellWithIdentifier("Cell1", forIndexPath: indexPath) as! MyCell1
return cell1
}
else{
let cell2 = tableView.dequeueReusableCellWithIdentifier("Cell2", forIndexPath: indexPath) as! MyCell2
for index in 0..<myArray.count{
cell2.titleButton.setTitle(title[index],forState:UIControlState.Normal)
}
return cell2
}
}
I am stuck here, your help will be appreciated.
Following is the solution, reason it was going out of range was because value incremented when cell were dequed as cellforrowAtIndexPath was called every time we scrolled down(since some cells were not visible and these cells were dequed when we scrolled down):-
var name = ["HouseBolo","HouseBolo1","HouseBolo2","HouseBolo3"]
var propertyVal:Int = 0
var projectVal:Int = 0
var type = ["Apartment","Villa","Home","Flat","Plot"]
var arrangedData = [String]()
var flatId = [0,1,2,0,0]
override func viewDidLoad() {
super.viewDidLoad()
// I want the Expected Output in Tableview Cell is
// 1. Apartment 2. HouseBolo 3. HouseBolo1 4. Villa 5. Home
for item in flatId {
if item == 0 {
arrangedData.append(type[propertyVal])
propertyVal+=1
}
else {
arrangedData.append(name[projectVal])
projectVal+=1
}
}
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrangedData.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let flatDetails = flatId[indexPath.row]
// For Property Cell
if flatDetails == 0{
let cell = tableView.dequeueReusableCellWithIdentifier("Cell1", forIndexPath: indexPath) as? PropertyCell
if(cell != nil) {
cell!.pType.text = arrangedData[indexPath.row]
}
return cell!
}
// For Project Cell
else {
let cellan = tableView.dequeueReusableCellWithIdentifier("Cell2", forIndexPath: indexPath) as? ProjectCell
if(cellan != nil) {
cellan!.projectName.setTitle(arrangedData[indexPath.row], forState: UIControlState.Normal)
}
return cellan!
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
You need to use :
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! searchCell
in :
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {}
block.
searchCell : is a class of type : UITableViewCell
After that, go in Storyboard and change the identifier of your cell with : "cell"
In the code...
let cell2 = tableView.dequeueReusableCellWithIdentifier("Cell2", forIndexPath: indexPath) as! MyCell2
for index in 0..<myArray.count{
cell2.titleButton.setTitle(title[index],forState:UIControlState.Normal)
}
return cell2
you are iterating over 'myArray' and assigning the value to 'cell2.titleButton'. Cell 2 will always have the last value assigned to it's title. It's assigning it to 'value1', then reassigning it to 'value2'. Looping through the array seems to be the issue (assuming the cells are displaying - just always showing the title from the last item in the array.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
}
You have to add your custom tableview cell class name in the place of UITableViewCell
Something like this -
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> **custom tableview cell class name**
{
}
And also in Storyboard ,change the identifier of your cell with : "cell1" and "cell2"
Related
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 am using table view for selecting objects. I want to select muliple objects in a tableview. I am using following code:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: ContactCell = tableView.dequeueReusableCellWithIdentifier(textCellIdentifier) as! ContactCell
let row = indexPath.row
let person=contacts[row]
cell.setCell(person.nameLabel,image: "")
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let row = indexPath.row
let person=contacts[row]
if let cell = tableView.cellForRowAtIndexPath(indexPath) {
if cell.accessoryType == .Checkmark
{
cell.accessoryType = .None
}
else
{
cell.accessoryType = .Checkmark
}
}
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
My tableview looks like this:
I selected the "Kate" then I scroll down to bottom and "Test" is marked too. But why? I selected just "Kate". How can I prevent this?
It is selected "too", because inside a UITableView cells are reused...
let cell: ContactCell = tableView.dequeueReusableCellWithIdentifier(textCellIdentifier) as! ContactCell
If you want to solve this problem, the best way would be to save each cells state inside the array, which is holding your data of the UITableView... This is the best way.
Another way would be to declare a Dictionary of type [Int: Bool] and save your selected states to this... the Int key would be the row index, and its value could be true for selected, or false for not...
UPDATE
Following an example on how to solve your problem
class CustomTableViewController: UITableViewController {
#IBOutlet weak var contactsTableView: UITableView!
lazy var contactsArray: [[String: AnyObject]] = [[String: AnyObject]]()
//This method is to convert your contacts string array, into the array you need
private func appendContactsToContactsArray (contacts: [String]) {
for contact in contacts {
contactsArray.append(["name": contact, "selected": false])
}
contactsTableView.reloadData()
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return contactsArray.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: ContactCell = tableView.dequeueReusableCellWithIdentifier("Cell") as! ContactCell
cell.textLabel?.text = contactsArray[indexPath.row]["name"] as? String
if (isCellSelectedAtIndexPath(indexPath)) {
cell.accessoryType = UITableViewCellAccessoryType.Checkmark
} else {
cell.accessoryType = UITableViewCellAccessoryType.None
}
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if (isCellSelectedAtIndexPath(indexPath)) {
contactsArray[indexPath.row]["selected"] = false
} else {
contactsArray[indexPath.row]["selected"] = true
}
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
}
private func isCellSelectedAtIndexPath (indexPath: NSIndexPath) -> Bool {
return contactsArray[indexPath.row]["selected"] as? Bool ?? false
}
}
You're seeing this effect because cells are cached and reused. Note the word "Reusable" in dequeueReusableCellWithIdentifier.
Make "selected" a property of a contact or person. Set it true or false when a row is selected or deselected. Reload your data and set the accessory type in cellForRowAtIndexPath.
I have a UITableView with 3 prototyped cells (ex. 1st cell: image, 2nd cell: Description, 3. Links,...).
I would like to hide them if for a cell the data from the backend is empty (Ex. if there is no image, hide the first cell). In order to do that, I have override the heightForRowAtIndexPath function in this way:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
switch (indexPath.row) {
case 0:
if event?.photo_urls.count == 0{
return 0
}
else{
return 80.0
}
case 1:
if event?.description == ""{
return 0
}
else{
return 90.0
}
default:
return 100.0
}
}
and hidden the cell by doing
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
switch (indexPath.row) {
case 0:
let cell = tableView.dequeueReusableCellWithIdentifier("PhotoCell", forIndexPath: indexPath) as! UITableViewCell
if event?.photo_urls.count != 0 {
// ...
}
else{
cell.hidden = true
}
return cell
case 1:
let cell = tableView.dequeueReusableCellWithIdentifier("DesCell", forIndexPath: indexPath) as! UITableViewCell
if event?.description != "" {
// ...
}
else{
cell.hidden = true
}
return cell
default:
let cell = tableView.dequeueReusableCellWithIdentifier("PhotoCell", forIndexPath: indexPath) as! UITableViewCell
return cell
}
}
Until here no problem, it works properly!
Now, THE PROBLEM is that I would like to make the cells dynamics according to the cell contents (ex. description height). In order to do that, I have used
override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 80.0
}
and if I comment the heightForRowAtIndexPath the cells are actually dynamics but I can't hide them anymore.
Do you have any suggestion on how to be able to hide the cells if they are empty and apply the automatic dimension according to their content?
lets say you have dynamic data and you want to show it in tableview so you need to create an array of your data to display.
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet
var tableView: UITableView
var items: [String] = ["We", "Heart", "nothing" ,"" ,"imageurl", "", "xyz"]
override func viewDidLoad() {
super.viewDidLoad()
reloadTableAfterSorting()
self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
func reloadTableAfterSorting(){
for var i = 0; i < self.items.count; i++
{
if self.items[i] == ""{
self.items.removeAtIndex(2)
}
}
self.tableView.reloadData()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items.count;
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:UITableViewCell = self.tableView.dequeueReusableCellWithIdentifier("cell") as UITableViewCell
cell.textLabel?.text = self.items[indexPath.row]
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
println("You selected cell #\(indexPath.row)!")
}
}
For that i recommend you to sort the array before displaying it in the table view. Hiding the cell is not a good idea and its not good according to Apple recommendations. So you can do one thing except hiding the cell: remove the index from the array. In this way you can always have data to show in table and it will behave properly. So don’t try to hide the cell just pop the index from array.
My when a table cell is checked and you scroll down a check mark is repeated.
I know this is due to cell reuse, but don't know how to fix it.
function to populate table
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let item = self.myEvents[indexPath.row]
let cell = tableView.dequeueReusableCellWithIdentifier("row", forIndexPath: indexPath) as! UITableViewCell
cell.textLabel!.text = self.myEvents[indexPath.row]
return cell
}
//function to make the table checkable
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
println("indexpath: \(indexPath)")
let cell: UITableViewCell = tableView.cellForRowAtIndexPath(indexPath)!
let text = cell.textLabel!.text
if cell.accessoryType == UITableViewCellAccessoryType.None {
cell.accessoryType = UITableViewCellAccessoryType.Checkmark
//let text = cell.textLabel!.text
if(checkedEvents[0] == ""){
checkedEvents[0] = text!
}else{
checkedEvents.append(text!)
}
} else {
cell.accessoryType = UITableViewCellAccessoryType.None
var index = 0
for event in checkedEvents{
if event == text{
self.checkedEvents.removeAtIndex(index)
index++
}
}
}
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
First, you need to store the number of the selected row somewhere. How about self.selectedRowNumber?
var selectedRowNumber: Int? = nil
Set this when the user selects a row (short version):
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell: UITableViewCell = tableView.cellForRowAtIndexPath(indexPath)!
cell.accessoryType = .Checkmark
self.selectedRowNumber = indexPath.row
// You'll also need some code here to loop through all the other visible cells and remove the checkmark from any cells that aren't this one.
}
Now modify your -tableView:cellForRowAtIndexPath: method to clear the accessory if it's not the selected row, or add it if it is:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("row", forIndexPath: indexPath) as! UITableViewCell
cell.textLabel!.text = self.myEvents[indexPath.row]
cell.accessoryType = .None
if let selectedRowNumber = self.selectedRowNumber {
if indexPath.row == selectedRowNumber {
cell.accessoryType = .Checkmark
}
}
return cell
}
This code was written here in the browser, and may need some fixes to compile.
If you want only one selection, put tableView.reloadData() in your didSelectRowAtIndexPath function
I have a NSMutableArray (array2) as data source of table view. I want to add object to that array when i select a cell of searchResultsTableView and reload the self.tableView with that array.
If i add object with array2.addObject() method, then all the cells are okay with individual data.
But, if i add object with array2.insertObject(myObject, atIndex: 0), then all the cells show same data as data of array2[0]. Why ?
My problem is in didSelectRowAtIndexPath function of table view. I always want to add the selected object at the first position of my table view, thats why i implemented with insertObject method instead of addObject method. Below is my code portion.
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableView == self.searchDisplayController!.searchResultsTableView {
return self.array1.count
}else{
return self.array2.count
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell()
if tableView == self.searchDisplayController!.searchResultsTableView {
let number = self.array1[indexPath.row]
cell.textLabel?.text = String(number)
} else {
let cell: customCell = self.tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as customCell
let brand = self.array2[indexPath.row] as NSString
cell.name.text = brand
cell.comment.text = "100"
}
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if tableView == self.searchDisplayController!.searchResultsTableView {
let cell = tableView.cellForRowAtIndexPath(indexPath) as UITableViewCell!
self.array2.insertObject(cell.textLabel!.text!, atIndex: 0)
//self.array2.addObject(cell.textLabel!.text!)
self.searchDisplayController!.setActive(false, animated: true)
self.tableView.reloadData()
}
}
your cellForRowAtIndexPath method is weird!... your always returning "let cell = UITableViewCell()" and not in fact your dequeued "cell"
change your method to this:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if tableView == self.searchDisplayController!.searchResultsTableView {
let number = self.array1[indexPath.row]
let cell = UITableViewCell()
cell.textLabel?.text = String(number)
return cell
} else {
let cell: customCell = self.tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as customCell
let brand = self.array2[indexPath.row] as NSString
cell.name.text = brand
cell.comment.text = "100"
return cell
}
}