Swift 2 - Array - unexpectedly found nil while unwrapping an Optional value - arrays

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]])
}

Related

Swift / Adding objects to NSMutable Array

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.

Pass array between View Controllers

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.

Initializer for conditional binding must have Optional type, not '[String]' - Xcode 8.0 Swift 3.0

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!)
}
}

Storing array from Parse into local array

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.

swift array.removeAtIndex error

How come I'm getting the error "NSArray does not have a member named 'removeAtIndex'. How can I fix this? The error is on the fourth last line. Sorry if my question is stupid, I'm fairly new to programming. I appreciate all the help I get.
import Foundation
let userDefaults = NSUserDefaults.standardUserDefaults()
func isAppAlreadyLaunchedOnce()->Bool{
let defaults = NSUserDefaults.standardUserDefaults()
if let isAppAlreadyLaunchedOnce = defaults.stringForKey("isAppAlreadyLaunchedOnce"){
println("App already launched")
return true
}
else{
defaults.setBool(true, forKey: "isAppAlreadyLaunchedOnce")
println("App launched first time")
return false
}
}
struct newFactBook {
let factsArray = [
"Ants stretch when they wake up in the morning.",
"Ostriches can run faster than horses.",
"Olympic gold medals are actually made mostly of silver.",
"You are born with 300 bones; by the time you are an adult you will have 206.",
"It takes about 8 minutes for light from the Sun to reach Earth.",
"Some bamboo plants can grow almost a meter in just one day.",
"The state of Florida is bigger than England.",
"Some penguins can leap 2-3 meters out of the water.",
"On average, it takes 66 days to form a new habit.",
"Mammoths still walked the earth when the Great Pyramid was being built."]
}
var checkLaunch = isAppAlreadyLaunchedOnce()
var oldFunFactsArray = []
if(checkLaunch == false){
oldFunFactsArray = newFactBook().factsArray
}
else if (checkLaunch == true){
oldFunFactsArray = userDefaults.objectForKey("key") as! NSArray
}
func randomFacts1() -> (String, Int){
var unsignedArrayCount = UInt32(oldFunFactsArray.count)
var unsignedRandomNumber = arc4random_uniform(unsignedArrayCount)
var randomNumber = Int(unsignedRandomNumber)
return (oldFunFactsArray[randomNumber] as! String, randomNumber)
}
oldFunFactsArray.removeAtIndex[randomFacts1().1] //error here
userDefaults.setObject(oldFunFactsArray, forKey:"key")
userDefaults.synchronize()
println(oldFunFactsArray)
We have some problems here:
1 How to invoke a method
removeAtIndex is a method that accepts an Int as parameters. It cannot be invoked this way
removeAtIndex[randomFacts1().1]
instead you should write
removeAtIndex(randomFacts1().1)
2. The type of oldFunFactsArray is NSArray and it's wrong.
Intact when you write this:
var oldFunFactsArray = []
Swift does infer this:
var oldFunFactsArray : NSArray = []
But at this point you have an immutable NSArray so it does not have the removeAtIndex method.
Since you are using Swift I suggest you to declare the var oldFunFactsArray as follow:
var oldFunFactsArray : [String]
if checkLaunch == false {
oldFunFactsArray = newFactBook().factsArray
} else {
oldFunFactsArray = userDefaults.objectForKey("key") as! [String]
}
Please note that here I am declaring a Swift array of String(s). Since I use the var keyword this array will be mutable and we will be able to invoke removeAtIndex later on.
Also note that in the else branch I am force-casting the result of objectForKey to [String]. It will be fine since I see, below, you are writing the oldFunFactsArray in that slot.
Hope this helps.
You need to use NSMutableArray to use this method.
NSArray is not Mutable (can not change its content after it is intialized).

Resources