Two sections with one uiswitch? - Swift - ios

I have a working switch and a switch state that keeps track on which switch is flipped on using the Indexpath's row number and bool stored in a dictionary. While that works excellent for one section only. I am having a difficult time prevent it from spilling over to the next section as seen here:
Section 0 Row 3 switch turned on
Section 1 Row 3 switch turned on without me pressing it.
Is there a way to only keep switches on for that particular section? Right now I'm using two prototype cells one for the data I'm displaying which includes only one switch and the other cell for displaying the section header.
Here is some of the code I guess would be helpful in seeing what I put down:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("advCell", forIndexPath: indexPath) as! advDataCell
cell.advDelegate = self
switch(indexPath.section) {
case 0:
cell.lblCategoryItem.text = foodCategories[indexPath.row]["name"]
case 1:
cell.lblCategoryItem.text = activitiesCategories[indexPath.row]["name"]
default:
return cell
}
if advSwitchStates[indexPath.row] != nil {
cell.advOnOffSwitch.on = advSwitchStates[indexPath.row]!
}
else {
cell.advOnOffSwitch.on = false
}
cell.advOnOffSwitch.on = advSwitchStates[indexPath.row] ?? false
return cell
}
func switchCell(advSwitchCell: advDataCell,didChangeValue value: Bool) {
let indexPath = tableView.indexPathForCell(advSwitchCell)!
print("This advanced filter controller has received the switch event.")
advSwitchStates[indexPath.row] = value
}
And what I am using to store the switch states:
var advSwitchStates = [Int: Bool]()

In your cellForRowAtIndexPath you dequeue a recycled cell, like you are supposed to do. You need to fully configure every view in the cell, IN ALL CASES. That means that in ALL cases you need to set a value for your advOnOffSwitch.
In your cellForRowAtIndexPath method you have a switch statement for section values of 0, 1, or any other value. If the section value is not 0 or 1, you return without setting the state of the advOnOffSwitch. If you recycle a cell that had advOnOffSwitch set on, it will stay on, which you don't want. Change your switch statement like this:
switch(indexPath.section) {
case 0:
cell.lblCategoryItem.text = foodCategories[indexPath.row]["name"]
case 1:
cell.lblCategoryItem.text = activitiesCategories[indexPath.row]["name"]
default:
cell.advOnOffSwitch.on = false
return cell
}
With that code, you force the switch to the off position to start for sections other than section 0 or 1.

There are two different issues, first get rid of the early return when setting the name of the cell:
switch(indexPath.section) {
case 0:
cell.lblCategoryItem.text = foodCategories[indexPath.row]["name"]
default:
cell.lblCategoryItem.text = activitiesCategories[indexPath.row]["name"]
}
Secondly you need to track the toggle state with both the section and row as keys since you have multiple sections. The easiest way to do this is probably to make the key of advSwitchStates to be an NSIndexPath.
e.g. declare it as:
var advSwitchStates = [NSIndexPath: Bool]()
and then in cellForRowAtIndexPath
cell.advOnOffSwitch.on = advSwitchStates[indexPath] ?? false

Related

Check if at least one cell is selected in each section of a UITableView

I want to create a simple method that will determine whether or not at least one cell in each section is selected.
Something like this:
func checkSections () ->Bool {
let array : [IndexPath] = self.selectedIndexPaths.copy() as! [IndexPath];
for indexPath: IndexPath in array {
let SectionString : String? = self.sectionHeaderTitleArray[indexPath.section] // this will return the names of the selected sections but no way to use that
// check all the sections and see if they contain a selected cell
}
}
I know it's incomplete, but I have no idea how to start creating it and I'm hoping to find help to start the func at least. Please understand that I haven't touched anything programming related in 5+ years and I was never a professional
You can get the selected indexPaths as follows:
let array: [IndexPath] = tableView.indexPathsForSelectedRows
And then something like:
for indexPath: IndexPath in array {
if let section = indexPath.section {
// yes, we have a selected cell in this section
}
}
This should do the trick
/// Checks if the number of selected sections equals
/// the number of sections in the table view
func checkSections() -> Bool {
let selectedSections =
Set(tableView.indexPathsForSelectedRows?.map { $0.section } ?? [])
return selectedSections.count == tableView.numberOfSections
}
You also need to set tableView.allowsMultipleSelection = true to allow multiple selections in multiple sections of your table view.

How to add checkbox to table cell

