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")
Related
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.
I have a UITableView which displays a list of strings from an array. The array is in alphabetical order, and at the top of the UITableView is a UISearchController bar. Everything is working fine. However, I need to make a modification to the list that is presented in the UITableView where a certain subset within the collection is presented at the top of the UITableView (and this subset should ALSO be in alphabetical order). However, once the user enters a character inside the search filter at the top, the list of strings displayed shouldn't matter.
For example, I have a UITableView which displays a list of employees in alphabetical order. What I want to do is when the UITableView loads, instead of presenting ALL the employees in alphabetical order, I would like to first list the managers in alphabetical order, and then the remaining employees in alphabetical order (i.e. AFTER the managers).
The array being received in the ViewController which holds the UITableView from the previous ViewController, which sends this list already sorted in alphabetical order. In other words, the array to begin with, is received already sorted.
I'm assuming you don't want to use sections? That you just want them to all in the same section?
If that's the case you would probably need to do some preprocessing to split the array into your subsets (in viewDidLoad or somewhere else at the beginning of your controller's life cycle):
self.managerArray = [AnyObject]() // you'll need a property to hold onto this new data source
self.employeeArray = [AnyObject]()
for employee: Employee in self.allEmployees {
// assume allEmployees is the alphabetical array
if employee.isManager {
// your condition for determining the subset
managerArray.append(employee)
}
else {
employeeArray.append(employee)
}
}
Because the array is already alphabetized this should populate the subarrays in alphabetical order as well (append just adds to the next index).
Then to make sure the table doesn't load the values before it's been processed in this way you'd need to have
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if self.employeeArray && self.managerArray {
return self.employeeArray.count + self.managerArray.count
}
return 0
}
Then you can just just populate cells from self.managerArray before moving onto self.employeeArray.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.row < self.managerArray.count {
var manager: Employee = self.managerArray[indexPath.row]
// populate your cell info here
}
else {
// else we've already filled in manager's time to start on employees
// subtract the number of managers from the indexPath to start at beginning of employeeArray
var employee: Employee = self.employeeArray[indexPath.row - self.managerArray.count]
// populate cell here
}
return cell
}
Then when you search, you can search on the original self.allEmployees array like normal.
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.)
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.
I noticed in that to disable the highlight feature of a table cell you would yse the following method:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
...
tableView.deselectRowAtIndexPath(indexPath, animated: false)
}
What I don't understand is what's going on in the first argument indexPath. In the instructional book I've been reading just about every other method has been using indexPath.row for selecting individual cells:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "Cell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as CustomTableViewCell
cell.nameLabel.text = restaurantNames[indexPath.row]
cell.typeLabel.text = restaurantTypes[indexPath.row]
cell.locationLabel.text = restaurantLocations[indexPath.row]
}
Just out of curiosity I tried passing indexPath.row as an argument but got the error, 'NSNumber' is not a subtype of 'NSIndexPath'. In this particular case what is the main difference between using indexPath and indexPath.row.
From the NSIndexPath class reference:
The NSIndexPath class represents the path to a specific node in a tree
of nested array collections. This path is known as an index path.
Table views (and collection views) use NSIndexPath to index their items
because they are nested structures (sections and rows). The first index is the section of the tableview and the second index
the row within a section.
indexPath.section and indexPath.row are just a "convenience" properties, you always have
indexPath.section == indexPath.indexAtPosition(0)
indexPath.row == indexPath.indexAtPosition(1)
They are documented in NSIndexPath UIKit Additions.
So NSIndexPath is an Objective-C class and is used by all table view
functions. The section and row property are NSInteger numbers.
(Therefore you cannot pass indexPath.row if an NSIndexPath is expected.)
For a table view without sections (UITableViewStyle.Plain) the
section is always zero, and indexPath.row is the row number of
an item (in the one and only section). In that case you can use
indexPath.row to index an array acting as table view data source:
cell.nameLabel.text = restaurantNames[indexPath.row]
For a table view with sections (UITableViewStyle.Grouped) you have
to use both indexPath.section and indexPath.row (as in this
example: What would be a good data structure for UITableView in grouped mode).
A better way to disable the selection of particular table view rows is
to override the willSelectRowAtIndexPath delegate method:
override func tableView(tableView: UITableView, willSelectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath? {
// return `indexPath` if selecting the row is permitted
// return `nil` otherwise
}
In addition, to disable the appearance of the cell highlight on touch-down, you can also set
cell.selectionStyle = .None
when creating the cell. All this is (for example) discussed in
UITableview: How to Disable Selection for Some Rows but Not Others.
Your question and confusion stems from the fact that Apple decided to use somewhat imprecise method name/signature
tableView.deselectRowAtIndexPath(indexPath, animated: false)
The "Row" word in the method signature implies that row is going to be deselected, therefore one would expect a row as a method parameter. But a table view can have more than one section. But, in case it doesn't have more sections, the whole table view is considered one section.
So to deselect a row (a cell really) in table view, we need to tell both which section and which row (even in case of having only one section). And this is bundled together in the indexPath object you pass as a parameter.
So the before-mentioned method might instead have a signature
tableView.deselectCellAtIndexPath(indexPath, animated: false)
which would be more consistent, or another option, albeit a bit less semantically solid (in terms of parameters matching the signature)
tableView.deselectRowInSectionAtIndexPath(indexPath, animated: false)
Now the other case - when you implement the delegate method
tableview:cellForRowAtIndexPath
here again (Apple, Apple...) the signature is not completely precise. They could have chose a more consistent method signature, for example
tableview:cellForIndexPath:
but Apple decided to stress the fact that we are dealing with rows. This is an 8 year old API that was never the most shining work of Apple in UIKit, so it's understandable. I don't have statistical data, but I believe having a single section (the whole tableview is one section) is the dominant approach in apps that use a table view, so you don't deal with the sections explicitly, and work only with the row value inside the delegate method. The row often serves as index to retrieve some model object or other data to be put into cell.
Nevertheless the section value is still there. You can check this anytime, the passed indexPath will show the section value for you when you inspect it.
NSIndexPath is an object comprised of two NSIntegers. It's original use was for paths to nodes in a tree, where the two integers represented indexes and length. But a different (and perhaps more frequent) use for this object is to refer to elements of a UITable. In that situation, there are two NSIntegers in each NSIndexPath: indexPath.row and indexPath.section. These conveniently identify the section of a UITableView and it's row. (Absent from the original definition of NSIndexPath, row and section are category extensions to it).
Check out tables with multiple sections and you'll see why an NSIndexPath (with two dimensions) is a more general way to reference a UITableView element than just a one-dimensional row number.
i see the perfect answer is from apple doc
https://developer.apple.com/documentation/uikit/uitableview/1614881-indexpath
if you print indexpath you may get [0,0] which is first item in the first section
I was confused between indexPath and indexPath.row until i understand that the concept is the same for the TableView and the CollectionView.
indexPath : [0, 2]
indexPath.section : 0
indexPath.row : 2
//
indexPath : the whole object (array)
indexPath.section : refers to the section (first item in the array)
indexPath.row : refers to the selected item in the section (second item in the array)
Sometimes, you may need to pass the whole object (indexPath).