Swift prepareForSegue initializes empty array - swift

when I pass data from FirstViewController to SecondViewController (table view) via prepareForSegue I have an empty array in secondViewController to collect the data, but every time the view loads the array is init as empty.
Before getting into core data or nsuserdefaults, how can I add the data tho the second view controller?
There is a similar problem here but the method has not solved my problem.
Trouble passing Array through prepareForSegue
// View Controller
// a new dictionary object is created
myDictionary = ["apples": 3, "oranges": 4, "bananas": 5]
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "saveData" {
let dvc = segue.destionationViewController as! MyTableViewController
dvc.arrayOfDictionaries += [myDictionary]
}
resetAll()
}
// My Table View Controller
var arrayOfDictionaries: [[String: AnyObject]] = []
var dictionary = [String:AnyObject]()
in TableViewContrller I have a func
override func viewDidLoad() {
super.viewDidLoad()
loadData() //
}
func loadData() {
for dict in arrayOfDictionaries {
for (key, value) in dict {
// extract data
dictionary[key] = value
}
}
}
How can I get data to persist in arrayOfDictionaries?
Thanks

Why are you appending values to the array if it is empty?
To avoid future problems, you can just call
dvc.arrayOfDictionaries = [myDictionary]
So you'll never care about the arrayOfDictionaries state (if it is initialized, if it's empty or anything else)
And please, check your viewDidLoad and other view-lifecycle methods in the table view controller. Is possible that you're initializing again the array there?

Related

fatal errors with optionals not making sense