I am working on a project where I program a shopping app. I have 2 Table Views in a big UIViewController.
As you can see from picture, my 1st table called products and my 2nd table piesa. My 1st table works as a list of the items I have on stock (plafon, plafon cu 2 parti culisante, etc), when i tap one of the items from the table, it will remove it from thereand add it to my 2nd tabel. It works like a shopping list. I implemented this part. I am stuck on the 2nd part.
What do I want now is: on the items I have in table 2, for example the 1st cell plafon to have 4 checkboxes on him. It will work something like that What do you want to do with your item? Replaced, paint it, repair or or repaint it. To have 4 options.
My question is, how do I add checkboxes to each table cell from 2nd table. Is there any way to make checkboxes depend on each other (for example, can't have 2 checkboxes active at same time: repaint and paint, only one or 2 that differ like repair and repaint).
I am a starter newb in iOS, thanks in advance.
You should not have 2 tables in a controller, in fact is not a good practice.
What you need are 2 sections and nothing else. With that, you could use only one table.
The rest is logic problem and nothing else.
You must add a new key in the object, which tells you if you are selected or not.
In your custom cell (I hope and assume that you must be using one). Create a button to which you going to change the state based on the new key that you added (remember this key must be a boolean, for example "isSelected" = true).
The object that you need should be something like:
data =
(
{
sectionName: "Piese disponible"
rows: (
{
name: "Plafon",
isSelected: false //if need this param in this section
},
{
name: "Plafon cu 2 parti culisante",
isSelected: false //if need this param in this section
},
{
name: "etc etc etc",
isSelected: false //if need this param in this section
}
)
},
{
sectionName: "Piese"
rows: (
{
name: "Plafon",
isSelected: false //for the checkbox btn
}
)
}
)
In your didSelectRowAtIndexPath Delegate method you need change the isSelected key.
let cell = tableView.cellForRowAtIndexPath(indexPath) as! yourCustomTableViewCell
let eachRow = yourArray.objectAtIndex(indexPath.row)
let isSelected = eachRow["isSelected"] as! Bool
if isSelected == false {
eachRow.setValue(true, forKey: "isSelected")
}
tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: indexPath.row, inSection: indexPath.section)], withRowAnimation: .Fade)
And i you need unselect the last selected cell, create a loop and search the object with the isSelected key in true and set to false and use reloadRowsAtIndexPaths in that cell
Something like:
for eachInfo in yourArray {
let isSelectedRow = eachRow["isSelected"] as! Bool
if isSelectedRow == true {
eachInfo.setValue(false, forKey: "isSelected")
tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: i, inSection: theSection)], withRowAnimation: .Fade)
break
}
i += 1
}
However, considering that the cells are reused and must rendering your button with the new state must put your logic in cellForRowAtIndexPath delegate method.
Something Like:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "yourcustomCellId"
var cell: yourCustomTableViewCell! = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as? yourCustomTableViewCell
if cell == nil {
tableView.registerNib(UINib(nibName: "yournibCellname", bundle: nil), forCellReuseIdentifier: cellIdentifier)
cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as? yourCustomTableViewCell
}
cell.selectionStyle = UITableViewCellSelectionStyle.None
let eachRow = yourArray.objectAtIndex(indexPath.row)
let isSelected = eachRow["isSelected"] as! Bool
var imageNamebtn = "unselectedImage"
if isSelected == true {
imageName = "selectedImage"
}
cell.yourButon.backgroundImage = UIImage(named: imageName)
return cell
}
For Build The count of sections you need use numberOfSectionsInTableView from UITableViewDataSource and for build the section design need viewForHeaderInSection.
I hope I've helped
You are going to need to make a custom cell. You can change whatever you want to the UI for the custom cell and then hook up the exclusive logic inside your code. Here is a tutorial on how to use custom cells. Skip down to the part that starts with Designing Your Own Prototype Cells. Hope this helps!

weird behavior on UItableView that is not inside UITableViewController

