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.
Related
I have a tableview where the editingstyle is .delete for all the cells. But I want to have some specific cells that doesnt have editingstyle (so that you can't swipe them, .none). Anyone have any suggestions for how to implement this?
I tried to write something like UITableViewCell.EditingStyle.none for that specific row but that didnt work.
Thanks in advance, Pontus
Consider the following example:
class ViewController: UITableViewController {
private let arr = (1...10).map { "Item \($0)" }
override func viewDidLoad() {
super.viewDidLoad()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arr.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")
cell?.textLabel?.text = arr[indexPath.row]
return cell ?? UITableViewCell()
}
override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
if indexPath.row == 2 {//<- Your condition
return .none
} else {
return .delete
}
}
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
}
So the key is to use editingStyleForRowAt in order to change the style for a specific cell (or even multiple cells) (see the documentation for further reference: https://developer.apple.com/documentation/uikit/uitableviewdelegate/1614869-tableview).
So I have two table views. MainTableView and SecondaryTableView, The MainTableView has empty rows with no text. A Table view cell is configured though. These rows have the option to delete. The SecondaryTableView rows also have an editing style in which I am using to ADD the selected row into the MainTableView rows.
class MainVC: UIViewController,UITableViewDataSource,UITableViewDelegate {
#IBOutlet var mycartableview: UITableView!
var passedcar = String()
override func viewDidLoad() {
super.viewDidLoad()
mycartableview.tableFooterView = UIView(frame: CGRect.zero)
}
var diynames: [String] = ["a","b","c"]
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return diynames.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let diyname = diynames[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "mycarcell") as! mycarcell
cell.mycartitle.text = diyname
return cell
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete{
diynames.remove(at: indexPath.row)
tableView.beginUpdates()
tableView.deleteRows(at: [indexPath], with: .fade)
tableView.endUpdates()
}
}
SecondaryVC code:
class MainVC: UIViewController, UITableViewDelegate, UITableViewDataSource{
let names = ["aa","ba","ca","da","ea",]
#IBOutlet var tableview: UITableView!
#IBOutlet var slctedcar: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
self.tableview.backgroundColor = .white
// Do any additional setup after loading the view.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return names.count
return names.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "diylistcell") as! diylistcellTableViewCell
cell.diytitle.text = names[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let addbutton = UITableViewRowAction(style: .normal, title: "Add") { (rowAction, indexpath
) in
print("Add clicked")
let mycarvc = MyCarViewController()
mycarvc.diynames.append(contentsOf: self.hcdiynames)
let hcindexpath = IndexPath(row: mycarvc.diynames.count - 1, section: 0)
mycarvc.mycartableview.insertRows(at: [hcindexpath], with: .automatic) //this is where the error occurs. Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
mycarvc.mycartableview.endUpdates()
}
addbutton.backgroundColor = UIColor.darkGray
return [addbutton]
}
Clear Image of Error
That's where my pain is coming from. As you could see in the picture above the highlighted area it says that my array coming from the main vc is a count of 8. That checks out completely because the original array has 3 and I am appending 5 new ones. The error implicitly found nil has left me clueless in this scenario. If anybody has any suggestions on how to fix this problem it would be highly appreciated, thank you .
This is a common error when you try to transfer data between VC , I usually create 1 variable which will store what I want and after in the viewDidLoad() I give the value of this variable to what I really want , IN your code the equivalent is
class MainVC: UIViewController,UITableViewDataSource,UITableViewDelegate {
#IBOutlet var mycartableview: UITableView!
var passedcar = String()
// I JUST CREATED THE VARIABLE WHICH WILL HOLD OUR VALUES
var diynamesContainer = [String]()
///////////////////
var diynames: [String] = ["a","b","c"]
override func viewDidLoad() {
super.viewDidLoad()
mycartableview.tableFooterView = UIView(frame: CGRect.zero)
// I ADD TO OUR ARRAY THE VALUE OF THE diynamesContainer
diynames.append(contentsOf: diynamesContainer)
//////////////////
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return diynames.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let diyname = diynames[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "mycarcell") as! mycarcell
cell.mycartitle.text = diyname
return cell
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete{
diynames.remove(at: indexPath.row)
tableView.beginUpdates()
tableView.deleteRows(at: [indexPath], with: .fade)
tableView.endUpdates()
}
}
/*
// MARK: - Navigation
and in our main vc you replace
let mycarvc = MyCarViewController()
mycarvc.diynames.append(contentsOf: self.hcdiynames)
by
let mycarvc = MyCarViewController()
mycarvc.diynamesContainer = self.hcdiynames
Try to run your code, the error will go away
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 can not turn on editing mode in the first row of the cell. I tried this code but it didn't help.
public func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
if indexPath.row == 1{
return true
}
return false
}
Can someone help me?
Firstly, make sure your have added UITableViewDataSource protocol. Secondly, you may also need the following implementations.
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let myAction = UITableViewRowAction(style: .normal, title: "MY_ACTION") { (action, indexPath) in
print("I'm here")
}
return [myAction]
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
}
If you want to edit first row then replace
if indexPath.row == 1
with
if indexPath.row == 0
Because indexPath.row starts from 0 not from 1.
Hope that will help.
EDIT:
Since you didn't show your complete code I am adding example code here.
check below code:
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tbl: UITableView!
let arr = ["1", "2", "3"]
override func viewDidLoad() {
super.viewDidLoad()
tbl.dataSource = self
tbl.delegate = self
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tbl.dequeueReusableCell(withIdentifier: "cell")!
cell.textLabel?.text = self.arr[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arr.count
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
//set 0 for first cell
if indexPath.row == 0 {
return true
}
return false
}
//Need this method for delete cell
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == UITableViewCellEditingStyle.delete {
tbl.reloadData()
}
}
}
I've built a simple toDoList app with Swift 3. Now I want to be able to delete my items from a TableView by swiping from right to left. This code is what I've found. But nothing happens when I swipe to the left.
CODE:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return toDoList.count
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, cellForRowAtIndexPath indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "Cell")
cell.textLabel?.text = toDoList[indexPath.row]
return cell
}
//
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if (editingStyle == .delete) {
toDoList.remove(at: indexPath.row)
UserDefaults.standard.set(toDoList, forKey: "toDoList")
tableView.reloadData()
}
}
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
return .delete
}
This still does not work. Nothing happens when I swipe to the left. The to do List itself is working. I can add items to the table but I just can't remove them.
Thanks :)
Did you implement tableView:canEditRowAtIndexPath: method?
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
EDIT:
Thanks to #rmaddy for mentioning that the default value of tableView:canEditRowAtIndexPath: is true, implementing it doesn't solve the problem.
I'm not pretty sure of what are you trying to do from your code snippet, so make sure that you are implementing the following methods (UITableViewDelegate):
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if (editingStyle == .delete) {
toDoList.remove(at: indexPath.row)
UserDefaults.standard.set(toDoList, forKey: "toDoList")
tableView.reloadData()
}
}
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
return .delete
}
You can also keep the implementation of tableView:canEditRowAtIndexPath: method:
Asks the data source to verify that the given row is editable.
So, -for example- if you want to let the first row is not editable, i.e user cannot swipe and delete the first row, you should do somthing like:
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
if indexPath.row == 0 {
return false
}
return true
}
Make sure that the UITableViewDataSource and UITableViewDelegate are connected with the ViewController.