UITableView edit mode not showing delete button - ios

I'm replicating the sample Food Tracker app from apple and have an issue where the items from my TableView in Edit mode don't show the left hand delete icon (the round red one). If I swipe left I get a delete button on the right. I've downloaded the Food Tracker code and it works.
The difference I can see is that the sample app has implemented a UITableViewController, whereas I have a UITableView within a standard UIViewController. I'm not sure why this doesn't work.
There are some similar questions but they are older versions of Swift / Ios, so I'm not sure they are still relevant.
Here is the link to the sample app https://developer.apple.com/library/content/referencelibrary/GettingStarted/DevelopiOSAppsSwift/index.html#//apple_ref/doc/uid/TP40015214-CH2-SW1
Here are the funcs I recently added that should make it work
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
//delete the row from the dataSource
dataSource.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
}
else if editingStyle == .insert {
//create a new instance and insert the row
}
}
And I added this to the ViewDidLoad() to get the Edit button in the Nav bar
navigationItem.leftBarButtonItem = editButtonItem
Here's what the Food Tracker example looks like;
enter image description here
and mine is missing the left hand button. The only difference I can see is that I'm using a TableView inside a ViewController.
Thanks
Nick

No need to check the title of the barButtonItem as tapping it already toggles its state and passes the correct state in editing param.
Also, when overriding, you need to invoke super's implementation as absence would always show the delete indicator.
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: true)
tableView.setEditing(editing, animated: true)
}

I think I solved this. Effectively there's a fair bit of 'magic' that a UITableViewController is doing. To recreate this in a normal UIViewController with a TableView you have to override the SetEditing function and based on the title of the button put it in edit mode.
override func setEditing(_ editing: Bool, animated: Bool) {
let status = navigationItem.leftBarButtonItem?.title
if status == "Edit" {
tableView.isEditing = true
navigationItem.leftBarButtonItem?.title = "Done"
}
else {
tableView.isEditing = false
navigationItem.leftBarButtonItem?.title = "Edit"
}
}

Related

UITableView: How to change the background of overlapped cells when Drag cell?

I have attached a image.
This is the iPhone's default reminder app.
Like this, I want to make a gray background when the drag cell overlap.
Here is what I tried: ;But it looks ugly in the emulator.
var beforeTouch: IndexPath?
func tableView(_ tableView: UITableView,
targetIndexPathForMoveFromRowAt sourceIndexPath: IndexPath,
toProposedIndexPath proposedDestinationIndexPath: IndexPath) -> IndexPath {
if let cell = tableView.cellForRow(at: proposedDestinationIndexPath) {
if (self.beforeTouch != sourceIndexPath)
&& (sourceIndexPath != proposedDestinationIndexPath) {
if self.beforeTouch == nil {
cell.setSelected(true, animated: false)
} else {
cell.setSelected(true, animated: false)
tableView.cellForRow(at: beforeTouch!)?.setSelected(false, animated: false)
}
}
self.beforeTouch = proposedDestinationIndexPath
}
return proposedDestinationIndexPath
}
In this code, I used below method.
func tableView(_:, targetIndexPathForMoveFromRowAt:, toProposedIndexPath:) -> IndexPath
It seems executed every time when drag and can trace the cell by using the IndexPath of the parameter.
Also, I think there will be a performance problem because the 'cell.setSelected()' is used in a tableView function that is continuously called while dragging. I also have the problem of having to store the indexPath of the previous cell in the storage property of the class, so I am looking for another way.
I think there will be a traditional way to this issue.
Can someone help on how I can achieve it?

how to select multiple cells using button and image to check and uncheck

