Passing data thru a Swift segue works every other time - swift

I have a tableview which reads and RSS feed of episodic radio shows. I want the playlist for the selected show to pass to a second controller for viewing in a textview when a cell is selected. I am using a segue and it works when I select the same cell twice (every other time). I have searched everywhere without success and its driving me nuts! Please help. Here is my code
// Only grab the data at the selected cell
//
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
// Load the variable to hold whats in the row
currentList = feeds.objectAtIndex(indexPath.row).objectForKey("itunes:summary") as NSString
// Load the row number
myRow = (indexPath.row)
}
// Pass the data thru the segue
override func prepareForSegue(segue: (UIStoryboardSegue!), sender: AnyObject!) {
if (segue.identifier == "mySegue") {
// var vc = segue.destinationViewController as secondViewController
// vc.toPass = currentList
// println(vc.toPass)
let vc = segue.destinationViewController as secondViewController
let indexPath = self.tableView.indexPathForSelectedRow
vc.toPass = currentList
}
}
}
Here is the code from my second view controller
import UIKit
class secondViewController: UIViewController {
// Create a property to accept the data
#IBOutlet weak var textPlayList: UITextView!
// Create a variable to store the data
var toPass:String!
override func viewDidLoad() {
super.viewDidLoad()
textPlayList.text = toPass
textPlayList.textColor = UIColor .whiteColor()
textPlayList.font = UIFont .boldSystemFontOfSize(10)
}
}

The problem is that prepareForSegue happens before didSelectRowAtIndexPath so your currentList variable is set up too late for be useful in prepareForSegue.
To fix this, move this code:
// Load the variable to hold whats in the row
currentList = feeds.objectAtIndex(indexPath.row).objectForKey("itunes:summary") as NSString
to prepareForSegue:
override func prepareForSegue(segue: (UIStoryboardSegue!), sender: AnyObject!) {
if (segue.identifier == "mySegue") {
let vc = segue.destinationViewController as secondViewController
if let indexPath = self.tableView.indexPathForSelectedRow() {
let currentList = feeds.objectAtIndex(indexPath.row).objectForKey("itunes:summary") as NSString
vc.toPass = currentList
}
}
}
In general, you don't need didSelectRowAtIndexPath if you are using segues because prepareForSegue is where you set up the transition.

Related

De-initialzing a ViewController after dismissal?

