Data sent on button press persists when different button is pressed - ios

Issue: I have a table view in complaintController which has "open" buttons in it. Whenever I press on the open button I segue to DetailComplaintViewController with the data of that row, but when I go back to ComplaintController and select a different button I am presented with the same data from previous selection.
NOTE - I have created a segue from button in cell of tableView.
This is the code I am using to segue from ComplaintController to DetailComplaintController.
var passRow = 0
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let billCell = tableView.dequeueReusableCell(withIdentifier: "complaintCell") as! ComplaintTableViewCell
billCell.openBtn.tag = indexPath.row
billCell.openBtn.addTarget(self, action: #selector(btnClicked), for: .touchUpInside)
return billCell
}
func btnClicked(sender: UIButton) {
passRow = sender.tag
print("Position......\(sender.tag)")
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let rVC = segue.destination as? DetailComplaintViewController
rVC?.issueType = self.complaintListArray[passRow].issueType
rVC?.issuedescription = self.complaintListArray[passRow].description
rVC?.issuedate = self.complaintListArray[passRow].issueDate
}

The problem is that the prepare for segue will be called before your btnClicked function and thats why you are not getting the correct data.
A quick fix for your situation would be to get the button tag inside the prepare for segue method like this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let button = sender as? UIButton else {
print("Segue was not called from button")
return
}
let row = button.tag
let rVC = segue.destination as? DetailComplaintViewController
rVC?.issueType = self.complaintListArray[row].issueType
rVC?.issuedescription = self.complaintListArray[row].description
rVC?.issuedate = self.complaintListArray[row].issueDate
}
Other option is to remove the segue from the button and create it on the view controller and programatically perform that segue inside your btnClicked method which is explained in this answer

Related

Double tap on cell in UITableview to perform segue

I am attempting to have the user segue to another view controller when tapping a cell. Currently I have my cell segue set up by doing the control drag in the story board to my view controller and then passing in the following code for a prepare(for segue...). Basically, there are my guard statements confirming I have the correct view controller and then I pass an object between them.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
switch(segue.identifier ?? "") {
case "ShowEventDetailsSegue":
guard let navVC = segue.destination as? UINavigationController else {
fatalError("Unexpected destination: \(segue.destination)")
}
guard let EventDetailViewController = navVC.viewControllers.first as? EventViewController else {
fatalError("Unexpected navigation view controller: \(String(describing: navVC.viewControllers.first))")
}
guard let selectedEventCell = sender as? EventTableViewCell else {
fatalError("Unexpected sender: \(String(describing: sender))")
}
guard let indexPath = tableView.indexPath(for: selectedEventCell) else {
fatalError("The selected cell is not being displayed by the table")
}
let selectedEvent = events[indexPath.section]
EventDetailViewController.event = selectedEvent
default:
fatalError("Unexpected Segue Identifier; \(String(describing: segue.identifier))")
}
}
I know that other people recommend calling something like
DispatchQueue.main.async {
self.present(myVC, animated: true, completion: nil)
}
(see here and also here)
but when I do that, I get an error "Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Application tried to present modally an active controller".
Basically, I want to be able to have the user only click one time on the cell and still be able to pass data over to the other view controller.
What is also strange is that this just started happening. I did not have this issue before today.
So what you are doing is :
In tableView didSelectRowAt method you are adding :
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.performSegue(withIdentifier: "ShowEventDetailsSegue", sender: nil)
}
which triggers the delegate method:
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
there is already a controller preparing to open and you are presenting a different controller programmatically, which obviously will throw an error.
Either you write a different logic for presenting view programmatically in tableView didSelectRowAt or you can shift the entire code in the second view controller viewDidLoad method.
Turns out, the selection style of my cell is set to none.
cell.selectionStyle = UITableViewCellSelectionStyle.none
I don't want a selection style though, so the way around this is to make an asynchronous call
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
DispatchQueue.main.async {
self.performSegue(withIdentifier: "ShowEventDetailsSegue", sender: nil)
}
}
This now fixes the issue. For more info, check out this post.

Perform a segue using a button in a custom cell and passing on an object

