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

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?

Related

Trouble with segue/unsegue when making a list app

I'm new to coding, so please bear with me. I was following an online tutorial that worked with plists to make a habit list app. I have a table view controller that shows a list of habits and a segue that presents modally a view controller that has text fields to add a habit.
enter image description here
Every time it runs, nothing happens when I click on the "save" and "cancel" buttons. I realize this is a vague question as it doesn't pinpoint to a specific issue, but I am really struggling with fixing this issue and would really appreciate if someone proofreads the code. The app builds and runs with no warnings.
This is the table view controller that shows the habits:
class TableViewController: UITableViewController {
//MARK: Properties
var habits = [Habit]()
//MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return habits.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Table view cells are reused and should be dequeued using a cell identifier.
guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell") else {
fatalError("The dequeued cell is not an instance of ViewController.")
}
// Fetches the appropriate habit for the data source layout.
let habit = habits[indexPath.row]
cell.textLabel?.text = habit.mainGoal
cell.detailTextLabel?.text = habit.microGoal
return cell
}
#IBAction func unwindToHabitList(sender: UIStoryboardSegue) {
if let source = sender.source as?ViewController, let habit = source.habit {
//add a new habit
let newIndexPath = IndexPath(row: habits.count, section: 0)
habits.append(habit)
tableView.insertRows(at: [newIndexPath], with: .automatic)
}
}
This is the view controller that adds a habit:
class ViewController: UIViewController, UITextFieldDelegate, UINavigationControllerDelegate {
#IBOutlet weak var saveButton: UIBarButtonItem!
#IBOutlet weak var mainGoalTextField: UITextField!
#IBOutlet weak var microGoalTextField: UITextField!
var habit: Habit?
//method for configuring controller before presenting
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
//configure this destination view controller only when save button is pressed
guard let button = sender as? UIBarButtonItem, button === saveButton else {
os_log("save button was not pressed, cancelling", log: OSLog.default, type: .debug)
return
}
let mainGoal = mainGoalTextField.text ?? ""
let microGoal = microGoalTextField.text ?? ""
//set the habit to be passed on to tableViewController after the unwind segue
habit = Habit(mainGoal: mainGoal, microGoal: microGoal)
}
#IBAction func cancel(_ sender: UIBarButtonItem) {
// Depending on style of presentation (modal or push presentation), this view controller needs to be dismissed in two different ways.
let isPresentingInAddHabitMode = presentingViewController is UINavigationController
if isPresentingInAddHabitMode {
dismiss(animated: true, completion: nil)
}
else if let owningNavigationController = navigationController{
owningNavigationController.popViewController(animated: true)
}
else {
fatalError("The ViewController is not inside a navigation controller.")
}
}
I appreciate any and all help in advance!
STORYBOARD CONNECTIONS:
TABLEVIEW CONTROLLER CONNECTIONS
ADD HABIT VIEW CONTROLLER CONNECTIONS

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.

Tab Bar Item hidden behind tableview / not being shown?

I have an empty view with a tab bar pictured below, when i load a routine a table appears containing the contents, however it seems to overlay the tab bar killing off app navigation. Its not sized in the storyboard to overlay it and its constraint locked to not do so, so im unsure why this is happening, pics of the issue and VC's code below:
VC Code:
import Foundation
import UIKit
import CoreData
class RoutineController: UIViewController, UITableViewDataSource, UITableViewDelegate {
// MARK: - DECLARATIONS
#IBAction func unwindToRoutine(segue: UIStoryboardSegue) {}
#IBOutlet weak var daysRoutineTable: UITableView!
#IBOutlet weak var columnHeaderBanner: UIView!
#IBOutlet weak var todaysRoutineNavBar: UINavigationBar!
#IBOutlet weak var addTOdaysRoutineLabel: UILabel!
let date = Date()
let dateFormatter = DateFormatter()
let segueEditUserExerciseViewController = "editExerciseInRoutineSegue"
//This is the selected routine passed from the previous VC
var selectedroutine : UserRoutine?
// MARK: - VIEWDIDLOAD
override func viewDidLoad() {
super.viewDidLoad()
setupView()
daysRoutineTable.delegate = self
daysRoutineTable.dataSource = self
view.backgroundColor = (UIColor.customBackgroundGraphite())
dateFormatter.dateStyle = .short
dateFormatter.dateFormat = "dd/MM/yyyy"
let dateStr = dateFormatter.string(from: date)
todaysRoutineNavBar.topItem?.title = dateStr + " Routine"
}
// MARK: - VIEWDIDAPPEAR
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.daysRoutineTable.reloadData()
self.updateView()
}
// MARK: - TABLE UPDATE COMPONENTS
private func setupView() {
updateView()
}
// MARK: - TABLE SETUP
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let count = self.selectedroutine?.userexercises?.count
{
print("exercises: \(count)")
return count
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as? TodaysRoutineTableViewCell else {
fatalError("Unexpected Index Path")
}
cell.backgroundColor = UIColor.customBackgroundGraphite()
cell.textLabel?.textColor = UIColor.white
configure(cell, at: indexPath)
return cell
}
// MARK: - VIEW CONTROLER ELEMENTS VISIBILITY CONTROL
fileprivate func updateView() {
var hasUserExercises = false
if let UserExercise = self.selectedroutine?.userexercises {
hasUserExercises = UserExercise.count > 0
}
addTOdaysRoutineLabel.isHidden = hasUserExercises
columnHeaderBanner.isHidden = !hasUserExercises
daysRoutineTable.isHidden = !hasUserExercises
}
// MARK: - SETTING DATA FOR A TABLE CELL
func configure(_ cell: TodaysRoutineTableViewCell, at indexPath: IndexPath) {
if let userExercise = selectedroutine?.userexercises?.allObjects[indexPath.row]
{
print("\((userExercise as! UserExercise).name)")
cell.todaysExerciseNameLabel.text = (userExercise as! UserExercise).name
cell.todaysExerciseRepsLabel.text = String((userExercise as! UserExercise).reps)
cell.todaysExerciseSetsLabel.text = String((userExercise as! UserExercise).sets)
cell.todaysExerciseWeightLabel.text = String((userExercise as! UserExercise).weight)
}
}
}
requested table constraints
Debug hierarchy
The Segue that sends the user back to the view that looses its tab bar
if segue.identifier == "addToTodaySegue" {
let indexPath = workoutTemplateTable.indexPathForSelectedRow
let selectedRow = indexPath?.row
print("selected row\(selectedRow)")
if let selectedRoutine = self.fetchedResultsController.fetchedObjects?[selectedRow!]
{
if let todaysRoutineController = segue.destination as? RoutineController {
todaysRoutineController.selectedroutine = selectedRoutine
}
}
}
I also feel perhaps the viewDidAppear code may cause the issue, perhaps the super class?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.daysRoutineTable.reloadData()
self.updateView()
Updated storyboard image
I suspect you need to embed your viewController in a UINavigationController.
Consider the following setup:
I suspect your setup is like the upper one:
TapBar -> ViewController -show segue-> ViewController
Which results in a hidden tapbar, like in your description:
While the bottom setup:
TapBar -> NavigationCntroller -rootView-> ViewController -show segue-> ViewController
results in:
which is what you want, how I understood.
Update
It's hard to see. The screenshot of your Storyboard is in pretty low resulution, but the segues look wrong. Double check them. A Segue of type show (e.g push) looks like this:
Also clear project and derived data. Segue type changes sometime are ignored until doing so.
Try calling this self.view.bringSubviewToFront(YourTabControl).
The previous suggestion should work. But the content at the bottom part of tableview will not be visible as the tabbar comes over it. So set the bottom constraint of tableview as the height of tabbar.

