Must I keep my own UITableView "isSelected" array for multiselect? - ios

I am trying to use a UITableview with multiple selection on and a check mark accessory view for selected rows. This is mostly working if I turn on and off the accessory view in tableView:didSelectRow.
However, I tried to build a selectAll method, and I found that the array of selected cells was being cleared after I had spun through all the cells and selected them if I then call reloadData().
I suspect reloading the table clears selection. I don't know of any other way to have all the cells drawn after I set the selected flag and accessory view.
I am wondering if I need to keep my own array of selected rows. Has anyone else built something like this? Its seems like a common scenario.
Any tips or sample code appreciated.

Take an Array and add the indexPath of each selected cell into it and put a condition in cellForRowAt... that if the Array contains that particular indexPath, set it as selected.

There are two approaches you can take. One is to track the selected row numbers. To do this, you can use an NSMutableIndexSet or its Swift counterpart IndexSet.
Essentially, when a row is selected, add it to the set. When you deselect it, remove it from the set. In cellForRowAtIndexPath you can use containsIndex to determine if a check mark should be shown or not.
Since you explicitly mention an issue with selection when you reload the table, it is worth considering the issue with storing row numbers (whether in a set or an array), and that is that row numbers can change.
Say I have selected rows 4,7 and 9 and these values are stored in the index set. When I reload the data, new data may have been inserted after the "old" row 8, so now I should be selecting rows 4,7 and 10, but I will be selecting 4,7 and 9 still.
A solution to this is to store some sort of unique identifier for the data that should be selected. This will depend on your data, but say you have a string that is unique for each item. You can store this string in a NSMutableSet or Swift Set, which again makes it easy to check if a given item is selected using contains

you need add some functionality in cellForRowAtIndexPath method like this ang your view controller code like this
let we take one example of photo gallery application
class CreateEvent: UIViewController,UITableViewDataSource,UITableViewDelegate {
var yourArray : [Photo] = [Photo]()
//MARK: - Content TableView Methods
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CustomCell", forIndexPath: indexPath)
let objPhoto = yourArray[indexPath.row]
if objPhoto.isPhotoSelected == true
{
cell.accessoryType = .Checkmark
}
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
let objPhoto = yourArray[indexPath.row]
objPhoto.isPhotoSelected = true
let cell = tableView.cellForRowAtIndexPath(indexPath)
cell.accessoryType = .Checkmark
}
//MARK: Action Method
func selectAllPhoto()
{
for objPhoto in yourArray
{
objPhoto.isPhotoSelected = true
}
tableView.reloadData()
}
}
and one more thing you need to create your custom object like
class Photo: NSObject {
var photoName:String = ""
var isPhotoSelected = false
}
hope this will help you

The best approach for multiple selection is
Take a model object, in that take all your attributes and one extra boolean attribute (like isSelected) to hold the selection.
In case of selecting a row
Fetch the relevant object from the array
Then update the isSelected boolean (like isSelected = !isSelected) and reload table.
In case of select all case
Just loop through the array.
Fetch the model object from array.
make the isSelected = true.
After completion of loop, reload the table.

Related

Change variable based on Table View Cell

I need an event, that changes a variable based on which TableViewCell I click. But unlike an action connected to a button, there is no action indicator for table view cells at all. So my question is:
I want to make a TableView that contains items of an array. Based on which item I click, I want to change my variable so that the result on the next ViewController depends on which button you click.
So to make things easier, here is an example what I want the app to look like:
On the first TableViewController I have a list based on an array and on the second ViewController I have a label that shows text based on the variable.
I have a nameArray = ["Mum", "Brother", "Me"] and a weightArray = [140, 160, 120] and a variable weight = 0. The label on the second ViewController tells the var weight. So when you click on "Mum" in the TableView I want the next ViewController to say 140, when I click on "Brother" then 160 and so on...
Until here everything works just fine and I have no problems with anything but changing the var based on what I click.
Long story, short sense:
I want an Action for the TableViewCell that changes the var like in an Action connected to a Button, but there is no Action outlet for Cells at all.
Use this method. Use indexPath.row to find what row number you selected
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var cell : UITableViewCell = tableView.cellForRowAtIndexPath(indexPath)!
switch cell.labelSubView.text as! String {
case "Mum":
self.weight = weightArray[0]
case "Brother"
self.weight = weightArray[1]
and so on..
..
default:
statements
}
}
Note A better alternative
I also considered a case where you have too many entries in nameArray and switch statement might not be good. In that case you can get the text inside the selected row by cell.labelSubView.text as! String
next you can check if the nameArray contains the cell text and get the index of the name that matches the cell text. Next you can get the required weight at the same index in weightArray. And then do self.weight = weightArray[requiredIndex]
Hope this helps.
Update : My experienced friend #Duncan mentioned down below that switch statement in this case is a bad coding practice . I am not going to delete it because it is a lesson for me and also my fellow programmers who are relatively new to programming. So i have put it in a yellow box, stating that it is not a good code
A better option for this would be :
As Duncan mentions, creating an array of dictionary is a good option
Second option is the option in my answer after my Note
You need to maintain array of dictionaries , those dictionaries have keys like "person", and "weight", then you can easily get weight value after selecting the cell by using table view delegate method UITableViewDelegate's tableView:didSelectRowAtIndexPath:
Create an instance variable in your view controller (a var at the top level after the class definition) for the selected cell.
class MyTableViewController: UIViewController
var selectedRow: Int
override func tableView(tableView: UITableView,
didSelectRowAtIndexPath indexPath: NSIndexPath)
{
selectedRow = indexPath.row
//invoke a segue if desired
performSegueWithIdentifier("someSegue");
}
override func prepareForSegue(segue: UIStoryboardSegue,
sender: AnyObject?)
{
if segue.identifier == "someSegue"
{
//Cast the destination view controller to the appropriate class
let destVC = DestVCClass(segue.destinationViewController)
destVC.selectedRow = selectedRow
}
}
As Andey says in his answer, it's probably better to create a single array of data objects (dictionaries, structs, or custom data objects). Then when the user taps a cell, instead of passing the index of the selected row to the next view controller, you could pass the whole data object to the destination view controller. Then the destination view controller could extract whatever data it needed. (Weight, in your example.)

