Hi I'm developing an app with custom action in tableview cell.
when the user swipe the cell. There will be 2 action: delete and more. I want more action to present a new viewController and pass the object data to the new viewController. How do we achieve that?
I have tried using this
in viewDidLoad
if let split = self.splitViewController {
let controllers = split.viewControllers
self.detailViewController = (controllers[controllers.count-1] as! UINavigationController).topViewController as? DetailViewController
}
and editActionsForRowAt method
override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let moreRowAction = UITableViewRowAction(style: .normal, title: "More", handler:{action, indexpath in
if let indexPath = self.tableView.indexPathForSelectedRow {
let object = self.fetchedResultsController.object(at: indexPath)
let controller = self.detailViewController!
controller.detailItem = object
controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem
controller.navigationItem.leftItemsSupplementBackButton = true
self.present(controller, animated: true, completion: nil)
}
// self.performSegue(withIdentifier: "showDetail", sender: self)
});
moreRowAction.backgroundColor = UIColor(red: 0.298, green: 0.851, blue: 0.3922, alpha: 1.0);
let deleteRowAction = UITableViewRowAction(style: .destructive, title: "Delete", handler:{action, indexpath in
self.deleteTapped(indexPath: indexPath as NSIndexPath)
});
return [deleteRowAction, moreRowAction];
}
The delete function work but the more action does not work. And help is much appreciate! Thanks
My prepareforseuge
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetail" {
if let indexPath = self.tableView.indexPathForSelectedRow {
let object = self.fetchedResultsController.object(at: indexPath)
let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
controller.detailItem = object
controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem
controller.navigationItem.leftItemsSupplementBackButton = true
}
}
}
You can pass your object via the sender parameter to your segue:
let moreRowAction = UITableViewRowAction(style: .normal, title: "More", handler:{action, indexpath in
let object = self.fetchedResultsController.object(at: indexPath)
self.performSegue(withIdentifier: "showDetail", sender: object)
});
Then in prepare(for segue: sender:) -
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetail" {
guard let navVC = segue.destination as? UINavigationController,
let destVC = navVC.viewControllers.first as? DetailViewController else {
return
}
var object = sender as? Event
if object == nil {
if let indexPath = self.tableView.indexPathForSelectedRow {
object = self.fetchedResultsController.object(at: indexPath)
}
}
destVC.detailItem = object
}
}
The Master-Detail template project uses a navigation controller as the detail destination, so the prepare(for segue) function needs to access the navigation controller's first view controller.
Also, since you are using the same segue for row tap and "more" actions, my code checks for both possibilities - the detail item could be in the sender or in the selected row
Related
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 }
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)")
}
}
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
}
}
}
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.
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