I have a UIViewController, and when a user clicks a button, I want to show a UITableView. I receive the data from the server.
The problem is that there is something weird happening. Sometimes, not all the cells are updated. In other words, in my prototype cell, there are two buttons, with default text, but when I load the data from server not all the buttons text are updated, instead when I scroll the table, they become updated. Also when I **scroll* the table, sometimes the buttons go back to the default text.
Here are my code: (really easy)
class CusinePreferencesTableView: NSObject, UITableViewDelegate, UITableViewDataSource {
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(CellIdentefiers.oneCusinePreferencesCell.rawValue, forIndexPath: indexPath) as! OneCusinePreferencesTableViewCell
var row = indexPath.row
print("row = \(row)")
let oneCusineDataLeft = Preferences2ViewController.cusines![row]
cell.leftButton.titleLabel?.text = oneCusineDataLeft
row = row + 1
if row < Preferences2ViewController.cusines!.count{
let oneCusineDataRight = Preferences2ViewController.cusines![row]
cell.rightButton.titleLabel?.text = oneCusineDataRight
}else {
//I should hide the right button
}
return cell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let cusines = Preferences2ViewController.cusines {
if cusines.count % 2 == 0 {
return cusines.count/2
}else {
var count = cusines.count/2
count = count + 1
return count
}
}else {
return 0
}
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
}
And this is the table view (in case you want it)
If you see that you didn't get the description of my problem, I can make a video for you
Update 1
I already call the reloadData as you see here (where I get the data from the server)
func loadCusiens(){
let url = NSURL(string: ConstantData.getWebserviceFullAddress()+"preferences/cusines")
let request = NSMutableURLRequest(URL: url!)
request.HTTPMethod = "POST"
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request, completionHandler: {(data, response, error ) in
if let error = error {
print("error = \(error)")
}
if let data = data {
do{
let jsonArray = try NSJSONSerialization.JSONObjectWithData(data, options: []) as! NSArray
var results = [String]()
for oneJSON in jsonArray {
let name = oneJSON["name"] as! String
results.append(name)
}
dispatch_async(dispatch_get_main_queue(), {
Preferences2ViewController.cusines = results
self.cusineTableView.reloadData()
})
} catch{
print("This exception happened = \(error)")
}
}
})
task.resume()
}
Update
Now in the else part, i set the text of the right button to "sometext"
this is a video about the problem
http://www.mediafire.com/watch/k2113ovebdvj46d/IMG_0911.MOV
Update
The main problem is not cell reuse, nor the layout problem I explain below, but the use of:
cell.leftButton.titleLabel?.text = oneCusineDataLeft
to set the button text. You must use:
cell.leftButton.setTitle(oneCusineDataLeft, forState: .Normal)
instead. (In essence, a UIButton keeps track of the title text it should display when it is in different "states" - normal, selected, etc. Although the your code changes the text that is currently displayed, as soon as the button changes state, it sets the text back to the stored value. I assume the button state is reset when the cells are displayed. The setTitle method updates the stored value).
Original
Setting aside the cell reuse problem, I don't think your code will achieve quite what you want. As currently coded, the layout would be something like this:
Row 0: left: cuisine 0, right: cuisine 1
Row 1: left: cuisine 1, right: cuisine 2
Row 2: left: cuisine 2, right: cuisine 3
I'm guessing you actually want this:
Row 0: left: cuisine 0, right: cuisine 1
Row 1: left: cuisine 2, right: cuisine 3
Row 2: left: cuisine 4, right: cuisine 5
If that's the case, amend your code as follows:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(CellIdentefiers.oneCusinePreferencesCell.rawValue, forIndexPath: indexPath) as! OneCusinePreferencesTableViewCell
var row = indexPath.row
print("row = \(row)")
let oneCusineDataLeft = Preferences2ViewController.cusines![2*row]
cell.leftButton.titleLabel?.text = oneCusineDataLeft
if (2*row+1) < Preferences2ViewController.cusines!.count{
let oneCusineDataRight = Preferences2ViewController.cusines![2*row+1]
cell.rightButton.titleLabel?.text = oneCusineDataRight
}else {
//I should hide the right button
cell.rightButton.titleLabel?.text = ""
}
return cell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let cusines = Preferences2ViewController.cusines {
if cusines.count % 2 == 0 {
return cusines.count/2
}else {
return (cusines.count+1)/2
}
}else {
return 0
}
}
I should also note that you would probably be better off using a UICollectionView rather than a UITableView, since the former will provide the multiple column flow much more easily and intuitively.
Looks like a reuse problem. Basically if you follow the examples in Apple and other places, it uses a dequeue function. What this means is that iOS will look for an already existing cell that is no longer being shown and give you that to draw the contents of a new cell. But of course if the old cell had a bunch of stuff set, you will see the old stuff. The solution is to always assign everything to the correct new value.
This can get you easily if your cells have variable information, for instance if there's a picture you set if a certain entry has it, but isn't set if it doesn't have it. What will happen then is when you get an entry with no picture your code might not set it the image to nil, and you'll see the picture of an old cell that was scrolled off.
Please let me explain the reuse of UITableViewCells works
Assume you have a very huge cells and you can see only 3 cells on you screen.
Assume now you see on your screen the following cells:
A
B
C
On the screen... and your datasource is: A B C C B A.
Now you are scrolling down for 1 cell exactly:
So the cell A is going from the Up (it was first cell) down and becoming your last visible cell
So now on the screen you will see
B
C
A // While you want to draw a cell `C` here according to the dataSource
What happens now in cellForRowAtIndexPath...
You are getting the same Cell A from the beginning.
Assume your cell A had only 1 button and a Cell C need to show 2 buttons
Because you have already draw the cell A you draw it with 1 button (lets assume you set the right button hidden property to YES).
So now you want to draw your cell C on the reused cell A
(Assume you set TAGS for the cell types to know what type of cell is popped up to you in the cellForRowAtIndexPath)
And now you can query the tag of the cell
if(myCell.tag == `A`/*(Assume you have an Enum for tags)*/)
{
//So here you will want to Unhide the Right button of the cell (which is currently still cell A
//After you have done with all needed adjustments you will want to set the appropriate tag for this cell to be ready for the next reuse -> so set it's tag to C
//Now next time this cell will get reused it will be drawn as cell `C` and it will think it is a cell `C`
}

