Swift UISwipeActionsConfiguration and segue - ios

I added leadingSwipeAction "Edit" button. However, when i press "Edit" button in simulator, app crashes and shows "Thread 1: signal SIGBART" in **prepare(for:sender:)**method.
I saw similar questions, but their solutions did't help. I'm newbie and can't understand where is the problem.
`//my edit button code`
override func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let edit = UIContextualAction(style: .normal, title: "Edit") { [self] (contextualAction, view, actionPrformed: (Bool) -> Void) in
//TODO:
performSegue(withIdentifier: "EditItem", sender: self)
actionPrformed(true)
}
return UISwipeActionsConfiguration(actions: [edit])
}
// segue
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "AddItem" {
let controller = segue.destination as! AddAndEditItemViewController
controller.delegate = self
}
else if segue.identifier == "EditItem" {
let controller = segue.destination as! AddAndEditItemViewController
controller.delegate = self
if let indexPath = tableView.indexPath(
for: sender as! UITableViewCell) { //erorr shows here
controller.itemToEdit = items[indexPath.row]
}
}
}

self here
performSegue(withIdentifier: "EditItem", sender: self)
is the vc instance itself not the cell , you need to pass indexPath.row
performSegue(withIdentifier: "EditItem", sender: indexPath.row)
Then
let index = sender as! Int
controller.itemToEdit = items[index]
You can also use
guard let index = tableView.indexPathForSelectedRow?.row else { return }

Related

Storyboard, Segues. What should I do if I want to use two segues from one element?

