TableView Pushing to Detail View in Swift - iphone

I need help with pushing my tableView to a detailView. Right now I have most of the code, but right when my app loads, it goes to the detailedView, and whenever I try to go back to the tableView, it instantly goes back to the detailedView and the following error shows up in the debug area:
Finishing up a navigation transition in an unexpected state. Navigation Bar subview tree might get corrupted.
I don't understand why this is happening since my code is not in the viewDidLoad section of the code... Here is my code for the tableView:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Label.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "Cell")
cell.textLabel.text = Label[indexPath.row]
performSegueWithIdentifier ("showDetail", sender: self)
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "showDetail" {
}
}
return cell
}
Please leave an answer below if you can help me make my segue work how it is supposed to. Thanks!
P.S. I am also curious on how to customize the back button on a navigation bar from "< Back" to just "<".
P.S.S. How can I make the tableViewCell auto release after pressed so it doesn't stay highlighted? Just so it looks like a nicer app overall.
P.S.S.S Sorry, but one last thing is how can I customize the DeatilView based on what cell the user clicks on? I already have the new class set up with text, but not sure how to make the if statement that customizes the text for each cell clicked.
Thanks again!

The error is in your cellForRowAtIndexPath:
performSegueWithIdentifier ("showDetail", sender: self)
this triggers the segue when the cell is displayed, not when it's selected. The easiest way to fix that is to create a segue in IB from the cell to the destination view controller.
Also, this code:
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "showDetail" {
}
}
creates a function local to cellForRowAtIndexPath which is never executed. The correct way is to declare it in the class scope as an override of the superclass implementation.
If you have already defined a segue in IB, then your code should like:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "Cell")
cell.textLabel.text = Label[indexPath.row]
return cell
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDetail" {
}
}

you have to perform the segue in tableView(_:didSelectRowAtIndexPath:) not
tableView(_:cellForRowAtIndexPath:)

P.S. How to customize the back button on a navigation bar from "< Back" to just "<".
I found this old question has the answer in Objective C, at How to change text on a back button
You must set this in the View Controller you are segueing from.
Let's say we have a simple iPhone app, that has a UITableViewController with a bunch of cells listing off numbers, let's call it the "NumberListTVC". We embed this NumberListTVC in a NavigationController. We set a selection segue on one of the cells to another ViewController, which we'll call the "NumberDisplayerVC". This NumberDisplayerVC has one label, which we will set to the contents of the label that was clicked on the NumberList. So when you select a cell in NumberListTVC, it will push to the "NumberDisplayer" in the NavigationController.
So for this app, you would put the code to customize the back button in the NumberListTVC, so that when you are looking at the NumberDisplayerVC, it will show the back button we want it to, instead of the default (since the back button brings it back to the NumberListTVC).
In Objective-C, as per the linked answer, the code to change "< Back" to "<" would be:
self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:#"" style:UIBarButtonItemStylePlain target:nil action:nil];
Basically, we create a new UIBarButtonItem with whatever title we want. In this case, I just gave it the title of "", which is just an empty String.
The same command in Swift would be:
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style:.Plain, target: nil, action: nil)
I put this in NumberListTVC's viewDidLoad method, to make sure it was ran when NumberListTVC loaded.
P.S.S. How can I make the tableViewCell auto release after pressed so it doesn't stay highlighted?
This is covered in Objective-C on the answer Selected UItableViewCell staying blue when selected
You just have to tell the UITableViewCell to deselect it in its didSelectRowAtIndexPath.
In Objective-C, you would override it and do this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//Do whatever else you want to do here...
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
The Swift code would be similar:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
//Do whatever else you want to do here...
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
P.S.S.S How can I customize the DetailView based on what cell the user clicks on?
For simplicity's sake, I am going to use my first example which is just using our navigationController with the NumberListTVC and NumberDisplayerVC, so not on a UISplitView, but the question asked affects either scenario.
Basically, you have to get what you want from the cell that was clicked on, and then set it to whatever you want to use it in the next ViewController. So, in your prepareForSegue, you would do something like this:
if segue.identifier == "amazingSegue"
{
if let cellText = (sender as? UITableViewCell)?.textLabel.text
{
if let someVC = segue.destinationViewController as? NumberDisplayerVC
{
someVC.textToDisplay = cellText
}
}
}
This is basically what Antonio said in his answer. I added a bit more to it. So in my case, the NumberListTVC is a bunch of dynamically created UITableViewCells. I want to grab the label out of each one and then use that in the NumberDisplayerVC. So we:
Check if it is the segue we want to actually work with, in this case the segue between NumberListTVC and NumberDisplayerVC is named "amazing segue".
Next, use optional binding to assign the text of the sender cell to the constant cellText. We use the optional type cast operator to check if the sender is a UITableViewCell. We then use optional chaining to look inside this UITableViewCell and extract the text from its textLabel.
Again use Optional Binding to assign our destinationViewController to the constant someVC. We optionally type cast it to a NumberDisplayerVC, which is what it should be. If it indeed is one, it will correctly bind to the someVC variable.
Then just set the textToDisplay property to the cellText constant we optionally bound earlier.
I'm not sure if this is the best way, I don't feel great about the tower of if let statements, but it does the job initially.

