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.
Related
I have a tableView that allows users to make multiple selections from an array of data,
When the user clicks done, I would like the selected text to be then transferred over to another tableViews textView
Is there a way to transfer over the selected text and have the text separated by a , ?
I am coding programmatically.
var checked = [Int]()
var items = [String]()
var selectedItems = [String]()
#objc func done() {
let hud = JGProgressHUD(style: .dark)
hud.textLabel.text = "Saving!"
hud.show(in: view)
dismiss(animated: true, completion: nil)
hud.dismiss()
let aCell = aboutCell(style: .default, reuseIdentifier: nil)
aCell.textField3.text = selectedItems.joined(separator: ",")
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if tableView.cellForRow(at: indexPath)?.accessoryType == UITableViewCell.AccessoryType.checkmark {
tableView.cellForRow(at: indexPath)?.accessoryType = UITableViewCell.AccessoryType.none
} else {
tableView.cellForRow(at: indexPath)?.accessoryType = UITableViewCell.AccessoryType.checkmark
if selectedItems.contains(items[indexPath.row]) {
selectedItems.remove(at: selectedItems.firstIndex(of: items[indexPath.row])!)
} else {
selectedItems.append(items[indexPath.row])
}
checked.append(indexPath.row)
}
}
According to my understanding to the question, these are my thoughts:
1. First setup necessary variables
var items = [String]() // data to display in tableview
var selectedItems = [String]() // here all the selected datas are stored
2. Store the selected items data from the didSelectRowAt delegate method
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if selectedItems.contains(items[indexPath.row]) { //check if the selected already contains the items and if contains remove it
selectedItems.remove(at: selectedItems.firstIndex(of: items[indexPath.row])!)
} else { // append the required items
selectedItems.append(items[indexPath.row])
}
// ..... other codes here
}
3. on done button
let requiredText = selectedItems.joined(separator: ",")
// pass this data through delegate method
There is no need to create an array for the selected items. You can simply call tableview method selectRow(at:animated:scrollPosition:) when selecting a row and when you need to get the selected rows just call tableview instance property indexPathsForSelectedRows. Then you just need to join the selected rows with a comma and use the resulting string in your textview or textfield. Don't forget to implement didDeselectRowAt item method as well to deselectRow.
import UIKit
class TableViewController: UITableViewController {
var items: [String] = ["1st", "2nd", "3rd", "4th", "5th"]
override func viewDidLoad() {
super.viewDidLoad()
tableView.allowsMultipleSelection = true
}
override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
if let cell = tableView.cellForRow(at: indexPath) {
cell.accessoryType = .none
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.selectRow(at: indexPath, animated: true, scrollPosition: .none)
if let cell = tableView.cellForRow(at: indexPath) {
cell.accessoryType = .checkmark
}
}
func done() {
if let indexPaths = tableView.indexPathsForSelectedRows {
// note that this will preserve the order that the rows where selected. Just sort the indexPaths if you need it sorted.
let string = indexPaths.map { items[$0.row] }.joined(separator: ",")
print(string)
// your code
}
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCellID", for: indexPath) as! TableViewCell
cell.textLabel?.text = items[indexPath.row]
cell.accessoryType = cell.isSelected ? .checkmark : .none
return cell
}
}
Trying to develop a checklist app and have been stuck for a while trying to save the state of a checkmark. When I go off the tableView and back on all of the saved checkmarks are erased. I have imported UKIT then defined the class.
Here is my code:
var PreDefinedTasks = ["1", "2", "3", "4"]
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
if let cell = tableView.cellForRow(at: indexPath as IndexPath) {
if cell.accessoryType == .checkmark{
cell.accessoryType = .none
}
else{
cell.accessoryType = .checkmark
}
}
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return PreDefinedTasks.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "List1", for: indexPath)
cell.textLabel?.text = PreDefinedTasks[indexPath.row]
return cell
}
I have looked into NSCoder as a solution but cant seem to get it to work properly. Any help is appreciated!
Here is how I would go about it, if you follow the whole solution, it will save even when the app closes.
Make an array off type Bool like this: var checkmarks = [Int : Bool]()
Then, in cellForRow function, add this:
if checkmarks[indexPath.row] != nil {
cell.accessoryType = checkmarks[indexPath.row] ? .checkmark : .none
} else {
checkmarks[indexPath.row] = false
cell.accessoryType = .none
}
And in the didSelectRow function, add this:
if let cell = tableView.cellForRow(at: indexPath as IndexPath) {
if cell.accessoryType == .checkmark{
cell.accessoryType = .none
checkmarks[indexPath.row] = false
}
else{
cell.accessoryType = .checkmark
checkmarks[indexPath.row] = true
}
}
If you want it to save when the app closes, you have to save the checkmarks array in UserDefaults by doing this:
In didSelectRow function, at the bottom after everything add this:
UserDefaults.standard.set(NSKeyedArchiver.archivedData(withRootObject: checkmarks), forKey: "checkmarks")
UserDefaults.standard.synchronize()
Then also, in viewDidLoad, add this:
if let checks = UserDefaults.standard.value(forKey: "checkmarks") as? NSData {
checkmarks = NSKeyedUnarchiver.unarchiveObject(with: checks as Data) as! [Int : Bool]
}
Let me know if this answer helped and if you have any questions.
EDIT:
So what I completely forgot about is that [Int : Bool] is not an NSDictionary, it is just a Dictionary. UserDefaults can't store Dictionary objects, only NSDictionary which is why you have to change it to NSData so it is able to save the [Int : Bool]. Hope it works this time :)
You can save the indexpath in your array based on checkmark selected on your tableView rows.
As per you implementation, the UITableViewCell is getting reused with identifier as "List1". So if you want to re-use the cell then you have to keep updating the correct accessoryType by storing the state against the predefines tasks.
Since cells get unloaded and later reused, you'll need to store the state of the checkmark elsewhere. In this example in an array called preDefinedTaskCheckmarkState. When loading the cells you'll also need to set the checkmark state.
var PreDefinedTasks = ["1", "2", "3", "4"]
var preDefinedTaskCheckmarkState = [Bool](repeating: false, count: 4)
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
if let cell = tableView.cellForRow(at: indexPath as IndexPath) {
preDefinedTaskCheckmarkState[indexPath.row] = !(cell.accessoryType == .checkmark)
cell.accessoryType = preDefinedTaskCheckmarkState[indexPath.row] ? .checkmark : .none
}
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return PreDefinedTasks.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "List1", for: indexPath)
cell.textLabel?.text = PreDefinedTasks[indexPath.row]
cell.accessoryType = preDefinedTaskCheckmarkState[indexPath.row] ? .checkmark : .none
return cell
}
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"
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.
This is a followup to a question I asked yesterday, I thought I had a solution based upon the answer received, but I'm having a new difficulty.
I have a single ViewController with two UITableViews (PropertyTypeListand PropertyDetailList)
I essentially want to treat this like a Master Detail View, When a row in PropertyTypeList is selected PropertyDetailList updates to an array based upon that selection. (I need it to be in one ViewController)
The problem was that I couldn't get the row selected from didSelectRowAtIndexPath into cellForRowAtIndexPath where I populate the tables through an If Statement to determine which tableview is in play.
I am now getting the currently selected row through a variable (currentRowInTypeList) and using that value to switch which array is loaded into PropertyDetailList.
Even though currentRowInTypeList is updating with each click the table isn't. What am I missing?
my code follows:
#IBOutlet weak var propertyTypeList: UITableView!
#IBOutlet weak var propertyDetailList: UITableView!
var testarray = ["test1", "test2"]
var testarray2 = ["test3", "test4"]
var currentRowInTypeList: Int = 0
func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int
{
if tableView == propertyTypeList {
return projectSources.count;
}
else {
if currentRowInTypeList == 0 {
return testarray.count
} else {
return testarray2.count
}
}
}
func tableView(tableView: UITableView!,
cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell!
{
let cell:UITableViewCell = UITableViewCell(style:UITableViewCellStyle.Default, reuseIdentifier:"Cell")
if tableView == propertyTypeList {
cell.textLabel?.text = projectSources[indexPath.row]
return cell
}
else
{
if currentRowInTypeList == 0 {
cell.textLabel?.text = testarray[indexPath.row]
return cell
} else {
cell.textLabel?.text = testarray2[indexPath.row]
return cell
}
}
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
let cell:UITableViewCell = UITableViewCell(style:UITableViewCellStyle.Default, reuseIdentifier:"Cell")
if tableView == propertyTypeList {
currentRowInTypeList = indexPath.row
println(currentRowInTypeList)
} else {
}
}
Just call propertyDetailList.reloadData()
In your code:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
let cell:UITableViewCell = UITableViewCell(style:UITableViewCellStyle.Default, reuseIdentifier:"Cell")
if tableView == propertyTypeList {
currentRowInTypeList = indexPath.row
propertyDetailList.reloadData()
println(currentRowInTypeList)
} else {
}
}