This is my first question here. I have TableViewController and I want to use two segues from one raw. One segue should work when you tapped a row, and the second is action from this row. Every of them need to show different ViewControllers and I don't understand how can I do that because I can't to create two segues from one row. The problem is that both of cases need to call prepare function and it called only with segue, and it does not called when I use performSegue.
override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let editAction = UIContextualAction(style: .normal, title: "Add"){(_,_, completionHandler) in
self.performSegue(withIdentifier: "editQuiz", sender: self)
tableView.reloadData()
}
let deleteAction = UIContextualAction(style: .destructive, title: "Delete"){(_,_, completionHandler) in
CoreDataManager.shared.deleteSomeQuizData(data: CoreDataManager.shared.quizzes[indexPath.row], indexNumber: indexPath.row)
self.navigationItem.title = "\(CoreDataManager.shared.quizzes.count) quizzes"
tableView.reloadData()
}
return UISwipeActionsConfiguration(actions: [deleteAction, editAction])
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let indexPath = tableView.indexPathForSelectedRow else {return}
print(segue.source)
if segue.identifier == "editQuiz" {
guard let destination = segue.destination as? AddWordsViewController else{return}
}
if segue.identifier == "showQuiz" {
guard let destination = segue.destination as? QuizViewController else{return}
destination.from = Int(CoreDataManager.shared.quizzes[indexPath.row].from)
destination.to = Int(CoreDataManager.shared.quizzes[indexPath.row].to)
}
}
prepare(for segue:) works only when it called from row segue to another ViewController and it didn't called with performSegue. Also if I create both of segue from TableViewController to ViewControllers and don't call performSegue, transition doesn't work.
All of the segues identifiers set correctly.
Even if I try
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.performSegue(withIdentifier: "showQuiz", sender: self)
}
prepare(for segue:) is calling, but in
let editAction = UIContextualAction(style: .normal, title: "Add"){(_,_, completionHandler) in
self.performSegue(withIdentifier: "editQuiz", sender: self)
tableView.reloadData()
}
it does not calling.
It looks like you have almost everything setup correctly, except...
In your prepare for segue code, the first thing you do is check for the selected row:
guard let indexPath = tableView.indexPathForSelectedRow else {return}
If you call performSegue from your "Edit" action, the table view will NOT HAVE a selected row.
A bit tough for me to test because I don't have all of your data management and destination controllers, but this should fix the issue (if you have your segues setup correctly in Storyboard):
override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let editAction = UIContextualAction(style: .normal, title: "Add"){(_,_, completionHandler) in
// instead of passing self as sender
//self.performSegue(withIdentifier: "editQuiz", sender: self)
// pass the indexPath
self.performSegue(withIdentifier: "editQuiz", sender: indexPath)
// no need to reload data here
//tableView.reloadData()
}
let deleteAction = UIContextualAction(style: .destructive, title: "Delete"){(_,_, completionHandler) in
CoreDataManager.shared.deleteSomeQuizData(data: CoreDataManager.shared.quizzes[indexPath.row], indexNumber: indexPath.row)
self.navigationItem.title = "Title \(indexPath.row)" // "\(CoreDataManager.shared.quizzes.count) quizzes"
tableView.reloadData()
}
return UISwipeActionsConfiguration(actions: [deleteAction, editAction])
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "editQuiz" {
// do something before segue to AddWordsViewController
if let indexPath = sender as? IndexPath {
print("editQuiz with IndexPath: \(indexPath)")
}
}
if segue.identifier == "showQuiz" {
guard let indexPath = tableView.indexPathForSelectedRow,
let destination = segue.destination as? QuizViewController
else {
// note: this will NOT Stop the segue
return
}
destination.from = Int(CoreDataManager.shared.quizzes[indexPath.row].from)
destination.to = Int(CoreDataManager.shared.quizzes[indexPath.row].to)
}
}
As I mentioned in my comment: "To give yourself the most control, don't use segues in that situation..."
To do that, delete the segues from your Storyboard.
Instead, use code like this to instantiate the "destination" view controllers as needed:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// instantiate QuizViewController
if let vc = self.storyboard?.instantiateViewController(withIdentifier: "QuizViewController") as? QuizViewController {
vc.from = Int(CoreDataManager.shared.quizzes[indexPath.row].from)
vc.to = Int(CoreDataManager.shared.quizzes[indexPath.row].to)
self.navigationController?.pushViewController(vc, animated: true)
}
}
override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let editAction = UIContextualAction(style: .normal, title: "Add"){(_,_, completionHandler) in
// instantiate AddWordsViewController
if let vc = self.storyboard?.instantiateViewController(withIdentifier: "AddWordsViewController") as? AddWordsViewController {
// do something before showing AddWordsViewController
self.navigationController?.pushViewController(vc, animated: true)
}
}
let deleteAction = UIContextualAction(style: .destructive, title: "Delete"){(_,_, completionHandler) in
CoreDataManager.shared.deleteSomeQuizData(data: CoreDataManager.shared.quizzes[indexPath.row], indexNumber: indexPath.row)
self.navigationItem.title = "Title \(indexPath.row)" // "\(CoreDataManager.shared.quizzes.count) quizzes"
tableView.reloadData()
}
return UISwipeActionsConfiguration(actions: [deleteAction, editAction])
}

Push segue from Cell doesn't work after click

I have been reading around on various ways to perform segues. I want a push segue from a cell click. I have the segue in the storyboard, so I am not using the didSelectForRow function:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let selectedRow = self.tableView.indexPathForSelectedRow as? Int else { return }
if segue.identifier == "detail", let vc = segue.destination as? DetailViewController {
vc.playerImage = UIImageView(image: UIImage(named: "userIcon"))
vc.currentRankingLabel.text = String(players[selectedRow].ranking)
vc.scoreLabel.text = String("\(players[selectedRow].wins) - \(players[selectedRow].losses)")
}
}
This is what I have got at the moment. It compiles, but the cell will not react to the click!
indexPathForSelectedRow is never Int, it's IndexPath?. Delete the conditional downcast and get the row from its row property
guard let selectedPath = self.tableView.indexPathForSelectedRow else { return }
let selectedRow = selectedPath.row
if segue.identifier == "detail", let vc = segue.destination as? DetailViewController {
vc.playerImage = UIImageView(image: UIImage(named: "userIcon"))
vc.currentRankingLabel.text = String(players[selectedRow].ranking)
vc.scoreLabel.text = String("\(players[selectedRow].wins) - \(players[selectedRow].losses)")
}
However if the segue is connected to the cell I recommend to use the sender parameter which represents the cell
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "detail", let vc = segue.destination as? DetailViewController {
let selectedPath = self.tableView.indexPath(for: sender as! UITableViewCell)!
let selectedRow = selectedPath.row
vc.playerImage = UIImageView(image: UIImage(named: "userIcon"))
vc.currentRankingLabel.text = String(players[selectedRow].ranking)
vc.scoreLabel.text = String("\(players[selectedRow].wins) - \(players[selectedRow].losses)")
}
}
Important note:
Be aware that the outlets in the destination controller are not connected in prepare(for so nothing will be displayed or the code even crashes.
I think you need call performSegue on cell selection, like below
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.performSegue(withIdentifier: "detail", sender: indexPath)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let selectedPath = sender as? IndexPath, let selectedRow = selectedPath.row, let player = players[selectedRow] else {
return
}
if segue.identifier == "detail", let vc = segue.destination as? DetailViewController {
vc.playerImage = UIImageView(image: UIImage(named: "userIcon"))
vc.currentRankingLabel.text = String(player.ranking)
vc.scoreLabel.text = String("\(player.wins) - \(player.losses)")
}
}