I have two viewControllers in my App, the code for the first viewController is as illustrated below:
import UIKit
class firstViewController: UIViewController {
// The below two variables will be passed from the firstViewController to the secondViewController then back again from the secondViewController to the firstViewController:
var selectedRowValue: Int = 0
var selectedSectionValue: Int = 0
let main = UIStoryboard(name: "Main", bundle: nil)
lazy var secondViewController = main.instantiateViewController(withIdentifier: "secondViewController")
override func viewDidLoad() {
super.viewDidLoad()
}
// The below function will be triggered when the user tap on a specific tableView cell detailClosure icon. This is when the needed data get sent from this viewController to the secondViewController:
func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
let secondViewControllerProperties = secondViewController as! secondViewController
secondViewControllerProperties.receivedSelectedSectionValueFromFirstVc = indexPath.section
secondViewControllerProperties.receivedSelectedRowValueFromFirstVc = indexPath.row
// The below is the relevant content of a UILabel inside the tapped tableView cell by the user that get send to the secondViewController for it to be displayed as its NavigationBar title:
secondViewControllerProperties.selectedUniversalBeamSectionDesignation = arrayWithAllDataRelatedToUbsSections.filter({ $0.sectionSerialNumber == "\(arrayWithAllSectionsSerialNumbers[indexPath.section])" }).map({ $0.fullSectionDesignation })[indexPath.row]
self.present(secondViewControllerProperties, animated: true, completion: nil)
}
}
// The below extension inside the firstViewController is used to pass data back from the secondViewController to the firstViewController:
extension firstViewController: ProtocolToPassDataBackwardsFromSecondVcToFirstVc {
func dataToBePassedUsingProtocol(passedSelectedTableSectionNumberFromPreviousVc: Int, passedSelectedTableRowNumberFromPreviousVc: Int) {
self.selectedRowValue = passedSelectedTableRowNumberFromPreviousVc
self. selectedSectionValue = passedSelectedTableSectionNumberFromPreviousVc
}
}
Below is the code inside the second view controller:
import UIKit
class secondViewController: UIViewController {
weak var delegate: ProtocolToPassDataBackwardsFromSecondVcToFirstVc?
// The below variables get their values when the data get passed from the firstViewController to the secondViewController:
var receivedSelectedRowValueFromFirstVc: Int = 0
var receivedSelectedSectionValueFromFirstVc: Int = 0
var selectedUniversalBeamSectionDesignation: String = ""
// Inside the below navigationBar declaration, its labelTitleText will depend on the tapped tableViewCell by the user inside the firstViewController:
lazy var navigationBar = CustomUINavigationBar(navBarLeftButtonTarget: self, navBarLeftButtonSelector: #selector(navigationBarLeftButtonPressed(sender:)), labelTitleText: "UB \(selectedUniversalBeamSectionDesignation)", navBarDelegate: self)
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(navigationBar)
}
// The below gets triggered when the user hit the back button inside the navigationBar of the secondViewController. This is where using the Protocol data get passed back to the firstViewController:
extension secondViewController: UINavigationBarDelegate {
#objc func navigationBarLeftButtonPressed(sender : UIButton) {
if delegate != nil {
delegate?.dataToBePassedUsingProtocol(passedSelectedTableSectionNumberFromPreviousVc: self.selectedTableSectionNumberFromPreviousViewController, passedSelectedTableRowNumberFromPreviousVc: self.selectedTableRowNumberFromPreviousViewController)
}
dismiss(animated: true) {}
}
}
However, what I am noticing is whenever the secondViewController gets dismissed when the user hit on the back button inside the navigationBar of the secondViewController. The secondViewController does not get de-initialized, and therefore, whenever I press on a different cell inside the tableView inside the firstViewController, the navigationBar title that gets displayed inside the secondViewController is still the same as the one displayed when I pressed the first time. Since the secondViewController did not get de-initialzied and thus, I am seeing the same values as the first time it got initialized.
My question is how to de-initialize the secondViewController when it gets dismissed, so that every time I tap on a different cell inside the tableView inside the firstViewController a new secondViewController gets initialized?
Your code generates secondViewController once and reuses it (it's a property).
lazy var secondViewController = main.instantiateViewController(withIdentifier: "secondViewController")
It means it will live until the first view controller is destroyed, and of course - will be reused.
Instead, you should create it as needed.
func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
// Create the second view controller
let secondViewController = main.instantiateViewController(withIdentifier: "secondViewController")
let secondViewControllerProperties = secondViewController as! secondViewController
secondViewControllerProperties.receivedSelectedSectionValueFromFirstVc = indexPath.section
secondViewControllerProperties.receivedSelectedRowValueFromFirstVc = indexPath.row
// The below is the relevant content of a UILabel inside the tapped tableView cell by the user that get send to the secondViewController for it to be displayed as its NavigationBar title:
secondViewControllerProperties.selectedUniversalBeamSectionDesignation = arrayWithAllDataRelatedToUbsSections.filter({ $0.sectionSerialNumber == "\(arrayWithAllSectionsSerialNumbers[indexPath.section])" }).map({ $0.fullSectionDesignation })[indexPath.row]
self.present(secondViewControllerProperties, animated: true, completion: nil)
}
}
Remove the lazy var of course, it is no longer needed.
Also, you could just do:
let secondViewController = main.instantiateViewController(withIdentifier: "secondViewController") as! SecondViewController instead of casting it later, it's a bit cleaner.

