Guard let construction, still getting fatal error: Index out of range - swift

I am trying to parse some data with this code:
func findDate(data: String?) -> String {
guard let date: String? = (data!.componentsSeparatedByString("T"))[0] else{
return "20000101"
}
return date!
}
I tried the guard structure to prevent errors when there is no data found, or it has a different structure, but I still get the error when I run it:
fatal error: Index out of range
Does somebody know how to fix this?

The access of the element at index zero always happens. If the result of the call to components(separatedBy:) returns an empty array, your code crashes. Also, you should avoid force unwrapping data.
A solution to these crashes is to use the first property of the array, which is optional, so you can safely unwrap it.
guard let date = data?.components(separatedBy: "T").first else {
return "20000101"
}
return date.
Also, this could then be simplified using the nil coalescing operator:
return data?.components(separatedBy: "T").first ?? "20000101"

Please check out this code:
func findDate(data: String?) -> String {
guard let date: String? = (data?.componentsSeparatedByString("T"))?[0] else{
return "20000101"
}
return date!
}
findDate(nil)
findDate("")
findDate("98588T99")

There are two issues here:
1) Trying to access an index of an array that isn't large enough doesn't produce nil, it just crashes. For example, this code will crash:
let strings: [String] = []
guard let firstString = strings[0] else {
// couldn't find firstString
}
Instead, use the first method:
let strings: [String] = []
guard let firstString = strings.first else {
// couldn't find firstString
}
first attempts to access the first element of the array, and it returns nil if the array doesn't have a first element.
2) The purpose of guard let is to guarantee that the value you get out isn't nil. So your date value should be of type String, not String?, and you shouldn't have to force-unwrap it. For example:
let strings: [String] = ["one", "two", "three"]
guard let firstString = strings.first else {
// no first string
}
print(firstString) // firstString is not nil, no need to unwrap it

Related

Casting in swift

So my swift skills arent great... I want to try and retrieve an audio track description for a video and have come up with this method. If anything fails the method should return null
func getAudioTrackDescription(path: String) -> AudioStreamBasicDescription? {
let asset = getAssetFromPath(path: path)
guard let track = asset.tracks(withMediaType: AVMediaType.audio).first else {
return nil
}
guard let audioDesc = track.formatDescriptions.first else {
return nil
}
// let casted = audioDesc as! CMAudioFormatDescription
// THE SDF IS JUST AN EXAMPLE THAT CAUSES IT TO FAIL
// I ACTUALLY WANT TO USE THE COMMENTED LINE ABOVE
if let casted = ("Sdf" as! CMAudioFormatDescription) {
let basic = CMAudioFormatDescriptionGetStreamBasicDescription(casted)
guard let pointee = basic?.pointee else {
return nil
}
return pointee
} else {
return nil
}
}
I am really struggling to understand how the casting works in swift...
The track.formatDescriptions list holds type Any which means it could crash if the type is not what i expect it to be (CMAudioFormatDescription)
For example I have changed the if audioDesc to be the string "Sdf" and the code crashes.
I want to be able to check if audioDesc can be casted to CMAudioFormatDescription and if cant i want to return nil.
I tried using as? and this always gives me this error
Conditional downcast to CoreFoundation type 'CMAudioFormatDescription' (aka 'CMFormatDescription') will always succeed
Can someone help?
---------------------- EDIT
is below a safe approach?
func getVideoAudioChannelCount(path: String) -> Int {
guard let audioFile = getAudioInformation(path: path) else {
return DEFAULT_AUDIO_CHANNEL
}
return Int(audioFile.channelCount)
}
func getAudioInformation(path: String) -> AVAudioFormat? {
var returnVar: AVAudioFormat?;
do {
returnVar = try AVAudioFile(forReading: URL(fileURLWithPath: path)).fileFormat
} catch _ {
returnVar = nil
}
return returnVar
}
According to the documentation, the array will always contain CMFormatDescription (aka CMAudioFormatDescription), so you can safely cast with as!.
The array contains CMFormatDescriptions (see CMFormatDescription), each of which indicates the format of media samples referenced by the track.
You get the error that conditional casts always succeed because CMAudioFormatDescription is a Core Foundation type (it conforms to _CFObject). For more info, see here.
Edit:
is below a safe approach?
Yes, if by "safe" you mean it won't crash. You can simplify the code if you use try?:
func getAudioInformation(path: String) -> AVAudioFormat? {
try? AVAudioFile(forReading: URL(fileURLWithPath: path)).fileFormat
}

