UITableViewCell Segue SOMETIMES connects to wrong controller - ios

I am having a very weird issue with my tableView, sometimes you click a cell and segues as it should, but other times it will segue to a random detailViewController.
I have 3 segues connecting from a UIViewController that contains tableview:
The segues "present modally" a detailViewController and pass a custom object "place" to the detailViewController
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("PLACE SELECTED: SECTION \(indexPath.section) ROW \(indexPath.row) :: \(my_sections[indexPath.section])")
self.selectedPlace = my_sections[indexPath.section].rows[indexPath.row]
let buttons_count = self.selectedPlace!.buttons.count
switch buttons_count {
case 0:
self.performSegue(withIdentifier: SegueIdentifier.NoButton.rawValue, sender: self.tableview.cellForRow(at: indexPath))
case 1:
self.performSegue(withIdentifier: SegueIdentifier.OneButton.rawValue, sender: self.tableview.cellForRow(at: indexPath))
case 2:
self.performSegue(withIdentifier: SegueIdentifier.TwoButton.rawValue, sender: self.tableview.cellForRow(at: indexPath))
default:
break
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (sender as? PlaceTableViewCell) != nil {
if let indexPath = self.tableview.indexPathForSelectedRow {
let place = self.my_sections[indexPath.section].rows[indexPath.row]
navigationController?.view.backgroundColor = UIColor.clear
switch(segue.identifier!) {
case SegueIdentifier.NoButton.rawValue:
assert(segue.destination.isKind(of: PlaceDetailsViewController.self))
let vc = segue.destination as! PlaceDetailsViewController
vc.place = place
case SegueIdentifier.OneButton.rawValue:
assert(segue.destination.isKind(of: OneButtonViewController.self))
let vc = segue.destination as! OneButtonViewController
vc.place = place
case SegueIdentifier.TwoButton.rawValue:
assert(segue.destination.isKind(of: TwoButtonViewController.self))
let vc = segue.destination as! TwoButtonViewController
vc.place = place
default: break
}
}
}
}
The place object has place.buttons: [Button]
The three detailViewControllers are almost identical except they have different number of buttons.
The tableView decides which segue to use based on the size of place.buttons
Sometimes the tableView works like normal and other times it passes random cells. Unsure why.

You can simplify your prepare(for: method to solve this as follows:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
navigationController?.view.backgroundColor = UIColor.clear
switch(segue.identifier!) {
case SegueIdentifier.NoButton.rawValue:
assert(segue.destination.isKind(of: PlaceDetailsViewController.self))
let vc = segue.destination as! PlaceDetailsViewController
vc.place = self.selectedPlace
case SegueIdentifier.OneButton.rawValue:
assert(segue.destination.isKind(of: OneButtonViewController.self))
let vc = segue.destination as! OneButtonViewController
vc.place = self.selectedPlace
case SegueIdentifier.TwoButton.rawValue:
assert(segue.destination.isKind(of: TwoButtonViewController.self))
let vc = segue.destination as! TwoButtonViewController
vc.place = self.selectedPlace
default: break
}
}

Seems like my_sections is array. Try sort it before access to it's items. I have simmular problem, when programm gets data from persistent store and items in the data array were unordered.