Actually I am trying to select and deselect multiple rows in tableview using image in tableviewcell,and also I want to delete selected rows when I click on delete button which is outside of the tableview.Here I am able to delete the selected row and am able to select and deselect single row.But I want to select and deselect multiple rows to delete when the rows are selected.Can anyone help me to do this.Thanks in advance.
//In tableviewcell class
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
if selected{
checkOrUncheckImg.image = UIImage(named:"check")
}else{
checkOrUncheckImg.image = UIImage(named:"uncheck")
}
// Configure the view for the selected state
}
Create a dictionary or a set of the IDs or indexPath of the cells that are selected. I'm going to use IDs as they are more unique, but it really depends on your DB. If your objects don't have a unique identifier use indexPath
var arrayIDs = Set<String>()
Implement didSelectRowAtIndexPath tableView delegate method. When the user taps the cell, add or remove the ID to the arrayIDs
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let objectID = objects[indexPath.row].id
if (arrayIDs.contains(objectID)){
arrayIDs.remove(objectID)
}else{
arrayIDs.insert(objectID)
}
}
In your cellForRowAtIndexPath, if the arrayIDs contains the objects id, set selected image
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "yourCellClass") as? YourCellClass {
if (arrayIDs.contains(objectID){
cell.checkOrUncheckImg.image = UIImage(named:"check")
}else{
cell.checkOrUncheckImg.image = UIImage(named:"uncheck")
}
return cell
}
And when clicking the button outside of the cell
#IBAction func buttonPressed(_ sender: Any) {
//Do something with arrayIDs (loop through them and delete each one with REST call and from datasource or whatever you're doing, then reloadData to update table
tableView.reloadData()
}
I didn't test any of this, so there may be some small syntax errors, but you get the gist.

tableview item looping through all items before displaying selected item siwift

I have an application where I receive information via Alamofire and it gets passed into a tableview. I can further get more details about selected products which is passed to a view controller. this works fine but the issue I have now is when I click a cell to get more details about a particular item it always has to navigate through all other items. If I click an item on cell 5, the detail viewcontroller would display from item 1,2,3,4 then 5 although it does it quickly. I believe once I close the modal like
#IBAction func closeModalPressed(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
}
it is suppose to remove the previous details. how do I do it to make it show item 5 without looping through 1-4 first
did select row
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selected = Services.instance.items[indexPath.row]
Services.instance.selected = selected
let index = IndexPath(row: indexPath.row, section: 0)
tableView.reloadRows(at: [index], with: .none)
tableView.selectRow(at: index, animated: false, scrollPosition: .none)
NotificationCenter.default.post(name: NOTIFY, object: nil)
performSegue(withIdentifier: TO_DETAILS, sender: nil)
}
I don't think you are doing any api calls or anything in didSelectRowAt, I would suggest try following,
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selected = Services.instance.items[indexPath.row]
Services.instance.selected = selected
NotificationCenter.default.post(name: NOTIFY, object: nil)
performSegue(withIdentifier: TO_DETAILS, sender: nil)
}
You are unnecessarily reloading row here and asking table view to select same row again which ultimately calls didSelectRowAt.

Swift: Custom section header includes buttons to either delete rows or add a row for ONLY that section's rows

