Second segue from TableViewController creating SIGABRT error relating to the first - swift

I have created a NoteBook application within a larger app. I have all the functionality working including a segue to an Add Note page which triggers programatically from clicking a note (to edit it) or a + barButtonItem.
I need a second segue to send the user back to the home page of the app, but every way I seem to try it conflicts with the existing segue I have in place.
Can anyone suggest a way to get the second segue to work. They both have different identifiers which I am referencing in the methods. Its just the goHome segue that will not work...
class NoteBookViewController: UITableViewController, NoteViewDelegate {
func didUpdateNoteWithTitle(newTitle: String, andBody newBody: String) {
self.noteBookEntries[self.selectedIndex] ["title"] = newTitle
self.noteBookEntries[self.selectedIndex] ["body"] = newBody
self.tableView.reloadData()
saveNotesArray()
}
var noteBookEntries = [[String:String]] ()
#IBAction func newNote() {
var newNote = ["title" : "", "body" : ""]
noteBookEntries.insert(newNote, at: 0)
self.selectedIndex = 0
self.tableView.reloadData()
saveNotesArray()
performSegue(withIdentifier: "editNoteBookSegue", sender: nil)
}
var selectedIndex = -1
func saveNotesArray() {
UserDefaults.standard.set(noteBookEntries, forKey: "notes")
UserDefaults.standard.synchronize()
}
override func viewDidLoad() {
super.viewDidLoad()
if let newNote = UserDefaults.standard.array(forKey: "notes") as? [[String:String]] {
noteBookEntries = newNote
}
self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.reply, target: self, action: #selector(NoteBookViewController.navigateToNextViewController))
}
func navigateToNextViewController(){
self.performSegue(withIdentifier: "goHome", sender: self)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return noteBookEntries.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell (withIdentifier: "CELL")! as UITableViewCell
cell.textLabel?.text = noteBookEntries[indexPath.row]["title"]
return cell
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == UITableViewCellEditingStyle.delete {
noteBookEntries.remove(at: indexPath.row)
UserDefaults.standard.set(noteBookEntries, forKey: "notes")
self.tableView.reloadData()
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.selectedIndex = indexPath.row
performSegue(withIdentifier: "editNoteBookSegue", sender: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) {
let notesEditorVC = segue.destination as! NewNoteBookEntryViewController
notesEditorVC.navigationItem.title = noteBookEntries[self.selectedIndex] ["title"]
notesEditorVC.noteBodyText = noteBookEntries[self.selectedIndex] ["body"]
notesEditorVC.delegate = self
}
}
error message relating to original segue - this segue works until the second is added

In your prepareForSegue method first line is:
let notesEditorVC = segue.destination as! NewNoteBookEntryViewController
This method is called for each of your segues. When it is called for your first segue it works totally fine because the destination view controller is, in fact, of type NewNoteBookEntryViewController.
However, when this method is called for your second segue, the destination controller is of different type. So, you get a crash when you force downcast it.
You should add some logic to your prepareForSegue method so that you distinguish between segues. For example:
if segue.identifier == "addNote" {
let notesEditorVC = segue.destination as! NewNoteBookEntryViewController
//some other code
}

Solved by adding the logic and then adding this to the destination controller:
override func viewWillAppear(animated: Bool) {
self.navigationController?.setNavigationBarHidden(true, animated: true)
}

Related

Is there any better way to send information to child view?

I have a view controller which has a child view. I want to convey information from the view to the child view. To do so, I did this :
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "visualize", sender: self)
invoiceNumber = indexPath.row
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let vc = segue.destination as! PreviewViewController
vc.invoiceNumber = invoiceNumber
}
But the problem is that the value of invoiceNumber is not updated on the first iteration but on the second. I tried to see what the problem was and found that "invoiceNumber = indexPath.row" runs after "vc.invoiceNumber = invoiceNumber". Please help ! Thanks
Here is the code of printing :
import UIKit
class ViewController: UIViewController {
var invoiceNumber: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print(invoiceNumber)
}
}
Sorry I have mistaken :
var customerData: [[customerInformation]] = []
var itemsData: [[Item]] = []
var totalData: [TotalInformation] = []
var invoiceNumber = 0
override func viewDidLoad() {
super.viewDidLoad()
tableView.reloadData()
}
override func viewDidAppear(_ animated: Bool) {
let tabBar = tabBarController as! baseTabBarController
customerData = tabBar.customerData
itemsData = tabBar.itemsData
totalData = tabBar.totalData
tableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return customerData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let invoiceCell = tableView.dequeueReusableCell(withIdentifier: "invoice", for: indexPath) as! invoiceTableViewCell
invoiceCell.textLabel?.text = (customerData[indexPath.row][0]).input
invoiceCell.detailTextLabel?.text = "Invoice n°" + String(indexPath.row)
invoiceCell.totalLabel.text = (totalData[indexPath.row]).total
return invoiceCell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let tabBar = tabBarController as! baseTabBarController
tabBar.customerData.remove(at: indexPath.row)
//itemData.remove(at: indexPath.row)
tabBar.totalData.remove(at: indexPath.row)
customerData = tabBar.customerData
itemsData = tabBar.itemsData
totalData = tabBar.totalData
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
invoiceNumber = indexPath.row
print(invoiceNumber, "yes")
performSegue(withIdentifier: "visualize", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let vc = segue.destination as! PreviewViewController
vc.invoiceNumber = invoiceNumber
}
}
It is the "vc.invoiceNumber = invoiceNumber" which comes before the "invoiceNumber = indexPath.row" as I tried to change the value of invoiceNumber a variable in the view and print out the value in the child view and if showed its value first (I tested -1).
I added the Preview view controller which receives the invoiceNumber :
import UIKit
import WebKit
class PreviewViewController: UIViewController {
#IBOutlet var webPreview: UIWebView!
var invoiceComposer: InvoiceComposer!
var HTMLContent: String!
var invoiceNumber: Int = -1
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print(invoiceNumber)
createInvoiceAsHTML()
}
func createInvoiceAsHTML() {
invoiceComposer = InvoiceComposer()
if let tabBar = tabBarController as? baseTabBarController {
if let invoiceHTML = invoiceComposer.renderInvoice(invoiceNumber: String(invoiceNumber), invoiceDate: "", recipientInfo: tabBar.customerData[invoiceNumber][0].input, items: tabBar.itemsData[invoiceNumber], totalAmount: tabBar.totalData[invoiceNumber].total) {
webPreview.loadHTMLString(invoiceHTML, baseURL: NSURL(string: invoiceComposer.pathToInvoiceHTMLTemplate!)! as URL)
HTMLContent = invoiceHTML
}
}
else {
print("tabBarController is not of type baseTabBarController or either nil ")
}
}
}
Change the order of execution, like this:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
invoiceNumber = indexPath.row
performSegue(withIdentifier: "visualize", sender: self)
}
I should have connected the segue from the view controller to the child view and not from the cell to the child view.