I keep getting a fatal error saying how a value was unwrapped and it was nil and I don't understand how. When I instantiate a view controller with specific variables they all show up, but when I perform a segue to the exact VC, the values don't show up.
Take these functions for example...
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
if let displayVC = storyboard?.instantiateViewController(withIdentifier: Constants.Storyboards.TeachStoryboardID) as? SchoolEventDetailsViewController {
displayVC.selectedEventName = events[indexPath.row].eventName
displayVC.selectedEventDate = documentsDate[indexPath.row].eventDate
displayVC.selectedEventCost = documentsCost[indexPath.row].eventCost
displayVC.selectedEventGrade = documentsGrade[indexPath.row].eventGrade
displayVC.selectedEventDocID = documentsID[indexPath.row]?.docID
navigationController?.pushViewController(displayVC, animated: true)
}
}
This combined with this function :
func verifyInstantiation() {
if let dateToLoad = selectedEventDate {
dateEditableTextF.text = dateToLoad
}
if let costToLoad = selectedEventCost {
costEditableTextF.text = costToLoad
}
if let gradesToLoad = selectedEventGrade {
gradesEditableTextF.text = gradesToLoad
}
if let docIDtoLoad = selectedEventDocID {
docIDUneditableTextF.text = docIDtoLoad
}
if let eventNameToLoad = selectedEventName {
eventNameEditableTextF.text = eventNameToLoad
}
}
Helps load the data perfectly, but when I try to perform a segue from a search controller the data is not there.
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = selectedEventName
I set the title of the vc to have the event name , and I also recently added a text field to store it as well for experimental purposes (this question).
Now the issue is I want to do a data transfer from an Algolia Search Controller to that VC and I got all the other fields to show up, except for one and that was the document ID. So I created a completion handler function to get the document ID as a string and have it inserted into the vc when the segue is performed, just like how it's there when the vc is instantiated.
Here is the function :
func getTheEventDocID(completion: #escaping ((String?) -> ())) {
documentListener = db.collection(Constants.Firebase.schoolCollectionName).whereField("event_name", isEqualTo: selectedEventName ?? navigationItem.title).addSnapshotListener(includeMetadataChanges: true) { (querySnapshot, error) in
if let error = error {
print("There was an error fetching the documents: \(error)")
} else {
self.documentsID = querySnapshot!.documents.map { document in
return EventDocID(docID: (document.documentID) as! String)
}
let fixedID = "\(self.documentsID)"
let substrings = fixedID.dropFirst(22).dropLast(3)
let realString = String(substrings)
completion(realString)
}
}
}
I thought either selectedEventName or navigationItem.title would get the job done and provide the value when I used the function in the data transfer function which I will show now :
//MARK: - Data Transfer From Algolia Search to School Event Details
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
otherVC.getTheEventDocID { (eventdocid) in
if let id = eventdocid {
if segue.identifier == Constants.Segues.fromSearchToSchoolEventDetails {
let vc = segue.destination as! SchoolEventDetailsViewController
vc.selectedEventName = self.nameTheEvent
vc.selectedEventDate = self.dateTheEvent
vc.selectedEventCost = self.costTheEvent
vc.selectedEventGrade = self.gradeTheEvent
vc.selectedEventDocID = id
}
}
}
}
But it ends up showing nothing when a search result is clicked which is pretty upsetting, I can't understand why they're both empty values when I declared them in the SchoolEventDetailsVC. I tried to force unwrap selectedEventName and it crashes saying there's a nil value and I can't figure out why. There's actually a lot more to the question but I just tried to keep it short so people will actually attempt to read it and help since nobody ever reads the questions I post, so yeah thanks in advance.
I'm a litte confused what the otherVC is, which sets a property of itself in the getTheEventDocID, whilste in the closure you set the properties of self, which is a different controller. But never mind, I hope you know what you are doing.
Since getTheEventDocID runs asynchronously, the view will be loaded and displayed before the data is available. Therefore, viewDidLoad does not see the actual data, but something that soon will be outdated.
So, you need to inform the details view controller that new data is available, and refresh it's user interface. Something like
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
otherVC.getTheEventDocID { (eventdocid) in
if let id = eventdocid {
if segue.identifier == Constants.Segues.fromSearchToSchoolEventDetails {
let vc = segue.destination as! SchoolEventDetailsViewController
vc.selectedEventName = self.nameTheEvent
vc.selectedEventDate = self.dateTheEvent
vc.selectedEventCost = self.costTheEvent
vc.selectedEventGrade = self.gradeTheEvent
vc.selectedEventDocID = id
vc.updateUI()
}
}
}
}
and in the destination view controller:
class SchoolEventDetailsViewController ... {
override func viewDidLoad() {
super.viewDidLoad()
updateUI()
}
func updateUI () {
navigationItem.title = selectedEventName
// and so on
}
}
Ok so I decided to attempt a workaround and completely ditched the getTheEventDocID() method because it was just causing me stress. So I decided to ditch Firebase generated document IDS and just use 10 digit generated ids from a function I made. I also figured out how to add that exact same 10 digit id in the Algolia record by just storing the random 10 digit id in a variable and using it in both places. So now instead of using a query call to grab a Firebase generated document ID and have my app crash everytime I click a search result, I basically edited the Struct of the Algolia record and just added an eventDocID property that can be used with hits.hitSource(at: indexPath.row).eventDocID.
And now the same way I added the other fields to the vc by segue data transfer, I can now do the same thing with my document ID because everything is matching :).

Passing an array of structs from UICollectionView to DetailView and use the indexes to go from one item to another?

I have a UICollectionView that is populated by an array of productItem structs, the struct blueprint is in it's own swift file called ProductoItem.swift
struct ProductoItem {
let id: Int
let name: String
var price: String
var picture: String
var description: String
}
This works and I can successfully populate my UICollectionView with it, what I want to do now is to pass this struct to a detailView so I create this variable in the detailView.
var productos = [ProductoItem]()
And I put the following code in my prepareForSegue Method.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "detalleProducto" {
let controller = segue.destination as! ProductoDetailViewController
controller.productos = productos
}
}
What I don't know how to do know is:
First as we are sending the entire array of structs I want to pass the values stored on the CollectionView cell's index to the DetailView to populate it using just the data of the product's cell, then I also want to have two buttons to go to the next / prev product that just go to the next / prev index value of my struct. How can I do this?
First, create a new 'currentIndex: Int' attribute in your detailView.
From your first UIViewController (the one that has the UICollectionView), pass the current index and the array to your DetailView:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "detalleProducto" {
let controller = segue.destination as! ProductoDetailViewController
controller.productos = productos
// do whatever you need to get your index
controller.currentIndex = self.collectionView.indexPathsForSelectedItems()[0].row
}
}
Then in your DetailView, you have your array and the current index, so you can just get the info of this specific product by doing:
let currentProduct = self.productos[currentIndex]
To get to the previous or next one, you can just increment or decrement 'currentIndex'.