UITableView swipe to edit

I have two uitableviewcontroller , one is lists table and the other is editing table.
I use accessory action to edit item. it works fine.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "EditItem" {
let controller = segue.destination as! ItemDetailViewController
controller.delegate = self
if let indexPath = tableView.indexPath(for: sender as! UITableViewCell) {
controller.itemToEdit = items[indexPath.row]
}
}
}
now I add swipe function to edit item
override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let edit = UIContextualAction(style: .normal, title: "Edit") { action, view, completion in
self.performSegue(withIdentifier: "EditItem", sender: self)
completion(true)
}
let delete = UIContextualAction(style: .destructive, title: "Delete") { [weak self] action, view, completion in
self?.items.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: UITableView.RowAnimation.automatic)
self?.saveChannelListItems()
completion(true)
}
edit.backgroundColor = .purple
edit.image = #imageLiteral(resourceName: "edit")
delete.backgroundColor = .red
delete.image = #imageLiteral(resourceName: "delete")
return UISwipeActionsConfiguration(actions: [delete, edit])
}
then I have errors.
Could not cast value of type 'abc.ListController' (0x1089b1398) to 'UITableViewCell' (0x1150f18e0).
how can I swipe to edit items?
Here you send self which is the vc in sender parameter
self.performSegue(withIdentifier: "EditItem", sender: self)
and force unwrap it to the cell which is the problem
if let indexPath = tableView.indexPath(for: sender as! UITableViewCell) {
Instead you can do
self.performSegue(withIdentifier: "EditItem", sender:items[indexPath.row])
with
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "EditItem" {
let controller = segue.destination as! ItemDetailViewController
controller.delegate = self
controller.itemToEdit = sender as! ItemType // where ItemType is the type of the array elements
}
}
}

Segue saves data on back bar button

I've a segue on TableViewCell click, and a save button on another ViewController, it works fine on save button, but it also changes TableViewCell when i click on back bar button. How can i discard changes? There are NoteTableViewController and NoteViewController
TableViewController methods
tableView cellRowAt method
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "NoteTableViewCell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! NoteTableViewCell
let note = notes[indexPath.row]
cell.noteLabel.text = note.note
return cell
}
TableViewController prepare for segue method
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ShowDetail" {
let noteDetailViewController = segue.destination as! NoteViewController
if let selectedNoteCell = sender as? NoteTableViewCell {
let indexPath = tableView.indexPath(for: selectedNoteCell)!
let selectedNote = notes[indexPath.row]
noteDetailViewController.note = selectedNote
noteDetailViewController.oldNote = someNote
}
}
}
This method is for saving data, it connected to saveButton of NoteViewController
#IBAction func unwindToNoteList(sender: UIStoryboardSegue) {
if let sourceViewController = sender.source as? NoteViewController {
let note = sourceViewController.note
if note?.note != nil {
if let selectedIndexPath = tableView.indexPathForSelectedRow {
notes[selectedIndexPath.row] = note!
tableView.reloadRows(at: [selectedIndexPath], with: .none)
} else {
let newIndexPath = NSIndexPath(row: notes.count, section: 0)
notes.append(note!)
tableView.insertRows(at: [newIndexPath as IndexPath], with: .top)
}
saveNotes()
}
}
}
NoteViewController methods
prepare for segue
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if saveButton === sender as? UIBarButtonItem {
if let noteText = noteTextView.text {
note?.note = noteText
}
note?.toDate = remindDate
note?.image = photoImageView.image
} else {
note = oldNote
}
}
Problem solved, by implementation NSObject copy() method. And sending a copy to NoteViewController.