I`m trying to get array from this evaluatejavascript to array

webView.evaluateJavaScript("getLangs()", completionHandler : { (value, error) in
print(value as Any)
})
result
Optional(["English","Հայերեն","Русский"])
Use guard statement to unwrap optionals:
guard let array = value as? [String] else { return }
print(array)
guard creates the variable that can be accessed from outside its block. It is useful to unwrap a lot of Optionals.
Check this for more details.
if let array = value as? [String]{ print(array) }// now you've got the array

Swift cast if possible

I have this code
let jsonData = try JSONSerialization.jsonObject(with: data, options: []) as! [Any?]
if var first = jsonData[0] as! String?{
if(first=="Error"){
DispatchQueue.main.async(execute: {
self.postNotFoundLabel.isHidden = false
});
}else if(first=="Empty"){
print("Empty")
}
}
What i want to do is to cast jsonData[0] to String if it's possible and if it's not then move on.But instead when it's not possible application stops and gives me an error
Could not cast value of type '__NSDictionaryI' (0x1092054d8) to 'NSString' (0x108644508).
How can i cast only when it's possible?
You are trying to force-cast to an optional String. That's not what you want.
Change:
if var first = jsonData[0] as! String? {
to:
if var first = jsonData[0] as? String {
This tries to cast to String. If jsonData[0] isn't actually a String, you get nil and the if var fails.
And you probably want if let, not if var since you don't seem to be making any change to first.
First of all JSON objects will never return optional values so [Any?] is nonsense.
Second of all the error message says the type cast to string is inappropriate because the type of the result is actually a dictionary.
Solution: Check the type for both String and Dictionary
if let jsonData = try JSONSerialization.jsonObject(with: data) as? [Any],
let first = jsonData.first {
if let firstIsDictionary = first as? [String:Any] {
// handle case dictionary
} else if let firstIsString = first as? String {
// handle case string
}
}
PS: A type cast forced unwrap optional to optional (as! String?) is nonsense, too.
Here's the Swifty way to do what you're doing :)
guard let jsonData = try JSONSerialization.jsonObject(with: data, options: []) as? [Any?], let first = jsonData[0] as? String else {
DispatchQueue.main.async(execute: {
self.postNotFoundLabel.isHidden = false
});
return
}
if(first == "Empty") {
print(first)
}
Don't use as! if you are not sure that casting will succeed. The exclamation mark after the as keyword forces the casting, which throws an error if the casting does not succeed.
Use as? instead, which returns an optional variable of the type you were trying to casting to. If the casting fails, instead of throwing an error, it just returns nil.
let jsonData = try JSONSerialization.jsonObject(with: data) as? [Any]
if var first = jsonData.first as? String{
if(first=="Error"){
DispatchQueue.main.async(execute: {
self.postNotFoundLabel.isHidden = false
});
}else if(first=="Empty"){
print("Empty")
}
}

Swift correct approach to nil value

Reading about Optional values I was sure that all the bases were covered in my code, but I still get the dreaded unexpectedly found nil while unwrapping an Optional value.
That makes sense, since I've read: What does “fatal error: unexpectedly found nil while unwrapping an Optional value” mean?. It suggests making the Int optional, which is what I want:
func myCountUpdate(mainDict: [String : NSObject]) {
let myDict = mainDict["start"] as! [String : CFString]
let myCount = subDict["count"] as? String
let myTotal = Int(myCount)? // nope, it forces me to use non-optional !
// as the other thread suggest it's easy to check for nil with an optional int.
// how the hell can you do that if it won't allow you to make it optional?
if myTotal != nil {
print(myCount!)
let label: String = "\(myCount)"
text = label
} else {
text = nil
}
}
I've tried quite a bunch of things, including using other values to check for nil, etc. The issue is that the compiler will not allow me to declare the Int as non-optional, so what are my options? Xcode shows no warnings or suggestions on this issue, so maybe someone here has one - ty.
The best approach here is to use swift guards in order to check if a value is nil.
First, in the second line, where you use the subDict, its not referenced anywhere else, should it be myDict ?
The thing here is that the cast in let myCount = subDict["count"] as? String may be returning nil or there is not "count" in subDict. Therefore, when you do Int(myCount!), the force unwrapp of myCount is throwing the exception, since its nil.
You should avoid force unwrappings as much as you can, unless you are 100% sure that the value is not nil. In other cases, you should use the setting of a variable to check if it is not nil.
With your code, an updated version using guard would be the following:
func myCountUpdate(mainDict: [String : NSObject]) {
guard let myDict = mainDict["start"] as? [String : CFString],
let myCount = myDict["count"] as? String,
let myTotal = Int(myCount) else {
text = nil
return
}
print(myTotal)
let label: String = "\(count)"
text = label
}
This is safer, because if any of the conditions in the guard fails, then it's setting the text to nil an ending the method.
First unwrap the variable optional myCount(String?) to a variable called count (String).
let myCount = mainDict["count"] as? String
if let count = myCount {
//..
}
Then try to create a Int based on the variable count (String).
Which could return a nil since you could pass Int("Hi") or Int("1").
myTotal = Int(count)
Then after that you will have a variable called myTotal (Int?) with the result that you want.
Code
func myCountUpdate(mainDict: [String : Any]) {
let myDict = mainDict["start"] as? [String : Any]
if let myCount = myDict?["count"] as? String {
if let myTotal = Int(myCount) {
print(myTotal)
}
}
if let myCount = myDict?["count"] as? Int {
print(myCount)
}
}
Example 1
let data = [
"start": [
"count": "1"
]
]
myCountUpdate(mainDict: data) // outputs 1
Example 2
let data1 = [
"start": [
"count": 1
]
]
myCountUpdate(mainDict: data1) // outputs 1

Optional in text view showing when printing

Hi all I have tried a few solutions but no luck.
I am getting the text from Data Core, but the textview has optional on it.
when it prints it shows optional in the text.
page22TextView?.text = ("\(trans.value(forKey: "page22"))")
can anyone shed light on this ! have tried to unwrap but it stillelow: shows.
the full function is below:
func getTranscriptions () {
//create a fetch request, telling it about the entity
let fetchRequest: NSFetchRequest<TextInputs> = TextInputs.fetchRequest()
do {
//go get the results
let searchResults = try getContext().fetch(fetchRequest)
//I like to check the size of the returned results!
print ("num of results = \(searchResults.count)")
//You need to convert to NSManagedObject to use 'for' loops
for trans in searchResults as [NSManagedObject] {
page22TextView?.text = ("\(trans.value(forKey: "page22"))")
//get the Key Value pairs (although there may be a better way to do that...
print("\(trans.value(forKey: "page22"))")
}
} catch {
print("Error with request: \(error)")
}
}
try to set default value of getting nil value
page22TextView?.text = (trans.value(forKey: "page22") as? String) ?? ""
It'll set your value from trans and if it retrun nill will be set by "".
Hope it'll help you.
try with if-let statement:
if let result = trans.value(forKey: "page22") {
page22TextView?.text = result
}
Or try with guard statement:
guard let result = trans.value(forKey: "page22") else { return }
page22TextView?.text = String(describing: result)
Or you can force upwrap it like:
let result = trans.value(forKey: "page22")
if result != nil {
page22TextView?.text = result! as! String
}
Or you can follow the way suggested by #MrugeshTank below in answers
try to unwrap optional using if let then assign to your textview (if necessary then downcast your value)
if let value = trans.value(forKey: "page22") {
page22TextView?.text = value
}
or
use guard for unwrap