How do I display the data fetched from called view controller into a dynamic tableviewcell of the calling view controller while using unwind segue.?

I have dynamic tableview, wherein one of the cell (duration) when tapped opens another view controller which is a list of duration viz (30 min, 1 hour, 2 hours and so fort). One of the durations when selected should display the selected duration in the first view controller. I am able to pass the data back to first view controller using unwind segue but unable to display the passed value. DOn't know whats missing.
I am displaying the code below:
FIRST VIEW CONTROLLER (CALLING)
#IBAction func unwindWithSelectedDuration(segue:UIStoryboardSegue) {
var cell = tableView.dequeueReusableCellWithIdentifier("durationCell") as! durationTableViewCell
if let durationTableViewController = segue.sourceViewController as? DurationTableViewController,
selectedDuration = durationTableViewController.selectedDuration {
cell.meetingDurationCell.text = selectedDuration
duration = selectedDuration
}
SECOND VIEW CONTROLLER (CALLED)
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "SaveSelectedDuration" {
if let cell = sender as? UITableViewCell {
let indexPath = tableView.indexPathForCell(cell)
if let index = indexPath?.row {
selectedDuration = durationList[index]
}
}
}
}
tableView.dequeueReusableCellWithIdentifier should only be called within tableView:cellForRowAtIndexPath:. It has no use outside this context.
The easiest fix is to just reload the table once you have stored the selected duration:
#IBAction func unwindWithSelectedDuration(segue:UIStoryboardSegue) {
if let durationTableViewController = segue.sourceViewController as? DurationTableViewController {
selectedDuration = durationTableViewController.selectedDuration
tableView.reloadData()
}
}
Note that this assumes you only need one selectedDuration for your whole table, rather than one per row. If you need one per row, I assume you have them stored in an array somewhere, so it is that array that should be updated instead before the reloadData.

How to update user interface on Core Data?