Returning multiple dequeueReusableCells in a TableView, Swift

The problem I'm trying to solve is to return more than one cell for the loaded task, since i cannot iterate without getting a brand new task loaded.
Each view has one or more cell that is supposed to be loaded for that specific task and i can't seem to solve the problem. Thank you for helping out!
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let categoryCells = self.tasks[indexPath.row] as? Task
var cellChooser: BoredTaskCell!
if selectedTaskCategory == "Movie"{
let cell = tableView.dequeueReusableCellWithIdentifier("imdbCell") as! BoredTaskCell!
cell!.selectionStyle = UITableViewCellSelectionStyle.None
cellChooser = cell
}
else if selectedTaskCategory == "Lifestyle" {
let emailCell = tableView.dequeueReusableCellWithIdentifier("emailCreatorCell") as! BoredTaskCell!
emailCell!.selectionStyle = UITableViewCellSelectionStyle.None
cellChooser = emailCell
}
else {
let emptyCell = tableView.dequeueReusableCellWithIdentifier("messageCell") as! BoredTaskCell!
cellChooser = emptyCell
emptyCell!.selectionStyle = UITableViewCellSelectionStyle.None
}
return cellChooser
}
The short answer is that you cannot return two cells from one call to cellForRowAtIndexPath. But to achieve what you want, change the way you construct the tableView, so it has one section for each task, rather than one cell for each task. ie use indexPath.section as the index for your array. You will need to amend all the table view data source methods (eg. so numberOfSectionsInTableView will return self.tasks.count). The trick comes in numberOfRowsInSection, which should typically return 1, but for those tasks where you want two cells, it should return 2. Then in cellForRowAtIndexPath, you determine which task to display using indexPath.section. Then if indexPath.row is 0 use one cell type (eg imdbcell), and if it's 1 use the other cell type (eg message cell).

identifying the last and first cell in a UITableView

I have a simple table view. I want the first and last rows to have a special picture showing the beginning and end of a line. However, I am getting the wrong identification. The table view thinks that cells in the middle are the ends cells. I believe that IOS is caching the rows and and loading them when there needed as when I scroll up and down some of the arrows change picture(they should be static once loaded). Also the documentation says here Any idea how to fix this and identify the last and first row regardless of caching? (Btw locations is the arrow used to load the cells and the first and last positions of the array are where the special picture should be).
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
var stop = locations[indexPath.row]
var cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! CustomRouteViewCell
cell.locationTitle.text = "\(stop)"
cell.downTime.text = BusRoute.getRecentDepartures(stop, direction: direction, name: name)
println(indexPath.row)
cell.indexRow = indexPath.row
if(cell.indexRow == 0)
{
cell.downImage.image = UIImage(named: "beginningRoute.png")
}
else if(cell.indexRow == locations.count - 1)
{
cell.downImage.image = UIImage(named: "endRoute.png")
}
return cell
}
The problem may not be you're misidentifying the first and last cells (that looks fine so long as you've only got one section in your UITableView). The problem could be reusing cells: you're not setting the cell's downImage.image to nil and the cell may previously have been the first or last cell.
Try the following:
switch indexPath.row {
case 0:
cell.downImage.image = UIImage(named: "beginningRoute.png")
case self.tableView(tableView, numberOfRowsInSection: 0) - 1:
cell.downImage.image = UIImage(named: "endRoute.png")
default:
cell.downImage.image = nil
}

Resources