Nothing Happens When UITableView is Clicked (trying to send data between views)

So I have a two page app. The purpose of the app being the user can store expenses. They log a name and amount (attributes) and this data is stored in Expenses (entity). I have figured out how to create core data values, delete and retrieve. I am now working on updating. This will work by the user tapping on a table in the first view (ExpensesViewController) where the expenses are stored and this takes them to the 2nd view (EditExpensesViewController) where they can update the value back into core data. I am stuck on this 'data transfer' between the views.
I am using the storyboard and connected the first view to the second via 'show' set the segue identifier as 'editExpense'. However nothing happens when the table row is tapped. Any idea why it's not working and what I may have missed out? See here for GIF
ExpensesViewController
import UIKit
import CoreData
class ExpensesViewController: UIViewController {
#IBOutlet weak var totalLabel: UILabel!
#IBOutlet weak var tableView: UITableView!
var expenses_array = [Expenses]()
var send_array = [Expenses]()
override func viewDidLoad(){
super.viewDidLoad()
retrieveExpenses()
}
func retrieveExpenses(){
let fetchRequest: NSFetchRequest<Expenses> = Expenses.fetchRequest()
do {
let expenses = try PersistenceService.context.fetch(fetchRequest)
self.expenses_array = expenses
self.tableView.reloadData()
} catch {
print(error.localizedDescription )
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "editExpense") {
let secondViewController = segue.destination as! EditExpensesViewController
secondViewController.send_array = send_array
}
}
}
extension ExpensesViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return expenses_array.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
cell.textLabel?.text = expenses_array[indexPath.row].name
cell.detailTextLabel?.text = expenses_array[indexPath.row].amount
return cell
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if (editingStyle == .delete) {
let fetchRequest: NSFetchRequest<Expenses> = Expenses.fetchRequest()
do {
let result = try PersistenceService.context.fetch(fetchRequest)
// Delete from Core Data and remove from the arrays then save
if result.contains(expenses_array[indexPath.row]){
PersistenceService.context.delete(expenses_array[indexPath.row])
expenses_array = expenses_array.filter { $0 != expenses_array[indexPath.row] }
PersistenceService.saveContext()
self.getTotalExpenses()
self.tableView.reloadData()
}
} catch {
print(error.localizedDescription )
}
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
send_array = [self.expenses_array[indexPath.row]]
self.performSegue(withIdentifier: "editExpense", sender: self)
}
}
EditExpensesViewController
import UIKit
import CoreData
class EditExpensesViewController: UIViewController {
var send_array = [Expenses]() // Defined from the previous view controller
override func viewDidLoad() {
super.viewDidLoad()
print(send_array)
}
}
First of all conform to tableView delegates and dataSource in your viewDidLoad() :
tableView.delegate = self
tableView.dataSource = self
Delete segue from stroyboard and we will present the controller in code using :
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "editExpense") {
let secondViewController = segue.destination as! EditExpensesViewController
secondViewController.send_array = send_array
// "someIdentifier" is the identifier of secondController in storyboard
storyboard?.instantiateViewController(withIdentifier: "someIdentifier")
present(secondViewController, animated: true, completion: nil)
}
}
Be aware to put storyboard identifier for second controller in storyboard using attribute inspector
The problem is that your first view controller is the UITableViewDataSource only. That is not enough. It needs to be the UITableViewDelegate too. didSelectRowAt Is a delegate method, not a data source method, and will not be called unless this view controller is the table views delegate and is explicitly declared as conforming to UITableViewDelegate.