I have an app with UITableView, Core Data and NSFetchedResultsController as well. I have passed data to the DetailViewController. And I can delete them from the DetailViewController! In the Apple's iOS Notes app, you can see such as functions as I wanted! When you delete a notes from the DetailViewController ( for example ), object deleted and Notes app automaticlly shows the next or previos notes! I want to create such as function. How update user interface after deleted current object? Here's my codes! Thanks `
import UIKit
import CoreData
class DetailViewController: UIViewController {
#IBOutlet weak var containerLabel: UILabel!
var retrieveData:NSManagedObject!
var managedObjectContext:NSManagedObjectContext!
var manager:Manager!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.containerLabel.userInteractionEnabled = false
self.containerLabel.textColor = UIColor.redColor()
self.containerLabel.alpha = 0
UIView.animateWithDuration(2.5) { () -> Void in
self.containerLabel.alpha = 1
}
if let demo = self.retrieveData.valueForKey("titleField") as? String {
self.containerLabel.text = demo
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func backToMain(sender: AnyObject) {
// Back to the MainTableViewController
self.dismissViewControllerAnimated(true, completion: nil)
}
#IBAction func trashButton(sender: AnyObject) {
self.managedObjectContext.deleteObject(retrieveData)
do {
try self.managedObjectContext.save()
} catch {
}
self.dismissViewControllerAnimated(true, completion: nil)
}
`
If I have 5 items on the list like so:
When I select fourth item from the list ( for example ). And detailVC shows me selected item like this:
And I want to delete them. When I delete "Four" and then my containerLabel.text shows previous objects from the list. They're after "Four" is deleted, "Three","Two" and "One" as well. After "One" is deleted my containerLabel.text shows strings
But I have left single object called as "Five"
My problem is "Five"! I can't delete it. Example: In iOS Notes App, if you have five objects on the list like my demo app. When you select fourth object from the list ( for example ). And begin deleting them, after "Four" is delete iOS Notes App shows "Five". And "Five" ( last object on the list ) is deleted and then iOS Notes App shows "Three", "Two" and "One". Maybe problem line is here:
if index != 0 {
self.retrieveData = fetchedObject[index! - 1]
} else {
self.retrieveData == fetchedObject[0]
}
Let's take the easy (but not so elegant) route here. You'll have to pass over all the fetched objects to the detail VC like this:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "yourSegueIdentifier"{
if let destinationVC = segue.destinationViewController as? DetailViewController{
destinationVC.managedObjectContext = yourContext
destinationVC.retrieveData = yourManagedObject
destinationVC.arrayOfFetchedObjects = yourFetchedResultsController.fetchedObjects
//pass over other data...
}
}
}
Then, in your detailVC, write a method that will be executed when you press the delete button. Something like this:
#IBAction func trashButton(sender: AnyObject) {
//make sure you have an array with YourObjects
guard let fetchedObjects = arrayOfFetchedObjects as? [YourObjectType] else {return}
//get index of the shown object in the array of fetched objects
let indexOfObject = fetchedObjects.indexOf(retrieveData)
//delete the object from the context
self.managedObjectContext.deleteObject(retrieveData)
do {
try self.managedObjectContext.save()
//delete the object from the fetchedObjects array
fetchedObjects.removeAtIndex(indexOfObject)
} catch {
}
//get the object that should be shown after the delete
if indexOfObject != 0{
//we want the object that represents the 'older' note
retrieveData = fetchedObjects[indexOfObject - 1]
updateUserInterface(true)
}
else{
//the index was 0, so the deleted object was the oldest. The object that is the oldest after the delete now takes index 0, so just use this index. Also check for an empty array.
if fetchedObjects.isEmpty{
updateUserInterface(false)
}
else{
retrieveData = fetchedObjects[0]
updateUserInterface(true)
}
}
}
func updateUserInterface(note: Bool){
switch note{
case true:
//update the user interface
if let demo = retrieveData.valueForKey("titleField") as? String {
self.containerLabel.text = demo
}
case false:
self.containerLabel.text = "no more notes"
}
}
You either need to pass the details view controller
A list of all managed objects and an index for where in the list to start
A current managed object and a callback to get the next object
In order for it to have enough information to do what you want. The callback approach is nicest and is a simple form of delegate, where your master view controller is the delegate supplying the extra data.

CoreData and tableview

How can a tableview (in another view controller) read this data from first view controller?
Should I put this into array or something? This is code for retrieving saved data, I already managed to save data.
do {
let request = NSFetchRequest(entityName: "Users")
let results = try context.executeFetchRequest(request)
if results.count > 0 {
for item in results as! [NSManagedObject] {
let name = item.valueForKey("username")
let password = item.valueForKey("passwords")
print(name!, password!)
array.append (name, password) // this do not work, can't put that in array so tableview can read array in another vc.
}
}
}
When I put name and password objects in array it says:
Cannot convert any object to array string
How can I retrieve core data to array so tableview can read from array, or should it read from coredata directly?
If you want to share data from VC A to VC B, you should use segue method and the display data in the tableView.
Segue method
self.performSegue(withIdentifier: "identifier", sender: nil)
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "identifier" {
let destination = segue.destination as! AlreadyStartViewController
destination.variable = variable /*and other*/
}
}
And then display data in the tableView
If you want to ask me about code - ask