I'm new to Swift, so please elaborate on your answer.
Basically I've created a custom header using a nib and created a subclass of UITableViewHeaderFooterView associating to the nib I created. The custom header contains two buttons which one is "Add Object" and when the user clicks on it, it should just insert a custom cell (Yes. My cells are custom cells that dynamically display when the application launches) row with some default data and the other is "Delete Object" which enables the edit mode and allows the user to delete rows. It also contains a label to display the title of that section header.
I don't want to use navigation control or create my buttons programmatically, so I'm just mentioning it before anyone gives me an answer like that. I will explain exactly what I've done and what I'm trying to do.
The problem I am having is whenever I click "Delete Object" it does enable the edit mode and displays the red circles, but to ALL sections. I only want it to show for the section I clicked it on. I've already tried to use canEditRowAt and it doesn't seem to work. The problem I keep getting is it either displays the red circles to just one section regardless which section I click the "Delete Object".
func tableView(_ tableView: UITableView, commit editingStyle:
UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
//There are two sections
switch indexPath.section {
case 0:
if editingStyle == .delete{
//Updating data model before removing
firstArray.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .left)
}
else if editingStyle == .insert{
firstArray.append(objects(name: "Test", second: "Cell", image: UIImage(named: "Unknown")!))
let indexPath = IndexPath(row: firstArray.count - 1, section: 0)
tableView.insertRows(at: [indexPath], with: .left)
}
case 1:
if editingStyle == .delete{
//Updating data model before removing
secondArray.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .left)
}
else if editingStyle == .insert{
let indexPath = IndexPath(row: secondArray.count - 1, section: 0)
tableView.insertRows(at: [indexPath], with: .left)
}
default:
break
}
}
For the buttons inside the nib, I did something similarly to how you would unwind from a viewController. I created the #IBAction outlets in the firstViewController (which is the datasource and delegate of the TableView) and inside the nib I control + clicked on each button and dragged the line to the firstResponder box and assigned it to the events I created. I've tested them and they work.
#IBAction func deleteTheObject(sender: UIButton){
//This does show the edit mode and changes the buttons name each time
//it's clicked, but I only want it to enable the edit mode on the
// section that the section's header "Delete Object" button was clicked on.
//NOT both sections
if self.table.isEditing == true{
table.setEditing(!table.isEditing, animated: true)
sender.setTitle("Done", for: .normal)
}
else{
self.table.isEditing = true
sender.setTitle("Delete Object", for: .normal)
}
}
#IBAction func addTheObject(sender: UIButton){
//Should add row to section
}
Add this to only allow, for example section1, editable
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
if indexPath.section == 1 {
return true
}
return false
}

Why does UITableViewCell remain highlighted?

