Is it possible to implement an undo manager when you delete a row in a tableView?
I have read about undo manager and you have to set the viewController as becomeFirstResponder().
The problem is that I am using a tableView inside a collectionViewCell
How shall I proceed with registering an undo?
I tried the following and it doesn't seem to work
#IBAction func tableButtonUndo(_ sender: Any) {
print("undo working")
undoItem()
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
indexPathSelected = indexPath.row
if tableView == ordersTableView {
print("tap in kitchen table view")
self.removeItemKitchen()
ordersTableView.reloadData()
}
}
func removeItemKitchen() {
orderSingle.orderListFormatted.remove(at: indexPathSelected)
undoManager?.registerUndo(withTarget: MyVC(), selector: #selector(self.undoItem), object: nil)
undoManager?.setActionName("Undo")
}
func undoItem() {
undoManager?.registerUndo(withTarget: MyVC(), selector: #selector(self.removeItemKitchen), object: nil)
undoManager?.setActionName("Undo")
undoManager?.undo()
}
This code should undo the operation, but instead, I get a redo, it deletes the next row.
I would like to recover the deleted row by tapping the undo button
Related
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.
I have viewcontroller and it has split by two views. one is view which cotrolled by tableviewcontroller which is childcontroller of viewcontroller, and one is statusview which is show data directly when tableviewcontrollersdidselectrowatindexpathis called. but when i cant reload my data in statusview when i called didselectrowatindexpath. i can relaod my data in tableview but status view does not reflect data.
the darkgray area is statusview and middle view is tableview.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.tableView.reloadData()
}
by this code i can reload my data in tableview . but when i use setneedsDisplay or setneedslayout of parentController(viewcontroller) it crash. how can i solve this problem?
In this case you will need one of these two options:
Create a protocol that your main UIViewController will implement, so that UITableViewController can pass data via the delegate to its parent view controller
Post a UINotification in the UITableViewController and receive this notification in your status bar view and display the data.
Lets explore both options:
Define a protocol, in this example I am only sending a String of the cell that was tapped on:
#objc protocol YourDataProtocol {
func didSelectCell(withString string: String)
}
Next add a delegate property to your UITableViewController
class YourTableViewController: UIViewController {
weak var delegate: YourDataProtocol?
...
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)
//call the delegate method with your text - in this case just text from textLabel
if let text = cell?.textLabel?.text {
delegate?.didSelectCell(withString: text)
}
}
}
Make your UIViewContoller be the delegate of the UITableViewController subclass:
class YourViewController: UIViewController, YourDataProtocol {
...
let yourTableVC = YourTableViewController(...
yourTableVC.delegate = self
func didSelectCell(withString string: String) {
statusBar.text = string//update the status bar
}
}
Second option is with using NotificationCenter
In your UITableViewController you post a notification
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)
if let text = cell?.textLabel?.text {
let notificatioName = Notification.Name("DataFromTableViewCell")
NotificationCenter.default.post(name: notificatioName, object: nil, userInfo: ["YourData": text])
}
}
In status bar you start listening to this notification
NotificationCenter.default.addObserver(self, selector: #selector(didReceiveData(_:)), name: notificatioName, object: nil)
#objc func didReceiveData(_ notification: Notification) {
if let userData = notification.userInfo, let stringFromCell = userData["YourData"] {
print(stringFromCell)
}
}
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.
I need to trigger a function by clicking on a tableViewCell,
until now I used #IBAction, but that option is only available with button type (I haven't found another way..)
this is the way I now of:
#IBAction func springPrs(_ sender: Any) {
//doing stuff..
}
but now I have an #IBOutlet
#IBOutlet weak var nextTrackCell: nextTableViewCell!
and I want to trigger a function by clicking on it. Any help?
This is a wrong approach, you should implement a delegate method from UITableViewDelegate called didSelectRowAt:
public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//do your stuff here.
}
You shouldn't add an action directly to a table view cell because it violates the MVC design pattern and there is a handy callback already built into UITableViewDelegate to make this really easy.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.row == 0 {
// do something when the top row tapped
} else {
// do something when any other row is tapped
}
}
You could also declare a closure inside the cell, this is what I tend to use when having to pass some action to the view controller.
var onButtonPressed: (() -> ())?
#IBAction func buttonPressed(_ sender: Any) {
onButtonPressed?()
}
And use it like so in cellForRowAt:
cell.onButtonPressed = { [unowned self] in
// Do what you need to, no need to capture self however, if you won't access it.
}
I have an app that pulls objects from Firebase, then displays them in a table. I've noticed that if I delete 5 entries (this is about when I get to the reused cells that were deleted), I can't delete any more (red delete button is unresponsive) & can't even select the cells. This behavior stops when I comment out override func prepareForReuse() in the TableViewCell.swift controller. Why???
The rest of the app functions normally while the cells are just unresponsive. Weirdly, if I hold one finger on a cell and tap the cell with another finger, I can select the cell. Then, if I hold a finger on the cell and tap the delete button, that cell starts acting normally again. What is happening here??? Here is my code for the table & cells:
In CustomTableViewCell.swift >>
override func prepareForReuse() {
// CELLS STILL FREEZE EVEN WHEN THE FOLLOWING LINE IS COMMENTED OUT?!?!
cellImage.image = nil
}
In ViewController.swift >>
override func viewDidLoad() {
super.viewDidLoad()
loadUserThings()
}
func loadUserThings() {
ref.child("xxx").child(user!.uid).child("yyy").queryOrdered(byChild: "aaa").observe(.value, with: { (snapshot) in
// A CHANGE WAS DETECTED. RELOAD DATA.
self.arr = []
for tempThing in snapshot.children {
let thing = Thing(snapshot: tempThing as! DataSnapshot)
self.arr.append(thing)
}
self.tableView.reloadData()
}) { (error) in
print(error)
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomTableViewCell
let cellData = arr[indexPath.row]
...
// SET TEXT VALUES OF LABELS IN THE CELL
...
// Setting image to nil in CustomTableViewCell
let imgRef = storageRef.child(cellData.imgPath)
let activityIndicator = MDCActivityIndicator()
// Set up activity indicator
cell.cellImage.sd_setImage(with: imgRef, placeholderImage: nil, completion: { (image, error, cacheType, ref) in
activityIndicator.stopAnimating()
delay(time: 0.2, function: {
UIView.animate(withDuration: 0.3, animations: {
cell.cellImage.alpha = 1
})
})
})
if cell.cellImage.image == nil {
cell.cellImage.alpha = 0
}
// Seems like sd_setImage doesn't always call completion block if the image is loaded quickly, so we need to stop the loader before a bunch of activity indicators build up
delay(time: 0.2) {
if cell.cellImage.image != nil {
activityIndicator.stopAnimating()
cell.cellImage.alpha = 1
}
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// instantly deselect row to allow normal selection of other rows
tableView.deselectRow(at: tableView.indexPathForSelectedRow!, animated: false)
selectedObjectIndex = indexPath.row
self.performSegue(withIdentifier: "customSegue", sender: self)
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
print("should delete")
let row = indexPath.row
let objectToDelete = userObjects[row]
userObjects.remove(at: row)
ref.child("users/\(user!.uid)/objects/\(objectToDelete.nickname!)").removeValue()
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
if (self.tableView.isEditing) {
return UITableViewCellEditingStyle.delete
}
return UITableViewCellEditingStyle.none
}
A few things. For performance reasons, you should only use prepareForReuse to reset attributes that are related to the appearance of the cell and not content (like images and text). Set content like text and images in cellForRowAtIndexPath delegate of your tableView and reset cell appearance attributes like alpha, editing, and selection state in prepareForReuse. I am not sure why it continues to behave badly when you comment out that line and leave prepareForReuse empty because so long as you are using a custom table view cell an empty prepareForReuse should not affect performance. I can only assume it has something to do with you not invoking the superclass implementation of prepareForReuse, which is required by Apple according to the docs:
override func prepareForReuse() {
// CELLS STILL FREEZE EVEN WHEN THE FOLLOWING LINE IS COMMENTED OUT?!?!
super.prepareForReuse()
}
The prepareForReuse method is only ever intended to do minor cleanup for your custom cell.