Double tap on cell in UITableview to perform segue - ios

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.

Related

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 :=)

Segue lag, tableview in Swift

I am working at my first application in Swift 3. I am using tableView (by "extension MainController: UITableViewDataSource"). And from this tableView, by storyboard I have two segues. One for editing (by clicking on accessory icon) and the second one for more detail screen (by clicking on a table row). I am not calling this segues by code, but by storyboard.
And my problem is that there is sometimes huge lag. Like after clicking on a row, the next screen is showing after 30 seconds. But now always. Sometimes its working immediately. Interesting thing is that when I touch row 1, and nothing happens, next I am clicking row 2 and then row 1 is appearing.
I am also using delegates, this is the code for preparing segues:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// 1
if segue.identifier == "AddSensor" {
// 2
let navigationController = segue.destination
as! UINavigationController
// 3
let controller = navigationController.topViewController
as! AddController
// 4
controller.delegate = self
}
else if segue.identifier == "EditSensor" {
let navigationController = segue.destination
as! UINavigationController
let controller = navigationController.topViewController
as! AddController
controller.delegate = self
if let indexPath = tableView.indexPath(
for: sender as! UITableViewCell) {
controller.sensorToEdit = sensors[indexPath.row]
}
}
else if segue.identifier == "DetailSeq" {
let navigationController = segue.destination
as! UINavigationController
let controller = navigationController.topViewController
as! DetailController
controller.delegate = self
if let indexPath = tableView.indexPath(
for: sender as! UITableViewCell) {
controller.sensorRecieved = sensors[indexPath.row]
}
}
}
I was reading that it was common bug in iOS8 and could be resolved by adding
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
DispatchQueue.main.async {
self.performSegue(withIdentifier: "DetailSeq",sender: self)
}
}
But it didn't worked for me. I don't know what should I do next to resolve this problem. Can anyone guide me?
According to this SO question, you may be able to fix your bug if you present your view controller in code rather than with the segue in the storyboard. Something like this, where your destination is the VC that you want to go to.
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath:
NSIndexPath) {
DispatchQueue.main.async {
self.presentViewController(destination, animated: true) { () -> Void
in
}
}
}
You have to use like this :
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
DispatchQueue.main.async {
self.performSegue(withIdentifier: "DetailSeq",sender: self)
}
}

Data sent on button press persists when different button is pressed

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

Xcode: Passing Information from UITableViewController to UIViewController

I have a UIViewController which should show me DetailInformations depending on what Cell was pressed in the UITableViewController.
For the moment I am passing them through a sequel:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "show" {
var ctrl = segue.destination as! DetailViewController
ctrl.information = _informationList[id]
}
}
The id variable is set through:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
id = indexPath.row
}
Now in my UIViewController I change the information with:
override func viewDidLoad() {
super.viewDidLoad()
setInformation(i: information)
}
Now my problem is, that if I press, lets say cell 2. It switches to the ViewController and shows Information of cell 1. Than I go back to the tableview and I press cell 3. Then it shows me cell 2.
In short, it seems that the viewController is loaded (with the last information), before it sets the new information.
Is there any better way to solve this?
Try using indexPathForSelectedRow in prepareForSegue as of it looks like that you have created segue from UITableViewCell to the Destination ViewController so that prepareForSegue will call before the didSelectRowAt.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "show" {
var ctrl = segue.destination as! DetailViewController
if let indexPath = self.tableView.indexPathForSelectedRow {
ctrl.information = _informationList[indexPath.row]
}
}
}
I am assuming based on what you are describing is that you used a segue in your Storyboard to link directly from the cell to the detail view controller. This is not what you want to do, as mentioned earlier, because you don't get the order of events you would expect. You could use the delegation design pattern for this, but assuming you want to stick with segues you need to make the "show" segue from the table VC itself to the detail VC. You then manually call the segue from the tableView didSelectRowAt code.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
id = indexPath.row
performSegue(withIdentifier: "show", sender: self)
}
Finally, you could then use an unwind segue when you come back to catch any data changes initiated in the detail VC.

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
}
}

Resources