I figured out the problem and I am sorry to the people who took the time here, I did not give enough information for anyone to solve this problem.
my tableview gets information from
var my_sections: [Section] = [] {
didSet {
return self.my_sections.sort(by: { $0.index < $1.index})
}
}
My "Section" model looks like this:
struct Section: Ordered {
var section: PTPlace.Section
var rows: [PTPlace]
var sorted_rows: [PTPlace] {
return self.rows.sorted(by: { $0.open_status.rawValue > $1.open_status.rawValue })
}
}
I noticed something was up when rows were getting mixed up WITHIN sections but not between sections.
So I added the following line to didSelectRowAtIndexPath method:
print("PLACE SELECTED: SECTION \(indexPath.section) ROW
\(indexPath.row) :: \(my_sections[indexPath.section].rows[indexPath.row].name)")
And I saw that the tableView was arranged very differently than how the rows were sorted, as if there were two different arrays. And sure as heck, that was the problem.
My tableView was displaying the sorted_rows array in the View while segueing to
my_sections[indexPath.section].rows[indexPath.row]
instead of my_section[indexPath.section].sorted_rows[indexPath.row]
Problem solved. Silly mistake. Thanks again to everyone who helped me simplify my code.

Related

How to preserve the original indexPath.row after applying filters to Table View?

My app uses "filter" buttons in which the whereField query is refined based on which filter buttons are pressed. This is an example before filtering:
But this is an example after filtering:
The issue is that when I click into one of the Rows, it takes me to the next page that corresponds to the original indexPath.row in my database belonging to that Row. How can I preserve the original indexPath.row? E.g., Cell B to always be indexPath.row = 1, even after filtering.
This is my cellForRowAt of my first View Controller.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Get a cell
let cell = tableView.dequeueReusableCell(withIdentifier: "MealPlanCell", for: indexPath) as! MealPlanCell
// Get the mealPlan that the tableView is asking about
let mealPlanInTable = mealPlan[indexPath.row]
// Customize the cell
cell.displayMealPlan(mealPlanInTable)
// Return the cell
return cell
}
And how I connect this View Controller's indexPath.row to the next View Controller after a cell is tapped:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Detect the indexPath the user selected
let indexPath = tableView.indexPathForSelectedRow
// Get the mealPlan the user selected
let mealPlanSelected = mealPlan[indexPath!.row]
// Get a reference to the NextiewController
let NextVC = segue.destination as! NextViewController
// Get a reference to the currentMealPlanIndex in the NextViewController
NextVC.currentMealPlanIndex = indexPath!.row
}
Any advice is much appreciated!
You are getting values from wrong array. Also it's better to pass the obj instead of index.
You need to have 2 variables - one for all data & other for filtered data.
Use filtered data var in tableview datasource & for passing to NextVC.
Considering your class name is MealPlan. Here is the source.
var allMealPlans: [MealPlan]
var filteredMealPlans: [MealPlan]
func onFilterButtonPressed() {
filteredMealPlans = allMealPlans.filter({
// return true/false here based on your filters
})
tableView.reloadData()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Get a cell
let cell = tableView.dequeueReusableCell(withIdentifier: "MealPlanCell", for: indexPath) as! MealPlanCell
// Get the mealPlan that the tableView is asking about
let mealPlanInTable = filteredMealPlans[indexPath.row]
// Customize the cell
cell.displayMealPlan(mealPlanInTable)
// Return the cell
return cell
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Detect the indexPath the user selected
let indexPath = tableView.indexPathForSelectedRow
// Get the mealPlan the user selected
let mealPlanSelected = filteredMealPlans[indexPath!.row]
// Get a reference to the NextiewController
let NextVC = segue.destination as! NextViewController
// Get a reference to the currentMealPlanIndex in the NextViewController
NextVC.currentMealPlan = mealPlanSelected
}
Add a variable in your NextVC for currentMealPlan
class NextVC: UIViewController {
var currentMealPlan: MealPlan?
}
Thank you all for the comments/advice! Instead of connecting the data in the view controllers through the indexPath, I used a document ID that is consistent with the data flowing between my view controllers. This works with all of my filtering.
This is in my first ViewController:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let indexPath = tableView.indexPathForSelectedRow {
let ingredientsVC = segue.destination as! IngredientsViewController
let documentID = mealPlan[indexPath.row].docID
ingredientsVC.currentMealPlanIndex = indexPath.row
ingredientsVC.passedDocID = documentID!
}
}
And this is in my second ViewController:
// This variable references the unique Document ID
var passedDocID = ""
// This is how I use that document ID to get a reference to the appropriate data
let selectedMealPlanIndex = mealPlan.firstIndex(where: {$0.docID == passedDocID})
let currentMealPlan = mealPlan[selectedMealPlanIndex!]

How do I pass indexPathForSelectedRow into a UITabView via a UITabBarController?