I'm trying to write a tablet tracking app for my mother but I'm having an issue I'm using an edit button to access a static table view but it is refusing to pass on the medicine object.
I need to be able to get the indexPath in prepare(for:sender:) and I'd usually use tableView.indexPathForSelectedRow and then use the row to pull the right one from the array but as I'm using a button that's not possible.
I've tried using the tag to store the row, I've tried a protocol as suggested in other answers here but I've had no success. The app still crashes when I'm trying to go to the edit screen. I have my current code below, gone through too many iterations to list them all
#IBAction func editButtonTapped(_sender: UIButton) {
let point = sender.convert(CGPoint.zero to self.tableView)
buttonIndexPath = self.tableView.indexPathForRow(at: point)
preformSegue(withIdentifier: "showDetails", sender: sender)
}
and my prepare(for:sender:) code is as follows
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetails" {
let destination = segue.destination as! MedicineDetailTableViewController
let selectedMedicine = medicines[(buttonIndexPath?.row)!]
destination.medicine = selectedMedicine
}
}
I apologise if this is something stupid that I'm missing but I'm at wit's end.
You can setup delegate for for custom cell with function like
func buttonTapped(_ sender: UITableViewCell)
Then You can use sender to get IndexPath with indexPath(for:) on tableView and perform segue.
Then Your tableView(_:cellForRowAt:) can look like this:
tableView(_:cellForRowAt:){
//setup other stuff and dequeue cell
cell.delegate = self
return cell
}
In this case, I'm following these steps:
1) Add 'Your Edit Button' into your UITableViewCell and create IBOutlet.
class YourTableViewCell: UITableViewCell {
#IBOutlet weak var yourEditButton: UIButton!
}
2) In your cellForRowAtIndexPath method, assign button tag as index.
cell.yourEditButton.tag = indexPath.row
3) Create IBAction in your View Controller. Open storyboard and connect "your edit button" with this IBAction function.
class YourViewController: UIViewController {
let selectedMedicine: Medicine? //your global Medicine variable
#IBAction func editButtonTapped(_sender: UIButton) {
//now you can access selected row via sender.tag
selectedMedicine = medicines[sender.tag]
preformSegue(withIdentifier: "showDetails", sender: nil)
}
}
Finally:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetails" {
let destination = segue.destination as! MedicineDetailTableViewController
destination.medicine = selectedMedicine
}
}

How to save user's input in variable using delegate?

I'm making ios app. Please look my table view image below.
If you click add(+) button, you can add ENG word and its meaning in Korean(KOR) in each textfield.
After filling the textfield and click save button (it is located on right-top, "저장"), the word is added like the image below.
word is added
For example, the ENG is endless and the meaning(KOR) is "끝없는".
And, I want to use UIReferenceLibraryViewController .
If i click the cell of the list, i want to show its dictionary.
#IBAction func viewDictionary(_ sender: UITapGestureRecognizer) {
let engDictionaryWord = **engListWord**
let ViewController = UIReferenceLibraryViewController(term: engDictionaryWord)
self.present(ViewController, animated: true, completion: nil)
}
I want to use this method.
But, I don't know how to save my ENG input in engListWord.
In pic2 's swift file(addWordViewController.swift), there is prepare() method like this.
// This method lets you configure a view controller before it's presented.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
// Configure the destination view controller only when the save button is pressed.
guard let button = sender as? UIBarButtonItem, button === saveButton else {
os_log("The save button was not pressed, cancelling", log: OSLog.default, type: .debug)
return
}
let eng = engTextField.text ?? ""
let kor = korTextField.text ?? ""
// Set the meal to be passed to WordTableViewController after the unwind segue.
word = Word(eng:eng, kor:kor)
}
and viewDidLoad() method in addWordViewController.swift
override func viewDidLoad() {
super.viewDidLoad()
engTextField.delegate = self
korTextField.delegate = self
// Set up views if editing an existing Word.
if let word = word{
engTextField.text = word.eng
korTextField.text = word.kor
}
// Do any additional setup after loading the view.
}
I don't know which variable i have to use.
There is other swift file in my project, If i misuploaded that codes above, please tell me! I will edit my question immediately.
Main.Storyboard
If i use GestureRecognizer, i made this code but I don't know it is right...
#IBAction func MyDictionary(_ sender: UITapGestureRecognizer) {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "viewDictionary", sender: indexPath)
}
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// I just made this identifier up, but create one yourself in the storyboard
if segue.identifier == "viewDictionary" {
// Define your vc
let libController = segue.destination as! UIReferenceLibraryViewController
// Define indexPath
let indexPath = sender as! IndexPath
// Set value in destination vc
libController.engWord = words[indexPath.row].eng
}
}
}
I think the way to go is to use the UITableViewDelegate method didSelectRowAtindexPath in the WordTableViewController (if that's the class with your table view).
A gestureRecognizer does not seem like something you want in your tableViewController as it has nice build in delegate methods to register user presses. Therefore, replace your viewDictionary(_:) function with the following:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "viewDictionary", sender: indexPath)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// I just made this identifier up, but create one yourself in the storyboard
if segue.identifier == "viewDictionary" {
// Define your vc
let libController = segue.destination as! UIReferenceLibraryViewController
// Define indexPath
let indexPath = sender as! IndexPath
// Set value in destination vc
libController.engWord = yourArrayOfEngWords[indexPath.row]
}
}
This will get your eng word of the cell you have pressed and save it to an attribute "engWord" that you then define in your UIReferenceLibraryViewController:
class UIReferenceLibraryViewController(){
var engWord: String = ""
// Rest your class properties and functions here...
}
Once it is set you can use it as you like after the segue has been performed :=)