Related

fatal error: Array index out of range with many tap Swift

When I tap only one time on one of cell in table view, it works perfect to another view controller. But if I tap twice, triple or more times on the cell quickly before move to another view controller.
An error of array index out of range would be shown and highlighted "let labelTitle = self.resultsTitleArray[indexPath.row]". I tried to use self.view.userInteractionEnabled = false before performSegueWithIdenitifier and self.view.userInteractionEnabled = true after performSegueWithIdenitifier. But still doesn't work.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "moreDetail"){
let viewController = segue.destinationViewController as! ProductDetailViewController
let indexPath = self.resultsTable.indexPathForSelectedRow!
let labelTitle = self.resultsTitleArray[indexPath.row]
viewController.labelTitleText = labelTitle
self.resultsTable.deselectRowAtIndexPath(indexPath, animated: true)
}
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.performSegueWithIdentifier("moreDetail", sender: self)
}
you need to check if indexpath is nil
if(indexPath == nil){
return
}
and to be 100% sure it works you can also do
if(indexPath == nil || indexPath.row >= self.resultsTitleArray.length){
return
}
You can disable the multiple touches from the TableView so that
those events will not be fired by setting the multipleTouchEnabled to false.
self.tableView.multipleTouchEnabled = false
Can you try removing didSelectRowAtIndexPath and going to your storyboard and Control-dragging from the cell to the segue view controller. Name that segue "moreDetail." See if the segue fires immediately before you have a chance to double- or triple-click the button.

Using 3D Touch with storyboard's peek and pop

I found very easy way to use 3D Touch — check "Peek & Pop" in storyboard. But I'm struggling with one problem.
I have UITableView, when user touches cell all is working ok with my code:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDetail" {
print(tableView.indexPathForSelectedRow)
}
}
So I'm filling data in my Detailed controller based on selected row. But when I'm pressing with Peek or Pop method tableView.indexPathForSelectedRow always returning nil (hm.. I haven't selected row, I'm just previewing so indexPath is nil I guess). How can I get that "peeked" cell indexPath to pass it to segue?
Or storyboard's Peek & Pop not working in this simple way and I need to fully implement peek & pop in my code?
It is possible to fully implement it using the storyboard and prepareForSegue. You can do this by making use of the sender object.
By assuming that you have created your segue directly from the table cell in the storyboard to the next view controller, then the sender object will be of the type UITableViewCell. If you trigger the segue programmatically, then just remember to set the sender object in the method call.
You can use this cell to get a hold of the NSIndexPath from the tableView, something similar to the following (ignore all the force unwrapping, this is just for demonstration purposes):
override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) {
let cell = sender as! UITableViewCell
let indexPath = tableView.indexPath(for: cell)!
if segue.identifier == "mySegue" {
let destination = segue.destination as! DetailsViewController
destination.model = self.model[indexPath.row]
}
}

How would I unhide a button if user performs segue in swift?

I have a button inside a cell (PFQueryTableViewController) that is hidden and I want to unhide it when the user performs a certain segue that I call programatically.
When the user taps the cell it segues to a view controller which displays the contents of the cell full screen... I want the button to unhide in this cell when the segue is called so when the user goes back to the table of cells they can see it on the cell they just tapped.
How can I do this?
Edit after questions:
inside cellRowForIndexPath I have the following for the button
cell.myButton.tag = indexPath.row
cell.myButton.addTarget(self, action: "pressed:", forControlEvents: UIControlEvents.TouchUpInside)
cell.myButton.hidden = true
And the segue itself carries information from the cell (stored in Parse backend) across to FullPostViewController from AllPostsTableViewController. The code for that is this (would I call the unhide in here somewhere?):
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.performSegueWithIdentifier("showFullPost", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showFullPost" {
let indexPath = self.tableView.indexPathForSelectedRow
let fullPostVC = segue.destinationViewController as! FullPostViewController
let object = self.objectAtIndexPath(indexPath)
fullPostVC.post = object?.objectForKey("postContent") as? String
let likeCount = object!.objectForKey("likedBy")!.count
fullPostVC.likesCounted = String(likeCount)
self.tableView.deselectRowAtIndexPath(indexPath!, animated: true)
}
}
(Answer thoroughly edited after thorough edit of question)
One possible solution follows below.
Since you mention table cells (each containing a button; I'll assume UIButton), I assume you populate your table view cells with UITableViewCell objects; some fancy subclass to the latter. In this class:
If you haven't already, create an #IBOutlet from your button as a property in this class.
Overload the method setSelected(...) to un-hide your button in case the UITableViewCell is selected (which will precede the segue)
Hence, in your UITableViewCell subclass, you should be able to do something along the lines:
// ...TableViewCell.swift
Import UIKit
// ...
class ...TableViewCell: UITableViewCell {
// Properties
#IBOutlet weak var button: UIButton!
// button contained in UITableViewCell
// ...
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// If table cell is selected (after which segue will follow),
// un-hide button.
if (selected) {
button.hidden = false
}
}
}
Hopefully this will achieve your goal.