Get cell index from tableviewcell

Hey I have this tableview controller that list restaurants from my database. what I want to do is if the cell/row is selected it opens a more detail page about the restaurant. for some reason I can't retrieve the index or its not going to the func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) to give me index here is what I have:
class HomeTableViewController: UITableViewController{
var restaurantArray = [Restaurant]()
var resTransfer: Restaurant?
var resName: String?
var dataBaseRef: FIRDatabaseReference! {
return FIRDatabase.database().reference()
}
override func viewDidLoad() {
super.viewDidLoad()
title = "Home"
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Main Menu", style: .plain, target: self, action: #selector(SSASideMenu.presentLeftMenuViewController))
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
fetchRestaurants()
}
func fetchRestaurants(){
FIRDatabase.database().reference().child("AthensRestaurants/Restaurants").observe(.value, with: { (snapshot) in
var results = [Restaurant]()
for res in snapshot.children{
let res = Restaurant(snapshot: res as! FIRDataSnapshot)
results.append(res)
}
self.restaurantArray = results.sorted(by: { (u1, u2) -> Bool in
u1.name < u2.name
})
self.tableView.reloadData()
}) { (error) in
print(error.localizedDescription)
}
}
// MARK: - Table view data source
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return restaurantArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "restaurantsCell", for: indexPath) as! RestaurantsTableViewCell
// Configure the cell...
cell.configureCell(res: restaurantArray[indexPath.row])
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
print("Row \(indexPath.row)selected")
resTransfer = restaurantArray[indexPath.row]
resName = restaurantArray[indexPath.row].name
print(resName as Any)
performSegue(withIdentifier: "RestaurantDetailView", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if(segue.identifier == "RestaurantDetailView") {
let vc = segue.destination as! RestaurantDetailViewController
vc.resNam = restaurantArray.first?.name //instead of first should be index of cell!
print(vc.resNam! as String)
}
}
}
I am doing it like this, without segue:
func showRestaurantViewControllerWith(_ id: String) {
let storyBoard = UIStoryboard(name: "RestaurantCard", bundle: nil)
let destinationVC = storyBoard.instantiateViewController(withIdentifier: "RestaurantCard") as! RestaurantCardViewController
destinationVC.restaurant.id = id
self.navigationController?.pushViewController(destinationVC, animated: true)
}
and
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.showRestaurantViewControllerWith(self.allRestaurants[indexPath.row].id)
}
You have to use this function to fetch the index
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
print(indexPath.row)
}
It will give you the index.
First of all your code is obviously Swift 3 so the signature of didSelectRowAt is wrong.
Another solution is to pass the indexpath as sender
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("Row \(indexPath.row)selected")
performSegue(withIdentifier: "RestaurantDetailView", sender: indexPath)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "RestaurantDetailView" {
let indexPath = sender as! IndexPath
let restaurant = restaurantArray[indexPath.row]
let resName = restaurant.name
print(resName!)
let vc = segue.destination as! RestaurantDetailViewController
vc.resNam = resName
}
}
or still easier to connect the segue to the table view cell. Then you can delete didSelectRow... because prepare(for is called directly and the cell is passed as the sender parameter.
PS: Why is the property name optional? In real life have you ever heard about a restaurant without a name?

fatal error: unexpectedly found nil while unwrapping an Optional value: Swift, Core Data

I am getting error on the line:
let indexPath = self.menuTable.indexPathForSelectedRow()!.
Seems that I am not getting a value from indexPathForSelectedRow. I am parsing from a CSV file into Core Data. Not sure if it matters. I am new to coding, so not sure if I am missing something obvious.
import UIKit
import CoreData
class MenuTableViewController: UITableViewController {
#IBOutlet var menuTable: UITableView!
private var menuItems:[MenuItem] = []
var fetchResultController:NSFetchedResultsController!
override func viewDidLoad() {
super.viewDidLoad()
// Load menu items from database
if let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext {
let fetchRequest = NSFetchRequest(entityName: "MenuItem")
var e: NSError?
menuItems = managedObjectContext.executeFetchRequest(fetchRequest, error: &e) as! [MenuItem]
if e != nil {
println("Failed to retrieve record: \(e!.localizedDescription)")
}
}
// Make the cell self size
self.tableView.estimatedRowHeight = 66.0
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.layoutIfNeeded()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// Return the number of sections.
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Return the number of rows in the section.
return menuItems.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = menuTable.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! MenuTableViewCell
// Configure the cell...
cell.nameLabel.text = menuItems[indexPath.row].name
cell.detailLabel.text = menuItems[indexPath.row].detail
// cell.priceLabel.text = "$\(menuItems[indexPath.row].price as! Double)"
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
self.performSegueWithIdentifier("showFront", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
{
if (segue.identifier == "showFront")
{
var upcoming: CardFrontViewController = segue.destinationViewController as! CardFrontViewController
let indexPath = self.menuTable.indexPathForSelectedRow()!
let titleString = menuItems[indexPath.row].name
upcoming.titleStringViaSegue = titleString
self.menuTable.deselectRowAtIndexPath(indexPath, animated: true)
}
}
}
Since you have an implementation of tableView:didSelectRowAtIndexPath: and the cell is connected to the segue in the storyboard, the segue is happening twice. The second time the segue is performed there would be no selection because you deselect it during the first segue. You can fix this issue by deleting your implementation of tableView:didSelectRowAtIndexPath: or by creating the segue in the storyboard with the view controller itself as the source instead of the cell and leaving your manual invocation of the segue.
I don't know if this is the problem but why are u using self as sender if u need the indexPath?
Try:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
self.performSegueWithIdentifier("showFront", sender: indexPath)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
{
if (segue.identifier == "showFront")
{
var upcoming: CardFrontViewController = segue.destinationViewController as! CardFrontViewController
let titleString = menuItems[indexPath.row].name
upcoming.titleStringViaSegue = titleString
self.menuTable.deselectRowAtIndexPath(indexPath, animated: true)
}
}
I see you are using a UITableViewController. In a UITableViewController a UITableView is automatically created for you with the needed outlets. You can access it in code via self.tableView. My guess is that you do not connected the IBOutlet for your UITableView called menuTable. So the optional which is nil while unwrapping is not the indexPath but the UITableView.
Fix:
Delete your IBOutlet and everywhere you use the menuTable variable and use self.tableView instead.

How to make a segue by a tableViewDelegate

I'd like to create a segue from a tableViewDelegate cell to an other view. This is my code:
import UIKit
class CoriViewController: UIViewController, UITableViewDelegate, UITableViewDataSource
{
#IBOutlet var tableView: UITableView!
var elencoCori : SquadraModel! //This var contains label and array
override func viewDidLoad()
{
super.viewDidLoad()
self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
InterfaceManager.sharedInstance.putBlurBackgroundToTableView(self.view, tableView: self.tableView)
self.tableView.tableFooterView = UIView(frame:CGRectZero)
self.tableView.backgroundColor = UIColor.clearColor()
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return self.elencoCori.cori.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell : UITableViewCell = self.tableView.dequeueReusableCellWithIdentifier("cell") as! UITableViewCell
let coro = elencoCori.cori[indexPath.row]
cell.textLabel?.text = coro.marker
cell.backgroundColor = UIColor.clearColor()
cell.accessoryType = UITableViewCellAccessoryType.DisclosureIndicator
return cell
}
func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath)
{
self.performSegueWithIdentifier("toDettaglioController", sender: self)
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
}
Now the problem: how I could set a prepareForSegue that send to the secondView? In other circumstances I would have solved the problem like this:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
{
if segue.identifier == "toDettaglioController"
{
if let indexPath = tableView.indexPathForSelectedRow()
{
let controller = segue.destinationViewController as! Dettaglio controller
controller.coro = elencoCori.cori[indexPath.row]
}
}
}
but I know the this doesn't work and I have to use a delegate method. So can everyone help me with a step-by-step guide?
On your didSelectRowAtIndexPath method call performSegueWithIdentifier and pass the indexPath as your sender.
Then in prepareForSegue you can do if let indexPath = sender as NSIndexPath and setup your controller.
Suppose you have One UITableViewController-T and Two UIViewControllers-A and B.
Now suppose you want to go from T to A when cell 0 is clicked, and go from T to B when cell 1 is clicked.
Step 1 : Connect segue from T to A and name that segue "segueToA"
Step 2 : Connect segue from T to B and name that segue "segueToB"
Step 3 : Got to didSelectRowAtIndexPath in T and put if-else
if(indexPath.row == 0){
self.performSegueWithIdentifier("segueToA", sender: nil)
}else{
self. performSegueWithIdentifier("segueToB", sender: nil)
}
That's it.
Update: Here is a Sample Project.