Modifying cell of table view sorted by segmented control edited

I have a table view of tasks. I have also a segmented control above the table that has two choice, the first is for displaying the regular table the other one filters the array of the table to pick the elements that has the same date of the current date and then , I add these new elements to a new array and reload the table view that checks if the selected button in the segmented control is the second one , it will load the elements of filtered array as cells of the table. I have an action on the cell, so if you click them you go to a view where you can change the color of the cell. The problem is happening here. I store the index path of the cell in the table to determine which cell ( row ) is going to be edited, but if the selected segmented button is the second one, the index will be stored according to the order of the element in the filtered table , so when I finish editing the cell, the color will be changed of the cell that has that index not the actual cell because of the conflict between the normal table and the filtered table indexes. Hope that clear enough to explain my case , if it is not, ask me for more explanation.
here is the table loading code :
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete method implementation.
// Return the number of rows in the section.
if sortTableClicked
{
return filteredAchivements.count
}else
{
return achievements.count
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("myCell2", forIndexPath: indexPath) as! AchievementsCells
var arr = [achievement]()
if sortTableClicked
{
arr = filteredAchivements
}else
{
arr = achievements
}
cell.name.text = arr[indexPath.row].name
cell.des.text = arr[indexPath.row].des
cell.date.text = "\(arr[indexPath.row].date) : تم إضافته بتاريخ "
cell.backgroundColor = arr[indexPath.row].cellColor
return cell
}
and I have this function that receives the index path sent from the table and apply the changes depending on this index path.
func applyEdit(color: UIColor, name: String, des: String, index: NSIndexPath) {
taskMgr.editCellDataAtIndex(index, forTable: "achievementsTable", theNewName: name, theNewDes: des, theNewColor: color)
taskMgr.loadData("achievementsTable")
achievements = []
achievements = taskMgr.achievements
tableView.reloadData()
}
what happens is that if the loaded table is the filtered one and let's say I want to edit the second cell , the indexpath.row will be 1 , so the changed- color cell will be the second one on the table , but actually the second cell in the filtered table is the fifth one in the non-filtered table , so I want to apply changes on this fifth one not the second one , does anyone have any idea ?
Problem is that you are saving cell index path which is static and is not changing with change in your table view.
You can fix this easily by adding one more bool property in your model arr that feeds data to table. Name is shouldChangeColor and set this property to true. Now, your model data is updated. Use this property in your cellForRowAtIndexPath function to change the cell colour.
Since cells are drawn in the order you supply your data from model, this would guarantee that your 2nd cell is painted in colour in filtered mode and 5th in other tab mode.

Loading coredata for two collection views and one table view in swift