I'm trying to segue from a UITableView to a specific tab within a UITabBarController. While googling, I found two sets of info that seem to indicate how to do, but neither were using a UITableView as the source.
The first source is this wonderfully written answer I found here on StackOverflow: How to make a segue to second item of tab bar?
The second source was this site: http://www.codingexplorer.com/segue-uitableviewcell-taps-swift/
I have been attempting to combine the two for my app. Below is a truncated version of my originating UIViewController (I can post the full one if needed, just most the code isn't related to this segue, I don't think):
class BonusListViewController: UITableViewController {
// MARK: - Table View Configuration
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if isFiltering() {
print("Showing \(filteredBonuses.count) Filtered Results")
return filteredBonuses.count
}
print("Found \(bonuses.count) rows in section.")
return bonuses.count
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRow(at: indexPath as IndexPath, animated: true)
let row = indexPath.row
}
private var nextViewNumber = Int()
#IBAction func secondView(_ sender: UITapGestureRecognizer) {
self.nextViewNumber = 2
self.performSegue(withIdentifier: "tabBar", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "tabBar" {
let destination = segue.destination as! TabBarViewController
switch (nextViewNumber) {
case 1:
destination.selectedIndex = 0
case 2:
destination.selectedIndex = 1
if self.isFiltering() {
destination.bonus = filteredBonuses[(tableView.indexPathForSelectedRow?.row)!]
} else {
destination.bonus = bonuses[(tableView.indexPathForSelectedRow?.row)!]
}
default:
break
}
}
}
}
My issue resolves around trying to pass the tableView.indexPathForSelectedRow?.row over to the UITabViewController. In the prepare(for segue) near the end of the above code snippet I'm getting a compile error for the destination.bonus = lines that says,
Value of type 'TabBarViewController' has no member 'bonus'
This is technically true as I'm just trying to pass through the TabBarViewController to the second tab it controls.
How can I fix the above to let me tap on a cell, and then pass the selected row over to the target UITabView?
EDIT: In case it helps, here is a picture of the storyboard.
Value of type 'TabBarViewController' has no member 'bonus'
Because 'TabBarViewController' has no property named bonus
You can subclass TabBarViewController add property bonus
and set it from segue like
guard let destination = segue.destination as? YourTabbarSubClass else {return }
and you can access bonus by destination.bonus
Now when you need that bonus from tabbar controllers you can use it with (self.tabbarController as! YourTabbarSubClass).bonus
EDIT
class TabBarViewController: UITabBarController {
// Add property bonus here
var bouns:BounsStruct?
}
Now From your view controller where you need that
class YourFirstTabVC:UIVIewController {
//where you need that
self.bouns = (self.tabbarController as! TabBarViewController).bouns
self.tableview.reloadData()
}

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

pass the data from uitableview to other uiview