Pass data between view controllers in navigation controller

So I have a Navigation Controller together with 2 view controllers. The first view controller shows a text label with some text in it, a text field to type in a list of ingredients for food and a button that should bring us to our second view controller that will make a list out of the input in the text field. The following code is for the first view controller:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var foodImage: UIImageView!
#IBOutlet weak var searchField: UITextField!
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "goSegue"){
if (searchField.text != ""){
let listOfFood = segue.destination as! ListOfFoodViewController
listOfFood.commaString = searchField.text!
}
else if (searchField.text == ""){
let alertEmpty = UIAlertController(title: "Emptyness", message: "There is no input!", preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "Understood", style: .default, handler: nil)
alertEmpty.addAction(defaultAction)
self.present(alertEmpty, animated: true, completion: nil)
}
}
}
In the storyboard I made a segue (which I called "goSegue") to the second view controller (which is called ListOfFoodViewController) using the button. My code for the second view controller is
import UIKit
class ListOfFoodViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var commaString = ""
var listOfTags = [String]()
let simpleTableIdentifier = "SimpleTableIdentifier"
//DATA SOURCE METHODS
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return listOfTags.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: simpleTableIdentifier)
if (cell == nil) {
cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: simpleTableIdentifier)
}
cell?.textLabel?.text=listOfTags[indexPath.row]
return cell!
}
override func viewDidLoad() {
super.viewDidLoad()
self.commaString = ""
self.listOfTags = self.commaString.components(separatedBy: ",")
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
So the user has to input a comma separated list of words, and I make a listOfTags out of it and I put this in the table. So before the segue, the function prepare(for segue ...) should be called and in here I make an instance of my second view controller (which has a property commaString) and assign the input to commaString as follows
let listOfFood = segue.destination as! ListOfFoodViewController
listOfFood.commaString = searchField.text!
But when I run this program, everything works except that the tableview is empty! I do not know why does this not work. Does it have something to do with the fact that I am using a Navigation Controller?
This is because you clear it here in viewDidLoad
self.commaString = ""
so comment that line and declare commaString like this
var commaString = "" {
didSet {
self.listOfTags = self.commaString.components(separatedBy: ",")
}
}
//
self.tableView.delegate = self
self.tableView.dataSource = self
In ViewController, you are saying:
listOfFood.commaString = searchField.text!
In ListOfFoodViewController, you are saying:
self.commaString = ""
So whatever ViewController put into ListOfFoodViewController, ListOfFoodViewController immediately erases it, and we end up with an empty table.

Saving Swift TableView data using NSUSerDefaults

I have a tableview in swift. Each cell has a label with different text. When a cell is selected it passes the text to a label on another view controller. I can save this data using a button (Save Data) and load the saved data with another button (Load Data).
My problem is I would ideally like to not use buttons, but have the data loaded automatically when the view loads. However, when I place the NSUSerDefaults code within viewDidLoad (), it saves the data but I am no longer able to change the selection i.e. it loads the first selection permanently. I have posted my code below for the destination ViewController and for the TableViewController.
ViewController
import UIKit
class DetailViewController: UIViewController {
#IBOutlet weak var detailLabel: UILabel!
var passedValue: String?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
detailLabel.text = passedValue
}
#IBAction func saveDataClicked(sender: AnyObject) {
var defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(self.detailLabel.text, forKey: "optionValue")
}
#IBAction func loadDataClicked(sender: AnyObject) {
var defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
if let optionValueIsNotNill = defaults.objectForKey("optionValue") as? String {
self.detailLabel.text = defaults.objectForKey("optionValue") as! String
}
}
}
TableViewController
import UIKit
class TableViewController: UITableViewController {
var valueToPass:String!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
override func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
// Get Cell Label
let indexPath = tableView.indexPathForSelectedRow;
let currentCell = tableView.cellForRowAtIndexPath(indexPath!) as UITableViewCell!;
valueToPass = currentCell.textLabel!.text
performSegueWithIdentifier("showDetailView", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?){
if segue.identifier == "showDetailView" {
// initialize new view controller and cast it as your view controller
var viewController = segue.destinationViewController as! DetailViewController
// your new view controller should have property that will store passed value
viewController.passedValue = valueToPass
}
}
}
if You want to save selected Data to NSUSerDefaults You should write your saving code in didSelectRowAtIndexPath like this
if you have a function to save the data in NSUserDefaults
save(DataArray[indexPath.row])
then it will be saved , but i think if your data used when the application is running only , i think the best option to use is static instance .

