I currently have a sidebar menu in an iOS 9.2 app running on Xcode 7.2 written in Swift 2 that allows the user what data to load to populate the view. I'm using SWRevealViewController to create that sidebar. I have a container view controller which has the front page and the sidebar page listing all the options the user has. Each time the user selects a cell from the sidebar table, it performs a segue that allows the front page to be refreshed. What I want to do is to allow the user to delete a cell from the table with a long press. I'd like to show an AlertViewController to confirm the user's decision, and if "Yes" is selected, I want to delete the cell, and select the very first item in the table. I've tried following the instructions from Long press on UITableView
but I keep getting the error "unrecognized selector sent to instance"
Here's the code that I'm using for setting up the gestureRecognizer in the cellForRowAtIndexPath function:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as UITableViewCell;
cell.textLabel!.text = tableArray[indexPath.row];
let holdToDelete = UILongPressGestureRecognizer(target: cell, action: "longPressDelete");
holdToDelete.minimumPressDuration = 1.00;
cell.addGestureRecognizer(holdToDelete);
return cell;
}
And here's the longPressDelete function:
func longPressDelete(sender: UILongPressGestureRecognizer) {
let alert: UIAlertController = UIAlertController(title: "Please Confirm", message: "Are you sure you want to delete this car from your database?", preferredStyle: .Alert);
alert.addAction(UIAlertAction(title: "Yes", style: .Destructive, handler: { (UIAlertAction) -> Void in
if let tv = self.tableView {
let point: CGPoint = sender.locationInView(self.tableView);
let indexPath: NSIndexPath = self.tableView.indexPathForRowAtPoint(point)!;
tv.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade);
NSUserDefaults.standardUserDefaults().removeObjectForKey("fillUp" + tableArray[indexPath.row]);
NSUserDefaults.standardUserDefaults().removeObjectForKey("services" + tableArray[indexPath.row]);
tableArray.removeAtIndex(indexPath.row);
NSUserDefaults.standardUserDefaults().setObject(tableArray, forKey: "cars");
self.deleted = true;
self.performSegueWithIdentifier("tableToDashboard", sender: self);
}
}));
alert.addAction(UIAlertAction(title: "No", style: .Default, handler: nil));
self.presentViewController(alert, animated: true, completion: nil);
}
Here's the prepareForSegue function:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (deleted) {
let indexPath: NSIndexPath = NSIndexPath(forRow: 0, inSection: 0);
fillUpKey = "fillUp" + tableArray[indexPath.row];
servicesKey = "services" + tableArray[indexPath.row];
localFillUpArray = [fillUp]();
} else {
let indexPath: NSIndexPath = self.tableView.indexPathForSelectedRow!;
fillUpKey = "fillUp" + tableArray[indexPath.row];
servicesKey = "services" + tableArray[indexPath.row];
localFillUpArray = [fillUp]();
}
}
What I'd like to happen is that the user deletes the item in the cell, and the app then performs a segue to the front screen after loading the information from another source. Thanks for taking the time to read this and possibly provide an answer. I hope I haven't made a rookie mistake somewhere.
Incorrect Selector
let holdToDelete = UILongPressGestureRecognizer(target: self,
action: "longPressDelete:");
: after longPressDelete indicates that the method func longPressDelete(sender: UILongPressGestureRecognizer) actually accepts parameters.
self for target, assuming that the selector belongs to the same class that registered it.
The current selector "longPressDelete" would match a method signature without parameters:
func longPressDelete() { }
Very simple example if you want to select cell in uitableview
let longGesture = UILongPressGestureRecognizer(target: self, action: #selector(ViewController.longTap))
cell.addGestureRecognizer(longGesture)
// longTap
func longTap(gestureReconizer: UILongPressGestureRecognizer) {
print("Long tap")
let longPress = gestureReconizer as UILongPressGestureRecognizer
_ = longPress.state
let locationInView = longPress.location(in: tableview)
let indexPath = tableview.indexPathForRow(at: locationInView)
// whatever you want with indexPath use it //
}
Related
I have a UIMenuController with a "Delete" menu item on top of a collection view cell which is displayed when the user long presses on a cell with section 1:
#IBAction func handleLongPressOnCell(_ sender: UILongPressGestureRecognizer) {
let p = sender.location(in: collectionView)
guard sender.state == .began, let indexPath = self.collectionView.indexPathForItem(at: p), let cell = self.collectionView.cellForItem(at: indexPath) else { return }
if indexPath.section == 1 {
let frameInSuperView = collectionView.convert(cell.frame, to: view)
let deleteItem = UIMenuItem(title: "Delete", action: #selector(deleteCell))
UIMenuController.shared.menuItems = [deleteItem]
UIMenuController.shared.setTargetRect(frameInSuperView, in: view)
becomeFirstResponder()
UIMenuController.shared.setMenuVisible(true, animated: true)
}
}
How do I pass the index path of the cell to the function below? I need this information to delete the object from the server.
#objc internal func deleteCell(sender: UIMenuItem) {
print("delete menu item tapped! print index path of selected collection view cell?")
}
As #mkeremkeskin pointed out, there's an answer to this where he linked.. but that answer is in Objective-C, here you'll find a Swift 4 version.
You can subclass the UIMenuItem and add the indexPath to it!
I had to remove some code for it to work in my playground, but you get the idea :)
class CustomMenuItem: UIMenuItem {
var indexPath: IndexPath?
convenience init(title: String, action: Selector, indexPath: IndexPath? = nil) {
self.init(title: title, action: action)
self.indexPath = indexPath
}
}
class ViewController {
func handleLongPressOnCell(_ sender: UILongPressGestureRecognizer) {
let indexPath = IndexPath(item: 0, section: 1)
if indexPath.section == 1 {
let deleteItem = CustomMenuItem(title: "Delete", action: #selector(deleteCell), indexPath: indexPath)
UIMenuController.shared.menuItems = [deleteItem]
UIMenuController.shared.setMenuVisible(true, animated: true)
}
}
#objc internal func deleteCell(sender: CustomMenuItem) {
guard let indexPath = sender.indexPath else { return }
// Delete item based on indexPath
}
}
You cannot directly pass the info along with the selector action; instead you should store index path in a member variable which you set in your long-press handler and consume in your delete handler.
private var indexPathForDeleting: IndexPath? = nil
Don't forget to do your housekeeping and clear the variable when it's no longer needed.
You can subclass menu item to get the necessary object.
An example has been answered here:
Pass value through UIMenuItem of UIMenuController
I solved this type of issue in this way:
let menuController = UIMenuController.shared
menuController.accessibilityHint = String(indexPath.row)
#objc func deleteCell(_ sender: UIMenuController) {
print("delete menu item tapped! index path? \(sender.accessibilityHint)")
}
I had using swift 4. Hope it will help.
Good day to all. Faced a problem. I need to make a table with a button and by clicking on the button I get a alert with the number of the cell. The table cells themselves are not active. That's how I realized it. When I scroll the table in the beginning everything is fine, when you press the button, a alert is displayed with the correct line number, but after 4 elements an error appears.
This error appears in the line where I'm working with the 4 tag.
Fatal error: unexpectedly found nil while unwrapping an Optional value
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as UITableViewCell
if (tableView.tag == 1) {
let numLabel: UILabel = tableView.viewWithTag(3) as! UILabel
numLabel.text = String(indexPath.row)
} else if (tableView.tag == 2) {
//Error appears here
let numButton: UIButton = tableView.viewWithTag(4) as! UIButton
numButton.setTitle(String(indexPath.row), for: .normal)
numButton.tag = indexPath.row
}
return cell
}
#IBAction func showAlertForRow(row: UIButton) {
let alert = UIAlertController(title:"Test work",message:"Cell at row \(row.tag) was tapped!",preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Okay", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
What you are designing for implementing this procedure is not correct. What you can do
Make a custom cell
Add a button in custom cell
Add action in that button in CellForRowAtIndexPath
Handle action from ViewController where you added tableView.
I made a whole project for you.Just to let you know. if you want to add customCell in tableView you need to register it like this in viewDidLoad.
i have done is in ViewController.swift file. check out my project.
let nib = UINib.init(nibName:String(describing: sampleTableViewCell.self) , bundle: nil)
tableView.register(nib, forCellReuseIdentifier: "chatCell")
Then check cellForRowAtIndexPath function:
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "chatCell", for: indexPath) as! sampleTableViewCell
cell.clickMeBtn.tag = indexPath.row
cell.clickMeBtn.addTarget(self, action: #selector(onButtonPressed(sender :)), for: .touchUpInside)
return cell
}
Button press function:
func onButtonPressed(sender:UIButton) {
let alert = UIAlertController.init(title:"Cell index is"+String(sender.tag), message: nil, preferredStyle: UIAlertControllerStyle.alert)
let okAction = UIAlertAction.init(title: "ok", style: UIAlertActionStyle.default) { (UIAlertAction) in
}
alert.addAction(okAction)
self.present(alert, animated: true, completion: nil)
}
Check only three files:
Github link
ViewController.swift
sampleTableViewCell.swift
sampleTableViewCell.xib**
Here is the output:
Alright, I am testing around a bit with core data, something I recently have started to discover, basically, what i have so far, is a single view app, that has a data source, and i can press a button and it brings up and alert, which from there i can add names to the list, and delete names from the list, i can close my app and still maintain my data. here is the issue/question, i am trying to do an update, so i can edit names in the list, i have a uibutton set up on my prototype cell, and i have it linked to my viewController, and have a function set inside the IBAction for the button. however, the button does not appear in my sim at run time.
here i have some code.
this is code for the edit button, and its function:
#IBAction func editButton(sender: AnyObject) {
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
// 2
let alert = UIAlertController(title: "Update",
message: "Please enter the new name.",
preferredStyle: .Alert)
// 3
let updateAction = UIAlertAction(title: "Save",
style: .Default){(_) in
let nameTextField = alert.textFields![0]
self.updateName(indexPath.row, newName: nameTextField.text!)
self.tableView.reloadData()
}
// 4
let cancelAction = UIAlertAction(title: "Cancel", style: .Cancel, handler: nil)
alert.addTextFieldWithConfigurationHandler(nil)
alert.addAction(updateAction)
alert.addAction(cancelAction)
// 5
self.presentViewController(alert, animated: true, completion: nil)
}
here is my cellForRowAtIndexPath func
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell")!
let people = peoples[indexPath.row]
cell.textLabel!.text = people.name
return cell
}
here is an image of my storyboard
Storyboard
if you need further information or code, please let me know and i will provide it.
I am developing app which users will choose one of the two pictures in one cell. My prototype cell looks like :
How can I detect when the user presses the vote button which cell is selected ?
My tableView Code :
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "NewTableViewCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! NewTableViewCell
//For left image
if let url:NSURL? = NSURL(string: self.polls[indexPath.row].poll_photo1 ){
cell.leftImage.sd_setImageWithURL(url)
}
//For right image
if let url:NSURL? = NSURL(string: self.polls[indexPath.row].poll_photo2 ){
cell.rightImage.sd_setImageWithURL(url)
}
//For user picture
if let url:NSURL? = NSURL(string: self.polls[indexPath.row].users[0].user_photo ){
cell.userPicture.sd_setImageWithURL(url)
}
// gets username and text
cell.userName.text=self.polls[indexPath.row].users[0].user_name
cell.description.text = self.polls[indexPath.row].poll_textfield
return cell
}
My web API:
I am assuming that your custom table view cell NewTableViewCell is having an outlet for your vote button.
Just tag your voteButton with indexPath.row and fetch the tag in its target function as shown below. You will get to know which cell's voteButton was tapped when you press your Vote Button
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("NewTableViewCell") as! NewTableViewCell
//Tagging with indexPath.row
cell.voteLeftButton.tag = indexPath.row
cell.voteRightButton.tag = indexPath.row
//This is the latest Swift 2.2 syntax for selector. If you are using the older version of Swift, Kindly check the selector syntax and make changes accordingly
cell.voteLeftButton.addTarget(self, action: #selector(voteLeftButtonPressed), forControlEvents: .TouchUpInside)
cell.voteRightButton.addTarget(self, action: #selector(voteRightButtonPressed), forControlEvents: .TouchUpInside)
return cell
}
func voteLeftButtonPressed(sender:UIButton){
print("Left Button Table cell clicked is \(sender.tag)")
}
func voteRightButtonPressed(sender:UIButton){
print("Right Button Table cell clicked is \(sender.tag)")
}
Add target/action to your button in cell configure method like this:
let tap : UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(YourController.tapAction(_:)))
Then implement tapAction method
func tapAction(sender : UITapGestureRecognizer)
{
if sender.state != UIGestureRecognizerState.Ended
{
return
}
let btn = sender.view as! UIButton
let pointTo : CGPoint = CGRectOffset(btn.bounds, btn.frame.size.width/2, btn.frame.size.height/2).origin;
let buttonPosition : CGPoint = btn.convertPoint(pointTo, toView: self.tableView)
let indexPath = self.tableView.indexPathForRowAtPoint(buttonPosition)
...
}
I'm a beginner of Swift, and I searched a lot of stuffs, but couldn't figure out and decided to post my first question ever here.
I have a table view to show tweets by using twitter fabric and I use UITableViewRowAction to present two options to users when a swipe is done on a row, "funnelOne" and "funnelTwo", to categorize their tweets by adding tags to each tweet.
In the view controller, I add two functions to make an alert and get a value of 'funnelTag' to store it to my core data.
However, I am not sure if I can correctly store the number to the core data because somehow different cell would be deleted if I push one of swipeable buttons. I know I can write a code within 'func tableView' to delete the row, but how I can get access from out of 'func tableView' to delete the row successfully??
If it can be resolved, I should be able to successfully store the value to my core data.
func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [AnyObject]? {
let cell = super.tableView(tableView, cellForRowAtIndexPath: indexPath) as? CustomTableViewCell
let funnelOne = UITableViewRowAction(style: .Default, title: "funnel") {
(action, indexPath) -> Void in
funnelTag = 1 // funnelTag is Int I want to store in my Core Data
cell!.tag = funnelTag
// self.tweets.removeAtIndex(indexPath.row) - I know this is working
tableView.setEditing(false, animated: true)
}
let funnelTwo = UITableViewRowAction(style: .Default, title: "funnel") {
(action, indexPath) -> Void in
funnelTag = 2
cell!.tag = funnelTag
tableView.setEditing(false, animated: true)
// self.tweets.removeAtIndex(indexPath.row)
}
These are two functions I add. if I implement these functions, top row would be deleted even though I want to delete other row... the first function, funnelTweet() is properly working, but the second function does not seem to work correctly..
func funnelTweet(cell: CustomTableViewCell){
let index: Int = cell.tag
if SettingStore.sharedInstance.isNoAlert(){
self.submitFunnel(index, cell: cell)
} else {
self.alert = UIAlertController(title: NSLocalizedString("stock_confirm_funnel", comment: ""), message: nil, preferredStyle: .Alert)
self.alert!.addAction(UIAlertAction(title: NSLocalizedString("common_ok", comment: ""), style: .Destructive) { action in
self.submitFunnel(index, cell: cell)
})
self.alert!.addAction(UIAlertAction(title: NSLocalizedString("common_cancel", comment: ""), style: .Cancel) { action in
cell.moveToLeft()
})
self.presentViewController(self.alert!, animated: true, completion: nil)
}
}
func submitFunnel(index: Int, cell: CustomTableViewCell){
var tweet = self.tweets[index]
// store to local storage
TagStore.sharedInstance.saveData(tweet.createdAt, funnelTag: funnelTag, id: tweet.tweetID)
self.tweets.removeAtIndex(index)
self.tableView!.reloadData()
}
Thank you for your help!
In the Second function you have not initialized the index before using it.
func submitFunnel(index: Int, cell: CustomTableViewCell){
// Initialize index here before using it in the next statement.. that is give it a value, otherwise it will return nil
var tweet = self.tweets[index]
// store to local storage
TagStore.sharedInstance.saveData(tweet.createdAt, funnelTag: funnelTag, id: tweet.tweetID)
self.tweets.removeAtIndex(index)
self.tableView!.reloadData()
}