Swipe to edit sqlite database content in swift

Im trying to make an app that stores personal details which can be deleted, edited using editActionsForRowAtIndexPath. The delete option seems to work fine but I am having problems with the edit action.
I get an error as I've mentioned below:
Could not cast value of type 'Table_view.UserTableViewController' (0x10a1991b0) to 'NSIndexPath' (0x10a5e8438).
UserRecordViewController is the View Controller where the personal details are to be displayed. And InsertRecordViewController is the other View Controller.
UserTableViewController relevant Code :
func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {
// 1
let editAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "Edit" , handler: { (action:UITableViewRowAction, indexPath:NSIndexPath) -> Void in
self.performSegueWithIdentifier("editSegue", sender: self)
})
editAction.backgroundColor = UIColor.darkGrayColor()
// let editIndex = editAction.indexOfAccessibilityElement(indexPath.row)
let deleteAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "Delete" , handler: { (action:UITableViewRowAction, indexPath:NSIndexPath) -> Void in
let userInfo: UserInfo = self.marrUserData.objectAtIndex(indexPath.row) as! UserInfo
let isDeleted = ModelManager.getInstance().deleteUserData(userInfo)
if isDeleted {
Util.invokeAlertMethod("", strBody: "Record deleted successfully.", delegate: nil)
} else {
Util.invokeAlertMethod("", strBody: "Error in deleting record.", delegate: nil)
}
self.getUserData()
})
deleteAction.backgroundColor = UIColor.lightGrayColor()
return [deleteAction, editAction]
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.identifier == "editSegue"){
let selectedIndexPath = sender as! NSIndexPath
let index = selectedIndexPath.row
//if let indexPath: NSIndexPath = self.tableView.indexPathForCell(sender as! UITableViewCell) {
//let btnEdit : UIButton = sender as! UIButton
//let selectedIndex : Int = btnEdit.tag
let viewController : InsertRecordViewController = segue.destinationViewController as! InsertRecordViewController
viewController.isEdit = true
viewController.userData = self.marrUserData.objectAtIndex(index) as! UserInfo
// }
}
}
I would like to know where I'm going wrong. Any idea guys?
Thanks in advance!!
I had the same exact problem and I was able to solve it by adding a few extra things in the func prepareForSegue
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.identifier == "editSegue"){
let cell = sender as! UITableViewCell
let Path = tableView.indexPathForCell(cell)
let index = Path?.row
let viewController : InsertRecordViewController = segue.destinationViewController as! InsertRecordViewController
viewController.isEdit = true
viewController.userData = self.marrUserData.objectAtIndex(index!) as! UserInfo
// }
}
Now I realize this isn't exactly what you're trying to do. Because with my method you just click on the cell and it directs you immideatly to the path instead of sliding. I wanted to do the slide as well but I couldn't figure it out so I just with with the above method. Hope this helps :)
The line that cause the error is: let selectedIndexPath = sender as! NSIndexPath
Sender is the SelectedCell, not the IndexPath!
Before the line self.performSegueWithIdentifier("editSegue", sender: self) set an property of the class (selectedIndexPath) to indexPath, and then you access this property from prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?).
Another method to do this can be found at: https://developer.apple.com/library/ios/referencelibrary/GettingStarted/DevelopiOSAppsSwift/Lesson9.html#//apple_ref/doc/uid/TP40015214-CH9-SW1

Resources