Crash: Can only call -[PFObject init] on subclasses conforming to PFSubclassing

Hello I am learning Swift and I am trying to implement Parse into my app. So I have one MapView which has some annotations. Those annotation are drawn from coordinate stored in Parse database. Each coordinate tuple in Parse has come other details too like FirstName LastName and all. Now Once the user click on the DETAILS button which is present in the mapView. It takes user to a table view controller where user sees all the details pertaining to the coordinates that were visible in the mapView. Till now everything works fine. So If I have 4 annotations in map view. Then By clicking on DETAILS I am redirected to the Table view controller where I can see the details pertaining to all the coordinate/annotations present in the mapView. Now I want a functionality where user can click on the table view controller cell and I can pass on the data to another view pertaining to that particular cell. SO if in the table view controller user click on 4th cell which is belonging to one of the annotation displayed on the map view. I want that 4th cell detail to be passed to another view controller.
map view (with multiple annotations) -> tableview controller (with multiple cells) -> view controller (pertaining to the cell the user clicked).
Problem: As soon as I click on any of the cell in table view controller so that I can see that cell's detail in another view controller My app crashes and I see error as
2015-11-30 21:38:42.998 LifeLine[53788:6661072] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Can only call -[PFObject init] on subclasses conforming to PFSubclassing.'
I have put breakpoint AT PREPAREFORSEGUE METHOD in table view controller. But even before the prepareforsegue method breakpoints hits my app is already crashed. So the crash happens between -- my click on the cell and the hitting of breakpoint on prepareforsegue.
My MapViewController:
import UIKit
import MapKit
import MessageUI
class MultipleAnnotationViewController: UIViewController, MKMapViewDelegate, MFMailComposeViewControllerDelegate
{
var arrayOfPFObject: [PFObject] = [PFObject]()
var lat_ :Double = 0
var long_ :Double = 0
#IBOutlet weak var dispAnnotation: MKMapView!
let currentUser = PFUser.currentUser()
var pinAnnotationView:MKPinAnnotationView!
override func viewDidLoad()
{
super.viewDidLoad()
dispAnnotation.delegate = self
for coordinateItem in arrayOfPFObject
{
let pointAnnotation = MKPointAnnotation()
self.lat_ = coordinateItem["Latitude"] as! Double
self.long_ = coordinateItem["Longitude"] as! Double
pointAnnotation.coordinate = CLLocationCoordinate2D(latitude: self.lat_, longitude: self.long_)
dispAnnotation.addAnnotation(pointAnnotation)
pointAnnotation.title = String(coordinateItem["FirstName"]) + " " + String(coordinateItem["LastName"])
let miles = 10.0
let scalingFactor = abs(( cos ( 2*M_PI*pointAnnotation.coordinate.latitude/360.0 ) ) )
var span = MKCoordinateSpan(latitudeDelta: 0, longitudeDelta: 0)
span.latitudeDelta = miles/69.0
span.longitudeDelta = miles/(scalingFactor * 69.0)
let region = MKCoordinateRegionMake(pointAnnotation.coordinate, span)
[ self.dispAnnotation.setRegion(region, animated: true)]
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
{
if segue.identifier == "TableView"
{
let controller = segue.destinationViewController as! TableViewController
controller.arrayOfPFObject = arrayOfPFObject
}
if segue.identifier == "TableView2"
{
let controller = segue.destinationViewController as! ChecklistViewController
controller.arrayOfPFObject = arrayOfPFObject
}
}
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView?
{
guard !(annotation is MKUserLocation)
else
{
return nil }
let identifier = "com.domain.app.something"
var annotationView = mapView.dequeueReusableAnnotationViewWithIdentifier(identifier) as? MKPinAnnotationView
if annotationView == nil
{
annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
annotationView?.pinTintColor = UIColor.redColor()
annotationView?.canShowCallout = true
}
else
{
annotationView?.annotation = annotation
}
return annotationView
}
}
My TableViewController:
import UIKit
class ChecklistViewController: UITableViewController
{
override func viewDidLoad() {
super.viewDidLoad()
}
var arrayOfPFObject: [PFObject] = [PFObject]()
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return arrayOfPFObject.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCellWithIdentifier(
"ChecklistItem", forIndexPath: indexPath)
let label = cell.viewWithTag(1000) as! UILabel
print(arrayOfPFObject.count)
print(indexPath.row)
let coordinateItem = arrayOfPFObject[indexPath.row]
label.text = String(coordinateItem["Address"])
return cell
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
{
if segue.identifier == "call"
{
let controller = segue.destinationViewController as! CallEmailViewController
if let indexPath = tableView.indexPathForCell( sender as! UITableViewCell)
{
controller.itemToEdit = arrayOfPFObject[indexPath.row]
}
}
}
}
My View Controller which I where I want to show detail of clicked Cell.
import UIKit
import CoreLocation
import MapKit
class CallEmailViewController: UITableViewController
{
var itemToEdit: PFObject = PFObject()
override func viewDidLoad()
{
super.viewDidLoad()
mail() //print(arrayOfPFObject.count)
}
func mail()
{
print("")
}
}
Below is table view image. As soon as I click on any of the table view I get the error.
Change var itemToEdit: PFObject = PFObject() to
var itemToEdit: PFObject?

I want to send data when I tap a button in tableView Cell

I am implementing a commentView for my app. I have a main view which is tableview contains picture and a button to go comment view.
I want that when user tap comment button in table view, view shows comment view and pass PFObject by prepareforSegue method.
now comment button works, but I have an error from prepareforsegue
here is my code.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "mainToComment") {
let destViewController : CommentVC = segue.destinationViewController as! CommentVC
destViewController.parentObjectID = parentObjectID
let selectedRowIndex = self.tableView.indexPathForSelectedRow
destViewController.object = (postsArray[(selectedRowIndex?.row)!] as? PFObject)
and here is my how my button works.
#IBAction func commentButtonTapped(sender: AnyObject) {
let button = sender as! UIButton
let view = button.superview!
let cell = view.superview as! MainTVCE
let indexPath = tableView.indexPathForCell(cell)
parentObjectID = postsArray[(indexPath?.row)!].objectId!!
when I debug, selectedRowIndex has no value(nil)
I think it cause of I tap button instead of cell.
How can I set indexPath for this?
or
How can I make it work?
I don't know name of your main TableViewCell view controller. Assume that, I name this view controller is MainTableViewCell.
I create a closure in MainTableViewCell:
var didRequestToShowComment:((cell:UITableViewCell) -> ())?
When button comment is tapped:
#IBAction func commentButtonTapped(sender: AnyObject) {
self.didRequestToShowComment?(self) // self is this UITableViewCell
}
In table cellForRowAtIndex... of your main view controller.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
...
mainTableViewCell.didRequestToShowComment = { (cell) in
let indexPath = tableView.indexPathForCell(cell)
let objectToSend = postsArray[indexPath.row] as? PFObject
// Show your Comment view controller here, and set object to send here
}
...
return cell
}