I'm new in swift and IOS, i have some problem to pass the dictionary data to other uiview, anyone can help me to fix it?
LessonsTableViewController:
var mylessons = [
["title":"Posture", "subtitle":"Set up your body", "bgimage":"1", "lesimage":"l1"],
["title":"Breathing", "subtitle":"Breathing deeply", "bgimage":"2", "lesimage":"l2"],
["title":"Breathing", "subtitle":"Breathing Exercise", "bgimage":"3", "lesimage":"l3"],
["title":"Health", "subtitle":"Do’s & Don’ts", "bgimage":"4", "lesimage":"l4"]
]
and
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! LessonsTableViewCell
let lessonsObject = mylessons[indexPath.row]
cell.backgroundImageView.image = UIImage(named: lessonsObject["bgimage"]!)
cell.titleLabel.text = lessonsObject["title"]
cell.subtitleLabal.text = lessonsObject["subtitle"]
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "LessonSegue", sender: mylessons[indexPath.row])
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?){
let lessegue = segue.destination as! LessonDetailsViewController
lessegue.SelectedLessons = mylessons
}
LessonDetailsViewController:
#IBOutlet weak var LTitle: UILabel!
var SelectedLessons = [Dictionary<String, String>()]
override func viewDidLoad() {
super.viewDidLoad()
LTitle.text = SelectedLessons["title"]
// Do any additional setup after loading the view.
}
Finally, it has an error "Cannot subscript a value of type '[Dictionary]' with an index of type 'String'.
First your SelectedLessons is wrong type. You need use something like tis
var SelectedLessons:Dictionary<String, String>?
And you need past correct object.
override func prepare(for segue: UIStoryboardSegue, sender: Any?){
let lessegue = segue.destination as! LessonDetailsViewController
lessegue.SelectedLessons = sender as? Dictionary<String,String>
}
You should declare
var SelectedLessons = [String, String]()
Your current declaration is an array of dictionaries
You have a number of problems.
First is a coding style issue. Variable names should start with a lower-case letter, so SelectedLessons should be selectedLessons.
Second, you likely want to pass the user-selected lesson to the destination, not the entire array.
Your array mylessons is an array of dictionaries: (Type [[String:String]])
You should probably name the variable in LessonDetailsViewController selectedLesson (singular, starting with a lower-case letter) and make it type [String: String] (a single lesson.)
Then your prepareForSegue might look like this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?){
guard
let lessegue = segue.destination as? LessonDetailsViewController,
let selectedRow = tableView.indexPathForSelectedRow?.row else {
print("Error. No row selected. Exiting."
fatalError()
}
lessegue.selectedLesson = myLessons[selectedRow]
}
(The code above should have better error handling for the case where there's not a selected row, but it should give you the idea.)
EDIT:
By the way, it's not a good idea to write your prepare(for:) method as if you will only ever segue to a single view controller of a single type. It's very common to go back and expand an app to add additional segues, and if you do that, the code above will crash. Better to use a switch statement:
override func prepare(for segue: UIStoryboardSegue, sender: Any?){
switch segue.destination {
case let lessegue as LessonDetailsViewController:
guard
let selectedRow = tableView.indexPathForSelectedRow?.row else {
print("Error. No row selected. Exiting."
fatalError()
}
lessegue.selectedLesson = myLessons[selectedRow]
default:
print("Unrecognized segue. Exiting."
fatalError()
}
}
That syntax creates a switch statement where each case is executed based on the type of the destination view controller, with a built-in cast to the destination type. It's a neat variant of the switch statement that's very useful in prepare(for:) functions.

Segue pushing Nothing first time(delay in data sent)

Hello StackOverflow,
I'm just picking up swift and trying to implement data being passed between UITableView Cell to a UIViewController which will show a detailed view of the info shown on the tableview, and whenever I test the application on my emulator first time I press a table cell it passes an empty string and then when I try pressing another cell the viewController shows the string that was supposed to be seen earlier.I pasted the code I have for my tableview didSelectRowAtIndexPath below.
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: false)
var7 = lat[indexPath.item]
var6 = long[indexPath.item]
var5 = items[indexPath.item]
var1 = detail[indexPath.item]
var2 = date[indexPath.item]
var3 = wop[indexPath.item]
var4 = ViewController()
nextView.locationPassed = var1
//self.performSegueWithIdentifier("DetailPush", sender: self)
println("value stored in var1: \(var1)")
//println("The selected indexPath is \(indexPath.item + 1)")
println("The stored id is: \(storeSend)")
}
Here is my implementation for my push segue method
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "DetailPush"
{
if let crimesView = segue.destinationViewController as? ViewController {
crimesView.locationPassed = var1
//println("The passing address is: \(var1)")
}
}
}
Any idea on why I'm getting data delayed during the segue?
Thank you
Solution Found: I edited my prepareForSegue method with the following and it fixed my issue
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
//Adding the indexPath variable for the selected table Row within the segue
var indexPath: NSIndexPath = self.tableView.indexPathForSelectedRow()!
if segue.identifier == "DetailPush"
{
if let crimesView = segue.destinationViewController as? ViewController {
//Then just pass the data corresponding to the array I created identified by the index of the selected row
crimesView.locationPassed = self.arrayName[indexPath.row]
println("The passing address is: \(self.addressSend)")
}
}
}
I found the solution by watching some online videos and all I did to fix my issue was redefine my prepareForSegue function with:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
//Adding the indexPath variable for the selected table Row within the segue
var indexPath: NSIndexPath = self.tableView.indexPathForSelectedRow()!
if segue.identifier == "DetailPush"
{
if let crimesView = segue.destinationViewController as? ViewController {
//Then just pass the data corresponding to the array I created identified by the index of the selected row
crimesView.locationPassed = self.arrayName[indexPath.row]
println("The passing address is: \(self.addressSend)")
}
}
}
And it seems to work like a regular segue for me.......Thank you for all the suggestions given me
you said you are doing the prepareForSegue from async request
so try this:
if segue.identifier == "DetailPush"
{
dispatch_async(dispatch_get_main_queue()) {
if let crimesView = segue.destinationViewController as? ViewController {
crimesView.locationPassed = var1
//println("The passing address is: \(var1)")
}
}
}
try to remove the line
tableView.deselectRowAtIndexPath(indexPath, animated: false)
see if it still happens.
maybe move it to the end

Resources