What would cause a table view cell to remain highlighted after being touched? I click the cell and can see it stays highlighted as a detail view is pushed. Once the detail view is popped, the cell is still highlighted.
In your didSelectRowAtIndexPath you need to call deselectRowAtIndexPath to deselect the cell.
So whatever else you are doing in didSelectRowAtIndexPath you just have it call deselectRowAtIndexPath as well.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Do some stuff when the row is selected
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
The most clean way to do it is on viewWillAppear:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// Unselect the selected row if any
NSIndexPath* selection = [self.tableView indexPathForSelectedRow];
if (selection) {
[self.tableView deselectRowAtIndexPath:selection animated:YES];
}
}
This way you have the animation of fading out the selection when you return to the controller, as it should be.
Taken from http://forums.macrumors.com/showthread.php?t=577677
Swift version
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// deselect the selected row if any
let selectedRow: IndexPath? = tableView.indexPathForSelectedRow
if let selectedRowNotNill = selectedRow {
tableView.deselectRow(at: selectedRowNotNill, animated: true)
}
}
For the Swift users, add this to your code:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
It's paulthenerd's answer but in Swift instead of Obj-C.
Did you subclass -(void)viewWillAppear:(BOOL)animated? The selected UITableViewCell won't deselect when you don't call [super viewWillAppear:animated]; in your custom method.
Swift 3 Solution
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath as IndexPath, animated: true)
}
If you are using a UITableViewCell, then comment the following line
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
// [super setSelected:selected animated:animated];
}
Hope this helps.
Updated with Swift 4
After few experiments, also based of previous answers, I've got the conclusion that the best behaviour can be achieved in 2 ways: (almost identical in practice)
// First Solution: delegate of the table View
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: false)
}
// Second Solution: With the life cycle of the view.
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
let selectedRow: IndexPath? = tableView.indexPathForSelectedRow
if let selectedRow = selectedRow {
tableView.deselectRow(at: selectedRow, animated: false)
}
}
I'm personally adopting the first solution, because it's simply more concise. Another possibility, if you need a little animation when you return to your tableView, is to use viewWillAppear:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let selectedRow: IndexPath? = _view.tableView.indexPathForSelectedRow
if let selectedRow = selectedRow {
_view.tableView.deselectRow(at: selectedRow, animated: true)
}
}
Last but not least, if you're using a UITableViewController, you can also take advantage of the property clearsSelectionOnViewWillAppear.
To get the behaviour Kendall Helmstetter Gelner describes in his comment, you likely don't want deselectRowAtIndexPath but rather the clearsSelectionOnViewWillAppear property on your controller. Perhaps this was set to YES by accident?
See the comment in the default Apple template for new UITableViewController subclasses:
- (void)viewDidLoad
{
[super viewDidLoad];
// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;
}
Swift 5 Solution:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRow(at: indexPath as IndexPath, animated: true)
}
I was getting this problem as well for my drill-down application. After a viewcontroller, which I'll call VC, returns after pushing another ViewController, the selected cell in VC remained highlighted. In my app, I had created VC to handle the second level (out of three levels) of my drill-down.
The problem in my case is that VC was a UIViewController (that contained a View that contained a TableView). I instead made VC a UITableViewController (that contained a TableView). The UITableViewController class automatically handles the de-highlighting of the table cell after returning from a push. The second answer to the post "Issue with deselectRowAtIndexPath in tableView" gives a more complete answer to this problem.
The problem did not occur for the root viewcontroller because when I created the app as a "Navigation-based App" in XCode, the resulting root viewcontroller was already made to subclass UITableViewController.
If none of these work for you, consider this work-around:
Use an unwind segue to call:
#IBAction func unwind_ToTableVC (segue: UIStoryboardSegue) {
if let index = tableView.indexPathForSelectedRow {
tableView.deselectRowAtIndexPath(index, animated: true)
}
}
Why do this? Primarily if you're having trouble getting the deselect code to run at the right time. I had trouble with it not working on the viewWillAppear so the unwind worked a lot better.
Steps:
Write the unwind segue (or paste from above) into your 1st VC (the one with the table)
Go to the 2nd VC. Control-drag from the Cancel/Done/Etc button you're using to dismiss that VC and drag to the Exit Icon at the top.
Select the unwind segue you created in step 1
Good luck.
I am using CoreData so the code that worked for me was a combination of ideas from various answers, in Swift:
override func viewDidAppear(_ animated: Bool) {
if let testSelected = yourTable.indexPathForSelectedRow {
yourTable.deselectRow(at: testSelected, animated: true)
}
super.viewDidAppear(true)
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath as IndexPath, animated: true)
}
I've been having the same issue for long time so in case anyone else is struggling:
Take a look at your -tableView: cellForRowAtIndexPath: and see if you are creating cells or using a 'reuse identifier'. If the latter, make sure that your table in IB has a cell with that identifier. If you're not using a reuse Identifier just create a new cell for each row.
This should then give your table the expected 'fade selected row' on appearing.
Use this method in UITableViewCell class
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
// Just comment This line of code
// [super setSelected:selected animated:animated];
}
For Swift 3:
I would prefer it to use in viewDidDisappear
Define:-
var selectedIndexPath = IndexPath()
In viewDidDisappear:-
override func viewDidDisappear(_ animated: Bool) {
yourTableView.deselectRow(at: selectedIndexPath, animated: true)
}
In didSelectRowAtIndexPath:-
func tableView(_ tableView: UITableView, didSelectRowAtIndexPath indexPath: IndexPath) {
selectedIndexPath = indexPath
}
if the cell is remaining highlighted after touching it, you can call UITabelView method,
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
`[tableView deselectRowAtIndexPath:indexPath animated:YES];`
}
Or, you can use the following method and modify it according to your requirements,
// MARK: UITableViewDelegate
func tableView(tableView: UITableView, didHighlightRowAtIndexPath indexPath: NSIndexPath) {
if let cell = tableView.cellForRowAtIndexPath(indexPath) {
cell.backgroundColor = UIColor.greenColor()
}
}
func tableView(tableView: UITableView, didUnhighlightRowAtIndexPath indexPath: NSIndexPath) {
if let cell = tableView.cellForRowAtIndexPath(indexPath) {
cell.backgroundColor = UIColor.blackColor()
}
}
Xcode 10, Swift 4
I had this same issue and discovered I left an empty call to viewWillAppear at the bottom of my tableViewController. Once I removed the empty override function the row no longer stayed highlighted upon return to the tableView view.
problem func
override func viewWillAppear(_ animated: Bool) {
// need to remove this function if not being used.
}
removing empty function solved my problem.

Resources