In my table view, after I delete a record, I want it to disable editing if the last record was deleted, but even after calling setEditing the state doesn't change. Here's a simplified example that still shows the behavior:
class TestViewController: UITableViewController {
var items = [String]()
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.rightBarButtonItems = [
UIBarButtonItem(title: "Add", style: .plain, target: self, action: #selector(add)),
UIBarButtonItem(title: "Remove", style: .plain, target: self, action: #selector(remove))
]
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.text = self.items[indexPath.row]
return cell
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
self.items.remove(at: indexPath.row)
if self.items.count == 0 {
// This is getting executed, but not changing the table's state
self.tableView.setEditing(false, animated: false)
}
self.tableView.reloadData()
}
#objc func add () {
self.items.append("foo")
self.tableView.reloadData()
}
#objc func remove () {
self.tableView.setEditing(!self.tableView.isEditing, animated: true)
}
}
Clicking the "Remove" button toggles editing on and off without any problems, but if I delete the last one and then add a new one, it still shows up in editing mode. I tried stepping through the code, and it's definitely hitting the correct line, but even after executing it tableView.isEditing is still true. What am I doing wrong here?
I have checked your code, as per Hikaru Watanabe's comment , it is not working.
So i have made change in commit editingstyle method and it is working fine,
please check below code
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
self.items.remove(at: indexPath.row)
self.tableView.deleteRows(at: [indexPath], with: .left)
if self.items.count == 0 {
// This is getting executed, but not changing the table's state
self.tableView.setEditing(false, animated: false)
}
//self.tableView.reloadData()
}
Updated After John's Comment -
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
self.items.remove(at: indexPath.row)
if self.items.count == 0 {
// This is getting executed, but not changing the table's state
self.tableView.deleteRows(at: [indexPath], with: .none)
self.tableView.setEditing(false, animated: false)
}else{
self.tableView.reloadData()
}
}
You should use this method:
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return editingEnable
}
Just set true or false this variable "editingEnable" on your condition.
Related
I’m trying to implement the TableView example from Chapter 5 in Swift Programming in Easy Steps exercise. I have checked and re-checked the example code (even downloaded and tested the actual example code), but I’m still getting this runtime error. Anyone know why this is happening?
2019-11-01 07:56:51.247052+0100 TableView_EasySteps[2067:39485] Can't
end BackgroundTask: no background task exists with identifier 1
(0x1), or it may have already been ended. Break in
UIApplicationEndBackgroundTaskError() to debug.
here is the ViewController code:
import UIKit
class WebsitesTableViewController: UITableViewController {
var websites:[[String]] = [
["Apple", "https://www.apple.com"] ,
["NY Times", "https://www.nytimes.com"] ,
["DN", "https://www.dn.se"] ,
["NFL", "https://www.nfl.com"] ,
["Premier League", "https://www.premierleague.com"] ,
["The Guardian", "https://www.theguardian.com"]
]
override func viewDidLoad() {
super.viewDidLoad()
// preserve selection between presentations
self.clearsSelectionOnViewWillAppear = false
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return websites.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "cellIdentifier")
if cell == nil {
cell = UITableViewCell(style: .subtitle, reuseIdentifier: "cellIdentifier")
}
cell!.textLabel!.text = websites[indexPath.row][0]
cell!.detailTextLabel!.text = websites[indexPath.row][1]
return cell!
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let url = URL(string: websites[indexPath.row][1]) {
UIApplication.shared.open(url)
}
}
// Override to support editing the table view.
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
websites.remove(at: indexPath.row)
// Delete the row from the data source
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
}
Check the SceneDelegate.swift file and make sure it's part of the Control files if you're using MVC standard otherwise just make sure that it's part of the files in your Xcode project alongside the AppDelegate.swift and the ViewController.swift amongst others.
I have a dictionary that I have made called places and I made each cell in the tableViewController show each parts of the dictionary. I know the function to delete the rows in the controller, but when I run the app and do the action of deleting nothing happens.
// This is my entire TableViewController. I have another ViewController that appends the dictionary.
var places = [Dictionary<String,String>()]
var activePlace = -1
class TableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
if UserDefaults.standard.object(forKey: "places") != nil { //checks if the list is not empty
places = UserDefaults.standard.object(forKey: "places") as! [Dictionary<String, String>]
}
if places.count == 1 {
places.remove(at: 0)
places.append(["name":"Ex. Eiffel Tower", "lat": "48.858324", "lon": "2.294764"])
}
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, canEditRowAt
indexPath: IndexPath) -> Bool {
return true
}
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return places.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
DispatchQueue.main.async {
self.tableView.reloadData()
}
cell.textLabel?.text = places[indexPath.row]["name"]
return cell
}
override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
activePlace = indexPath.row
return indexPath
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "newPlace" {
activePlace = -1
}
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
tableView.deleteRows(at: [indexPath], with: .bottom)
places.remove(at: indexPath.row)
UserDefaults.standard.setValue(places, forKey: "places")
}
}
I am expecting that when I make the action of swiping to the left that it would delete the row and the contents of the cell from the tableView. Then it would also delete from the dictionary.
It's very complicate to delete table view cell sometimes. Your code is correct but you just need to remove a line. Instead of calling tableview.deleteRows you just delete the item of your dictionary and reload the table view.
Enable the table rows to editable using canEditRowAt function....
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
places.remove(at: indexPath.row)
UserDefaults.standard.setValue(places, forKey: "places")
tableView.reloadData()
}
}
Move deleteRows(at:with:) after remove(at:) in tableView(_: commit: forRowAt:) method, i.e.
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
places.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .bottom) //here...
UserDefaults.standard.setValue(places, forKey: "places")
}
}
The main issue is the wrong declaration of the data source array. The pair of parentheses must be behind the brackets
var places = [Dictionary<String,String>]()
In the method tableView(_:commit:forRowAt:the order is wrong. First remove the row from the data source array then delete the row.
Two Don'ts
Do not use setValue:forKey with UserDefaults to save a single value.
Do not declare the data source array outside of the class.
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
places.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .bottom)
UserDefaults.standard.set(places, forKey: "places")
}
}
Solved:
DispatchQueue.main.async was creating an endless loop of constantly reloading the data. By removing that the two functions of editing were allowed to run. I was able to perform the deleting action.
I have custom cells of UITableView which consist of UIImage and textLabel. When I select the editing mod, the textLabel shows the following image;
I want to move it to left. I tried all of things to do but it didn't work.
Please let me know.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return allRows.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCell(withIdentifier: "editCell", for: indexPath) as! CustomTableView
cell.textLabel?.text = self.allRows[indexPath.row].bname
cell.textLabel?.textAlignment = .left
let image:UIImage = UIImage(named: "group")!
cell.imageView!.image = image
return cell
}
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle{
return .delete
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath){
if editingStyle == .delete{
self.allRows.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath as IndexPath], with: .fade)
}
}
#objc func leftEditButtonPressed(_ sender: UIButton) {
self.tableView!.isEditing = true
self.tableView?.setEditing(true, animated: true)
}
look at this, I hope it helps you:
swipe left/right in tableViewCell
func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let closeAction = UIContextualAction(style: .normal, title: "Close", handler: { (ac:UIContextualAction, view:UIView, success:(Bool) -> Void) in
print("OK, marked as Closed")
success(true)
})
closeAction.image = UIImage(named: "tick")
closeAction.backgroundColor = .purple
return UISwipeActionsConfiguration(actions: [closeAction])
}
Try using the following code:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.dataArr.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 80.0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "TestTableCell") as? TestTableCell else {
return UITableViewCell()
}
cell.customImageView.image = #imageLiteral(resourceName: "ic_audio")
cell.customTitleLabel.text = self.dataArr[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
return UITableViewCellEditingStyle.delete
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
print("delete at \(indexPath.row)")
}
}
Here is the output for this code:
Note: You can change the editing action in editingStyleForRowAt method and for performing the action you need to write the logic in commit editingStyle method.
Hope this help you!
I suggest you to create new label in that UITableViewCell and connect that label to custom UITableViewCell class ! then you are free to do anything !
Swift 4 :
Create a new class which is subclass of UITableViewCell !
In your storyboard in right menu which called Utilities in toolbar of Utilities go to the Identity inspector defince your cell class to your new UITableViewCell class
Drag and drop UILabel from Object library into your cell
now using drag and drop connect that label to your UITableViewCell class then create an Outlet
Perfect !
now use myLabel instead of textLabel ;)
dont forget to cast your cell as! YourCustomCellClass
Good Luck ;)
I have a tableview where I'm trying to implement a swipe-to-delete action.
I'm using swift 4 and Xcode 9.
So far I have the next code which allows me to show a delete label when swipe:
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
switch editingStyle {
case .delete:
print("deleting row")
default:
return
}
}
So far so good, now I'd like to change the background red color to another one and if it's also possible i'd like to show an icon instead of a label.
I'm trying this code I've seen for Swift 4 but it's complaining when creating the button:
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let deleteButton = UITableViewRowAction(style: .default, title: "Delete") { (action, indexPath) in
self.tableView.dataSource?.tableView!(self.tableView, commit: .delete, forRowAt: indexPath)
return
}
deleteButton.backgroundColor = UIColor.black
return [deleteButton]
}
It gives this error -> Ambiguous reference to member 'tableView(_:numberOfRowsInSection:)'
I don't know if I'm trying the good way to edit, or if it's not editable at all on swift 4.
Any help is appreciated,
Thanks!
I am using Xcode 9.2 and swift 4. Used same code but with no errors. Full code is here. That may help to you.
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView = UITableView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height), style: .plain)
tableView.dataSource = self
tableView.delegate = self
self.view.addSubview(tableView)
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .default, reuseIdentifier: "Cell")
cell.textLabel?.text = "Lalala"
return cell
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
switch editingStyle {
case .delete:
print("deleting row")
default:
return
}
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let deleteButton = UITableViewRowAction(style: .default, title: "Delete") { (action, indexPath) in
self.tableView.dataSource?.tableView!(self.tableView, commit: .delete, forRowAt: indexPath)
return
}
deleteButton.backgroundColor = UIColor.black
return [deleteButton]
}
}
I was tried like this ,may be this will help you.
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let deleteAction = UITableViewRowAction(style: .default, title: "Delete") { (action, indexPath) in
print("Did tap the button")
self.deleteSession(sessionId: self.tableModel[indexPath.row].Id)
print("the session id is '\(self.tableModel[indexPath.row].Id)'")
self.tableModel.remove(at: indexPath.row)
print("\(indexPath.row)")
tableView.deleteRows(at: [indexPath], with: .fade)
print("\([indexPath])")
self.medilogTableView.reloadData()
}
deleteAction.backgroundColor = UIColor.red
//deleteAction.backgroundColor = UIColor(patternImage: UIImage(named: "delete_button")!)
//renameAction.backgroundColor = UIColor(red: 0.298, green: 0.851, blue: 0.3922, alpha: 1.0)
return [deleteAction]
}
medilogTableView is name of the table
You must implement your edit code inside the "in" callback :
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let deleteButton = UITableViewRowAction(style: .default, title: "Delete") { (action, indexPath) in
// here implement your delete code
}
deleteButton.backgroundColor = .blue
return [deleteButton]
}
Sample of implementation for your "delete code" :
self.tableView.beginUpdates()
self.data.remove(at: indexPath.row)
self.tableView.deleteRows(at: [indexPath], with: .left)
self.tableView.endUpdates()
I am trying to add a second swipe button to my table view rows, but editActionsForRowAtIndexPath is not being called when I swipe a row. This is despite my class declaring both
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
and
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
}
Both of these are called when I swipe because I also have a regular delete button.
My editActionsForRowAtIndexPath function is written as follows:
func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewRowAction? {
print("triggered!")
let more = UITableViewRowAction(style: .default, title: "More") { action, index in
print("more button tapped")
}
more.backgroundColor = UIColor.blue
return more
}
Thanks in advance!
You are not using the updated Swift 3 API for this particular method. The delegate method should be written as:
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
print("triggered!")
let more = UITableViewRowAction(style: .default, title: "More") { action, index in
print("more button tapped")
}
more.backgroundColor = UIColor.blue
return [more]
}