Master-Detail: UINavigatorController vs Storyboard Segue

Scenario: Master(TableView) --> Detail.
Modus Operandi: Select Row --> display DetailVC
As you can see below, I have a MasterVC embedded in a UINavigationController:
I currently display the DetailVC via pushing it into the UINavigationController's VC stack:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
println("tableView didSelectRowAtIndexPath")
let storyboard = UIStoryboard(name: "Bliss", bundle: nil);
let controller = storyboard.instantiateViewControllerWithIdentifier("DiaryPlayerVC") as DiaryPlayerViewController
self.navigationController?.pushViewController(controller, animated: true)
}
This works fine.
However, the 'prepareForSeque' doesn't fire:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDiaryPlayer" {
if let indexPath = self.tableView.indexPathForSelectedRow() {
let object = objects[indexPath.row] as NSDate
// (segue.destinationViewController as DiaryPlayerViewController).detailItem = object
}
}
}
I understand that I probably have two (2) conflicting paradigms here:
1) Using the UINavigationController vs
2) Using the Storyboard Relationship.
So...
Option 1: it appears that I can remove the Segue link to have a storyboard stand-alone DetailVC.
Option 2: via Segue, I'm assuming I can remove the UINavigatorController from the link.
I'm currently using Option #1, launching the DetailVC via the UINavigationController.
Question: If I choose Option #2, how do I access (launch) the DetailVC ("Diary Player") from the Master's Row and hence, fire the Segue's 'prepareForSegue()'?
Answer: create a segue from the table view cell to the detail view controller.
Your screenshot shows that you already created a segue in your storyboard. Give that segue an identifier in its property inspectory. Then you can simply perform the segue in the didSelectRowAtIndexPath method:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
println("tableView didSelectRowAtIndexPath")
performSegueWithIdentifier("mySegueIdentifier", sender: nil)
}
Note: Ctrl-drag the segue from the TableViewController icon, not from the TableViewCell.

Swift: Segue Error when calling

I have a segue setup in my swift class called ViewController and I am calling from a tableView didSelectRowAtIndexPath. I am using this code in my segue
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
var svc = segue!.destinationViewController as Homework;
svc.subject = subject_name
}
To tell it to set a varible called subject which is declared like this var subject:NSString! to a varible called subject_name. I then call it from my tableView didSelectRowAtIndexPath using this code prepareForSegue(UIStoryboardSegue(), sender: AnyObject?()).
This is my didSelectRowAtIndex
func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
println("You selected cell #\(indexPath.row)!")
let indexPath = tableView.indexPathForSelectedRow();
var currentCell = tableView.cellForRowAtIndexPath(indexPath) as UITableViewCell
println(currentCell.textLabel!.text)
subject_name = currentCell.textLabel.text
// // Showing new storyboard
performSegueWithIdentifier("Homework", sender: self)
let vc : AnyObject! = self.storyboard.instantiateViewControllerWithIdentifier("Homework")
self.showViewController(vc as UIViewController, sender: vc)
}
But when I go and run the app, and click the table view cell I get this error "fatal error: unexpectedly found nil while unwrapping an Optional value", and a green arrow points towards var svc = segue!.destinationViewController as Homework;. I tested the exact same code out on another app expect with the segue being called when a button is clicked and that worked perfectly, I also tried answers from Calling segue programatically not working, and Preparing for segue in embedded tableView in Swift. Both of these answers did not work.
If I understood it correctly, you are calling prepareForSegue(). You shouldn't, that is automatically invoked.
What you should do instead is calling performSegueWithIdentifier(identifier: String, sender: AnyObject?). That triggers a segue invocation, which automatically executes prepareForSegue().
The identifier parameter is the one you set from IB: select the segue and look at the attributes inspector.
Besides that, #AnthonyKong's answer is a safer way to deal with optionals (a segue in this case) - that ensures that no runtime exception is thrown.
Addendum
Looking at your updated question, specifically at the implementation of didSelectRowAtIndexPath. The last 2 lines:
let vc : AnyObject! = self.storyboard.instantiateViewControllerWithIdentifier("Homework")
self.showViewController(vc as UIViewController, sender: vc)
are redundant - if you perform a segue, that will instantiate the destination view controller, so you don't have to do it manually. Remove those lines.
You should do this instead:
if let svc = segue!.destinationViewController as? Homework {
svc.subject = subject_name
}
It is because you might get passed other segues which do not have Homework as desination VC