So I have this issue where I have to load one entity on two collection views, and one table view. The thing is, primarily I just had to load one entity on the VC where the tableview is located. And that data entity has these parameters for i.e.:
Engine type (diesel or petrol)
Car color
Max.velocity
Year of production
Now, the main point was, to color the cells on the tableview depending on the type of the car engine. So, if the data Entity "Car" has a Bool value of "isEngineDiesel" = true, then the cell would be orange, if false, then it would be light blue. And this worked just fine, a simple if statement on the table view delegate method for loading such cells. But, now I had to implement another VC which has two collection views, in which, the first one loads ONLY Diesel engine Car entity's, and the other Petrol type.
So I guess the issue is already clear here. How can I accomplish this? Because after countless hours of experimenting the only idea I had was two make TWO entity's in which the one is DieselCar and the other PetrolCar. But that means changing the complete data structure, and also, instead of one table view I would actually need two, which doesn't seem like a good idea due the fact that it would "overflow" with all the data there is.
So...any ideas gents?
EDIT:
So far I've only managed to get the cell titles, but the return value of number of cells is still a mystery on how to solve.
The Code for collection view delegate:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! CollectionViewCell
if let carData = fetchedResultsController?.objectAtIndexPath(indexPath) as? Car {
cell.layer.cornerRadius = 20
cell.layer.masksToBounds = true
if collectionView == collectionViewDiesel {
if carData.isEngineDiesel == true {
cell.backgroundColor = UIColor(netHex: 0x8DF060)
// Display the cell name
cell.cellTitle.text = carData.cellTitle
}
}else if collectionView == collectionViewPetrol {
if carData.isCarDiesel == false {
cell.backgroundColor = UIColor(netHex: 0xEB9B2D)
// Display the cell name
cell.cellTitle.text = carData.cellTitle
}
}
}
return cell
}
And this is the method I need answered:
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
On the other VC you need data structure for two collection views so I think make a dictionary from your data base on the isEngineDiesel if true add objects in one array and if false in another array then set those arrays for the respective diesel and petrol keys .
In you collection view delegate there always a UICollectionView argument place check on that (you can use tags or even == operator if you have outlet) to load the data from dictionary base on keys .

Converting NSIndexPath to NSInteger in Swift

I have a UITableView with a segue that connects to a view controller. When the user taps on a cell, I would like the indexPath.row to be passed to that view controller so I can update the content in the view based on which cell has been tapped.
I am aware of the different methods of doing this, however I would like to use NSUserDefaults() for my particular use. The problem is that I don't know how to convert the indexPath.row into an integer to be saved into NSUserDefaults(). This is what I have tried:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
println(indexPath.row)
NSUserDefaults.standardUserDefaults().setObject(NSInteger(indexPath.row), forKey: "index")
self.performSegueWithIdentifier("event", sender: self)
}
Second View Controller:
override func viewDidAppear(animated: Bool) {
println(NSUserDefaults.standardUserDefaults().integerForKey("event"))
}
At the moment, when I try to print out the indexPath.row in the next view controller, it always just returns 0.
You aren't using the same key
NSUserDefaults.standardUserDefaults().setObject(NSInteger(indexPath.row), forKey: "index") // <--- INDEX
NSUserDefaults.standardUserDefaults().integerForKey("event")) // <-- EVENT
Also you :
Might want to save it / read it with the equivalent method setInteger:forKey: and integerForKey:
Don't have to convert the value (it's already an integer) ;
Make sure you don't paint yourself in a corner. An NSIndexPath contains two numbers, not one: A section and a row within that section. In trivial cases when a table view has only one section the section number will always be zero, but you will get more complicated cases.
If you want to store index paths, I would add a category to NSUserDefaults with one method that turns an NSIndexPath into a dictionary with keys section and row and writes that, and one that reads a dictionary and creates an NSIndexPath with the values for section and row.
That said, a row number is something horribly unstable. Your UI designers decide that two rows have to change their order, and all your preferences are broken. What I do is one enum that identifies the purpose of each row; that enum must never, ever, ever change its values, and that is stored in the preference. And then there is another enum that refers to items in the UI, and that can change freely.
Saving A Row(integer) Value :
NSUserDefaults.standardUserDefaults().setInteger(indexPath.row, forKey: "row")
NSUserDefaults.standardUserDefaults().synchronize()
Loading A Row(integer) Value:
row() -> Int
var row = NSUserDefaults.standardUserDefaults().integerForKey("row")

Pass table cells textlabel data to array in swift

I want to pass table cell's textLabel data of UITableViewController to NSArray. Those cell have identifier name Cells and accessory type checkmark
Code that does exactly what you asked is here:
func getCellsData() -> [String] {
var dataArray: [String] = []
for section in 0 ..< self.tableView.numberOfSections() {
for row in 0 ..< self.tableView.numberOfRowsInSection(section) {
let indexPath = NSIndexPath(forRow: row, inSection: section)
let cell = self.tableView.cellForRowAtIndexPath(indexPath)!
if cell.reuseIdentifier == "Cells" && cell.accessoryType == UITableViewCellAccessoryType.Checkmark {
dataArray.append(cell.textLabel!.text!)
}
}
}
return dataArray
}
But I would like to recommend you find different approach, because this is the rude traverse of tableView. You probably have your dataSource model that can give you ll data you need. Additionally, this code doesn't check for errors, for example if there is no text in cell at some indexPath
Two things I'd advise.
First, looks like you're using the checkmark accessory to indicate multiple selections. It's really intended for single selection, like a radio button. Better to use the allowMultipleSelectionsoption on the tableview. This will allow...
...the second thing. Copying text from cells into an array is the wrong way round to do it. Better to ask the table view and call it's - (NSArray *)indexPathsForSelectedRows this will give you an array of index paths to selected cells then you can ask for each cell and grab any data you want from it. This gives you better live data and prevents you unknowingly creating a circular reference from the view controller of the tableview and the content in the tableview cells.

Resources