Swift 3 - Changing map Type from a view controller to another

I am working in Xcode 8 - Swift.
I have 2 viewcontrollers: MAin Screen (first) and View Options (Second ViewController).
In the Main Screen I have a map with my location. In this screen I have a button so that the user can go to Views Screen and select what type o map he wants (this is done by a segue). The user can choose from three options: Normal, Satellite and Terrain. I implemented the second view controller has a table view of buttons. Whatever I choose it sends me to the home screen (first view controller).
I dont have another segue to send me to first view controller has I implemented a navigation controller with back buckon.
Tested this so far. Everything working ok : maps, and other things.
I created a protocol/delegate so that, when I choose what type of map I want to see in the first view controller, it sends me the information to the first. I tried send a string from the first view controller to the second and a managed to do that.
The thing is, I cannot change the map type... I dont know why but it was shows me the standard map...
Can anyone help me please?
Here is a sample of me code. Main Screen First View Controller:
class ViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate, DataSentDelegate {
var locationManager = CLLocationManager()
#IBOutlet var map: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
self.map.showsUserLocation = true
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let userLocation: CLLocation = locations[0]
let latitude = userLocation.coordinate.latitude
let longitude = userLocation.coordinate.longitude
let latDelta: CLLocationDegrees = 0.05
let longDelta: CLLocationDegrees = 0.05
let span = MKCoordinateSpanMake(latDelta, longDelta)
let location = CLLocationCoordinate2DMake(latitude, longitude)
let region = MKCoordinateRegionMake(location, span)
self.map.setRegion(region, animated: true)
self.locationManager.stopUpdatingLocation()
}
//Protocol
func userSentView(data: String) {
switch (data) {
case "Satellite":
self.map.mapType = .satellite
break
case "Terrain":
self.map.mapType = .hybrid
break
default:
self.map.mapType = .standard
break
}
}
//Protocol
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "SegueView" {
let TVCView: TVCView = segue.destination as! TVCView
TVCView.delegate = self
}
}
}
In the second viewcontroller I have this:
protocol DataSentDelegate {
func userSentView(data: String)
}
class TVCView: UITableViewController {
var ViewNames = [String]()
var identities = [String]()
//***
var delegate: DataSentDelegate? = nil
override func viewDidLoad() {
super.viewDidLoad()
self.title = "View"
//globalView = 1;
ViewNames = ["Normal","Satellite","Terrain"]
identities = ["Normal","Satellite","Terrain"]
self.tableView.tableFooterView = UIView()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return ViewNames.count
}
//Add names to cells
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CellView", for: indexPath)
cell.textLabel?.text = ViewNames[indexPath.row]
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let viewController = storyboard?.instantiateViewController(withIdentifier: "Home")
self.navigationController?.pushViewController(viewController!, animated: true)
//To know which view was selected
let vcName = identities[indexPath.row]
//***
if delegate != nil {
let data = vcName
delegate?.userSentView(data: data)
dismiss(animated: true, completion: nil)
}
}
}
It looks like the problem is that you are pushing on a whole new (Home) ViewController rather than popping off the stack to the existing one.
You should remove these lines:
let viewController = storyboard?.instantiateViewController(withIdentifier: "Home")
self.navigationController?.pushViewController(viewController!, animated: true)
And replace this line:
dismiss(animated: true, completion: nil)
with:
let _ = navigationController?.popViewController(animated: true)

Passing data thru a Swift segue works every other time

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.