The cell I press in my table sends the label for the previously pressed cell

I am new to Swift and I have this interesting problem.
I am trying to send the label of a table cell when I segue to another view controller where I print it. The problem is that it is printing the label of the cell that was pressed previous to this press.
Here is the code in the main view controller that passes the label:
// When a user taps on a cell on the tableView, it asks for a tag name for that image.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("Cell \(indexPath) selected")
// Get cell image.
let indexPath = tableView.indexPathForSelectedRow
let currentCell = tableView.cellForRow(at: indexPath!) as! ImageFeedItemTableViewCell
imagePass = currentCell.itemImageView
labelPass = currentCell.itemTitle
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Send image to GreetingViewController.
if segue.identifier == "GoToGreeting" {
var greetingvc = segue.destination as! GreetingViewController
greetingvc.passedImage = imagePass
greetingvc.passedLabel = labelPass
}
}
and here is the relevant code in the view controller that receives the passed label:
var passedImage: UIImageView? = nil
var passedLabel: UILabel? = nil
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
print(passedLabel?.text)
}
Any help would be appreciated.
I believe prepare(for:sender:) gets called before tableView(_:didSelectRowAt:) when you hook up that segue in the storyboard.
What you can do is just use the sender parameter of the prepare(for:sender:) method to get the information you need at the right time. When a segue is triggered by a cell, as it seems to be in your case, then that cell will be the sender passed into the the prepare method. So, you could do something like:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Send image to GreetingViewController.
if let cell = sender as? ImageFeedItemTableViewCell, segue.identifier == "GoToGreeting" {
var greetingvc = segue.destination as! GreetingViewController
greetingvc.passedImage = cell.itemImageView
greetingvc.passedLabel = cell.itemTitle
}
}

Send variable value to next view controller when click a table cell

I have two table view controllers
InvoiceList view controller
InvoiceShow view controller
I use didSelectRowAtIndexPath method as bellow to get selected table cell specific value
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let rowObject = objects[indexPath.row]
let invoicehash = rowObject["hash_key"]!
}
i need to send invoicehash value to InvoiceShow controller when click the table cell of InvoiceList
i tried to use prepareForSegue function. but it is not applicable because it will trigger before the didSelectRawAtIndexPath function. so when i implemented it, gives the previous click event variable value. not correct one.
Please help me to access invoiceHash variable value from InvoiceShow controller
You will get the selected cell in prepareForSegue method itself.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let selectedIndexPath = self.tableView.indexPathForSelectedRow()!
let rowObject = objects[selectedIndexPath.row]
let invoiceHash = rowObject["hash_key"]!
let invoiceShowViewController = segue.destinationViewController as! InvoiceShowViewController
// Set invoiceHash to `InvoiceShowViewController ` here
invoiceShowViewController.invoiceHash = invoiceHash
}
You can still use a segue if you want and/or already setup on your storyboard.
You just need to connect the two view controllers in Interface Builder directly from one to another.
So, start ctrl-dragging from the controller itself and not from the TableViewCell (take a look at the screenshot)
then use the performSegueMethod with the new segue identifier like this:
self.performSegueWithIdentifier("mySegueIdentifier", sender: self)
and finally, your prepareForSegue method:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "mySegueIdentifier" {
let selectedIndex = self.invoiceTableView.indexPathForSelectedRow
//if element exist
if selectedIndex?.row < myDataSourceArray.count {
let destination = segue.destinationViewController as! InvoiceShowViewController
let invoice = myDataSourceArray[selectedIndex!.row]
destination.invoice = invoice
}
}
}
That's it!

Resources