Trying to pass an array between view controllers. I am not sure why as pretty sure that the array has something in it, still when it arrives on the other side it seems to be empty. No errors... just empty.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "segueMapSelection" {
if let destinaton = segue.destination as? MapTableChoiceViewController {
//destinaton.maps = sender as? [SkiMap]
print("size of array before passing it through: ", maps.count)
destinaton.maps = self.maps
}
}
}
#IBAction func SelectDifferentMapButton(_ sender: Any, forEvent event: UIEvent) {
performSegue(withIdentifier: "segueMapSelection", sender: self.maps)
}
On my receiving ViewController I have a
var maps : [ObjectTypeHere]! = []
Any idea what I am doing here? I have left the code commented of the other way I tried. When I tried that it gave an error.
Thanks for your help.
The issue is related to the sequence of things in view controller life cycles.
self.maps is collect at some point in the source VC
the segue begins, and reaches prepare(for segue:) occurs, which passes self.maps to the destination VC
only then does the destination controller (MapTableChoiceViewController) execute viewDidLoad, which (re)initializes the array
For that reason, if you declare the array as:
var maps:[ObjectTypeHere]!
You should have passed the array successfully - of course, assuming the object type between self.maps and designation.maps are the same.
For a full definition of the sequence of events, here's a detailed description.
Related
I am getting values from a firebase real-time database. I want to store these values into an array and display them in a UITableView. Here is what is happening:
I have defined the Array before my viewDidLoad() function as the following:
var taskTitles: [Task] = []
In my viewDidLoad() function I am calling another function to generate the array:
override func viewDidLoad() {
super.viewDidLoad()
//Setting the Title for the nav bar
title = "To Do List"
configureNavigationItems()
taskTitles = createArray() // creating the array of tasks in db
tableView.delegate = self
tableView.dataSource = self
}
In this function, I am passing information to my classes. Task and TaskCell. They are simply just handling the Title of the task.
func createArray() -> [Task] {
taskRef = Database.database().reference(withPath: "Tasks")
//getting values from db, storing them in an array.
refHandle = taskRef?.observe(DataEventType.value, with: { snapshot in
for taskSnapshot in snapshot.children {
let nodeA = taskSnapshot as! DataSnapshot
let keyA = nodeA.key
let theTask = Task(title: String(keyA))
self.taskTitles.append(theTask)
print("In the FOR Loop --> ", self.taskTitles)
}
print("outside of FOR Loop --> ", self.taskTitles)
})
print("outside of observe func --> ", taskTitles)
return taskTitles
}
}
However, it doesn't seem to save my items into the array. I did some debugging to figure where things were going wrong. Hopefully, the picture below can clarify:
Any idea what the issue is?
Your call to taskRef?.observe is asynchronous. That is why you see "outside of observe func --> []" printed before the other lines.
What is happening is that your createArray() function calls observe and then returns taskTitles which is still empty. Then your view finishes loading and shows (presumably) an empty table. After this the observe function calls your closure with the snapshot and you update taskTitles, but at this point tableView is already on screen and empty and you would have to take further actions to reload it (like, for example, calling reloadData() on it).
It is perhaps also worth mentioning that you are modifying your property in that function, then returning it, and assigning it to itself. This is perhaps redundant.
Im new to Swift and Im having a hard time understanding why this is not working - I've tried many different combinations of this through examples on stackoverflow and my variable "collections" still comes out empty (see last line) so I'm guessing I'm missing a small (but important) detail. Appreciate any help!
class CollectionsViewController: UITableViewController {
var collections = NSMutableArray()
override func viewDidLoad() {
super.viewDidLoad()
SDK.sharedInstance()
.getAuthenticatedUserBoards(withFields: ["id", "name","url","description","image"],
success: { data in
guard let myData = data?.parsedJSONDictionary["data"] as? [[String: Any]]
else {
return
}
for item in myData {
self.collections.add(item)
}
}, andFailure: nil)
print("collections.....\(collections)")
//Output: collections.....()
}
The loading of the data is asynchronous. Since your goal to load your table view after the data loads, you need to call reloadData on your table view at the end of the success block. But UI calls must be made on the main queue so you should use DispatchQueue to do this.
Here is what you need:
override func viewDidLoad() {
super.viewDidLoad()
SDK.sharedInstance()
.getAuthenticatedUserBoards(withFields: ["id", "name","url","description","image"],
success: { data in
guard let myData = data?.parsedJSONDictionary["data"] as? [[String: Any]]
else {
return
}
for item in myData {
self.collections.add(item)
}
print("collections.....\(collections)")
DispatchQueue.main.async {
tableView.reloadData()
}
}, andFailure: nil)
}
Since you are currently updating self.collections in the background, there is a small chance your UI on the main queue will see a partially up-to-date set of data. So I would recommend one further change:
override func viewDidLoad() {
super.viewDidLoad()
SDK.sharedInstance()
.getAuthenticatedUserBoards(withFields: ["id", "name","url","description","image"],
success: { data in
guard let myData = data?.parsedJSONDictionary["data"] as? [[String: Any]]
else {
return
}
var list = NSMutableArray()
for item in myData {
list.add(item)
}
DispatchQueue.main.async {
self.collections = list
print("collections.....\(collections)")
tableView.reloadData()
}
}, andFailure: nil)
}
This ensure the main collections property is only updated on the main thread.
I also suggest you use a Swift array of a specific type instead of using an NSMutableArray.
I'm assuming getAuthenticatedUserBoards is making some sort of asynchronous call to an API. Since we're not sure how long that call is going to take, the task is placed on a background thread while the rest of the app's tasks continue to run on the main thread. Your print statement is one of those tasks that will run while the API call is happening in the background. In essence, the print statement executes before anything inside of the getAuthenticatedUserBoards is completed.
The list of collections should be printed directly after the for loop and before the end of the closure. You can verify the order in which the different tasks are completed by placing print statements at various different points within your function - you should be able to see that your current print statement will print before any other statements that you place inside of the closure.
I am creating a function that allows me to create a array on VC1 and then I can transfer over the array using "prepare for segue" to VC2. On VC2 I can append an item to the array in VC2 and then transfer over the array back to VC1. My issue is, is that on the line if let newString = receivedString (VC1) and erro is comming up that states, "Initializer for conditional binding must have Optional type, not '[String]'"This is my code on VC1:
var receivedString = [String]()
override func viewDidAppear(_ animated: Bool) {
if let newString = receivedString {
print(newString)
}
}
This is my code on VC2:
let stringToPass = "Hello World"
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destinationVC = segue.destination as! ViewController
destinationVC.receivedString = [stringToPass]
destinationVC.receivedString.append("DYLAN MURPHY")
}
I am new to Swift so I realise that I may be completely wrong so I appreciate any help that leads me closer to this goal.
It's telling you that, since receivedString is not an optional type, the if let doesn't make sense. You've created it as an array of strings and that's what it's always going to be.
Even if the array is empty, it will still evaluate as an array.
I would personally do what you want to do a little bit differently. In VC1 I would add this code:
var username = [String]()
In VC2:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toSecondViewController" {
let hello = segue.destination as! ViewController
hello.username.append(textField.text!)
}
}
When I try to store arrays from Parse into a local array I can only access it within the findObjectsInBackgroundWithBlock {...}. When I print it outside of that block, it shows []...
Code :
var qArray : [[Int]] = []
override func viewDidLoad() {
super.viewDidLoad()
let query = PFQuery(className: "Trivia")
query.findObjectsInBackgroundWithBlock {
(objects: [PFObject]?, error: NSError?) in
if objects != nil {
if let objects = objects {
for object in objects {
self.qArray.append(object["mainPattern"] as! [Int])
}
print(self.qArray) // Prints a multi dimension array
}
}
if error != nil {
print(error)
}
}
print(self.qArray) // prints []
}
It's most likely because the array hasn't been populated yet because it's running in the background. You can try using dispatch_group to circumvent this issue.
I think you're misunderstanding what findInBackground means. It means the code after the callback continues to execute, so it calls query.findInBackground.... and then it continues with the next line, which is print(self.qArray). At some point later on, it hears back from the database and it executes all the code inside the Callback, which is when the array finally gets populated.
How can I prevent my app to crash if an array is empty?
var UserVideosInfo = [[String]]()
#IBAction func actionBtn(sender: UIButton) {
userVideoInfo = NSUserDefaults.standardUserDefaults().objectForKey("UserVideos") as! [[String]]
}
If the array is empty userVideosInfo crashes saying:
fatal error: unexpectedly found nil while unwrapping an Optional value
I have tried:
if var userVideoInfoArray:[[String]] = UserVideosInfo {
userVideoInfoArray = NSUserDefaults.standardUserDefaults().objectForKey("UserVideosJSON") as! [[String]]
}
The issue seems to be related to NSUserDefault instead.
With your attempt to retrieve the object with the key "UserVideos", NSUserDefaults might have returned nil. It was then explicitly unwrapped into an array, and that causes the program to crash.
Please verify the existence of userVideoInfo first before proceeding.
var UserVideosInfo: [[String]]?
#IBAction func actionBtn(sender: UIButton) {
userVideoInfoOrNil = NSUserDefaults.standardUserDefaults().objectForKey("UserVideos")
if userVideoInfo = userVideoInfoOrNil as! [[String]] {
//Do stuff with userVideoInfo
} else {
//Value is nil
}
}
You can prevent it by registering the key/value pair as Apple recommends.
In AppDelegate add as soon as possible
let defaults = NSUserDefaults.standardUserDefaults()
let defaultValues = ["UserVideos" : [[String]]()]
defaults.registerDefaults(defaultValues)
The benefit is that UserVideos can never be nil and then you can safely write
userVideoInfoArray = NSUserDefaults.standardUserDefaults().objectForKey("UserVideos") as! [[String]]
Please read the section about registering default values in the documentation
If I use the code below the app its not crashing
if self.Videos.count >0{
Videos = (NSUserDefaults.standardUserDefaults().objectForKey("Videos") as! [[String]])
}