Better way to write this if condition with a force-unwrap? - swift

Given these variables:
let urls: [URL] = []
let defaultURL = urls.first?
It's straightforward to write a conditional statement to do something based on whether there's a file or not.
if let url = defaultURL, FileManager.default.fileExists(atPath: url.path) {
// Do something
} else {
// Do something else
}
But if you don't need to do something in the first case, I can't come up with a better way to write the condition than this:
if defaultURL == nil || !FileManager.default.fileExists(atPath: defaultURL!.path) {
// Do something
}
Is there a better way to write this? I don't like the force-unwrap, but at the same time, it feels safe to write that.

There are many ways to solve this situation.
The simplest one is to extract the condition into a separate function/closure and check
func fileExists(defaultUrl: URL?) -> Bool {
guard let defaultUrl = defaultUrl else { return false }
return FileManager.default.fileExists(atPath: defaultUrl.path)
}
The same can be done using a variable:
let fileExists: Bool
if let defaultUrl = defaultUrl {
fileExists = FileManager.default.fileExists(atPath: defaultUrl.path)
} else {
fileExists = false
}
Or, you can use Optional.map:
let fileExists = defaultUrl.map { FileManager.default.fileExists(atPath: $0.path) } ?? false

You can make it more clear and readable by making a variable that makes if condition an english sentence i.e.
let fileNotExists = !(defaultUrl.map{ FileManager.default.fileExists(atPath: $0.path)} ?? false)
if fileNotExists {
//Do your stuff
}

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
}

Guard statment in Swift

I'm struggling with using a guard statement in Swift
The following is designed to strop force unwrapping
let pages = content.allpages?.pages?.compactMap{ $0.page?.html }
let titles = content.allpages?.pages?.compactMap{ $0.page?.title }
guard pages != nil && titles != nil else { let error = NSError(domain: "", code: -300, userInfo: [:]);
observer.onError(error); return }
let both = Array(zip(pages!, titles!))
It works, but I wanted to do something like
guard let pages = content.allpages?.pages?.compactMap{ $0.page?.html }, titles = content.allpages?.pages?.compactMap{ $0.page?.title } else {return}
but can't, some error about using autonomous arguments in the closure?
Why?
Trailing closure syntax isn't allowed in guard statements, because of some implementation difficulties.
Here's how I would write this:
guard let pages = content.allpages?.pages?.lazy.compactMap({ $0.page }) else {
observer.onError(NSError(domain: "", code: -300, userInfo: [:]))
return
}
let pageHTMLs = pages.compactMap { $0.html }
let pageTitles = pages.compactMap { $0.title }
let both = Array(zip(pages, titles))
Just add each closure inside a pair of brackets. (Also, add let for the titles)
guard let pages = content.allpages?.pages?.compactMap ({ $0.page?.html }), let titles = content.allpages?.pages?.compactMap ({ $0.page?.title }) else { return }

How to reduce if-condition looping - Swift

I know it sounds crazy, but just curious how I can reduce the if loop iteration for following? I have tried using guard let but stucked at some place.
{
if arenaEventItems == nil || arenaEventItems.count <= 0 {
return
}
if (arenaEventItems.count > 0 && (self.arenaEvents?.monthsDictObjList.count)! > 0){
if (self.tableView != nil){
if let arrVisibleRows = self.tableView.indexPathsForVisibleRows as? [IndexPath]{
if (self.tableView.indexPathsForVisibleRows!.count > 0){
let indexPath : IndexPath = self.tableView.indexPathsForVisibleRows!.first!
if let dict = self.arenaEvents?.monthsDictObjList[indexPath.row] {
if(self.arenaHeaderView != nil) && (dict.count) > 0 {
self.arenaHeaderView?.setMonthTitle(string: (dict.keys.first!))
let selectedMonthTitle = (dict.keys.first!)
for month in (self.arenaEvents?.uniqueMonthOnlyList)! {
if (selectedMonthTitle.contains(month)){
selectedMonthIndex = (self.arenaEvents?.uniqueMonthOnlyList.index(of: month)!)!
break
}
}
}
}
}
}
}
}
}
You can reduce it like that, without any forced unwrapping or nesting:
guard let arenaEventItems = arenaEventItems,
!arenaEventItems.isEmpty,
let arenaEvents = self.arenaEvents,
!arenaEvents.monthsDictObjList.isEmpty,
let arenaHeaderView = self.arenaHeaderView,
let indexPath = self.tableView?.indexPathsForVisibleRows?.first,
let selectedMonthTitle = arenaEvents.monthsDictObjList[indexPath.row].keys.first
else {
return
}
arenaHeaderView.setMonthTitle(string: selectedMonthTitle)
if let monthIndex = arenaEvents.uniqueMonthOnlyList.index(where: { selectedMonthTitle.contains($0) }) {
selectedMonthIndex = monthIndex
}
you replace if ... return with guard !... else return to avoid nesting
you replace .count > 0 with !...isEmpty as best practice
you replace multiple access to self.something? with if let something = self.something to avoid threading issues
you unloop for ... in ... { if (...) { ... } } to .index(where: ...)
You can combine all the conditions in "if" and get something like this:
if let eventItems = arenaEventItems,
eventItems.count > 0,
let events = self.arenaEvents,
!events.monthsDictObjList.isEmpty,
let tableView = self.tableView,
let arrVisibleRows = self.tableView.indexPathsForVisibleRows as? [IndexPath],
!arrVisibleRows.isEmpty,
let indexPath : IndexPath = arrVisibleRows.first,
let dict = events.monthsDictObjList[indexPath.row],
let headerView = self.arenaHeaderView,
!dict.isEmpty {
headerView.setMonthTitle(string: (dict.keys.first!))
let selectedMonthTitle = (dict.keys.first!)
for month in events.uniqueMonthOnlyList! {
if (selectedMonthTitle.contains(month)){
selectedMonthIndex = (events.uniqueMonthOnlyList.index(of: month)!)!
break
}
}
}
You should consider restructuring your code, your code is not readable and incomprehensible for anyone who look at it. Since, you are using Swift, it is really easy to write such code with guard ... else, if ... let
pattern.
Some improvements that you can do on class is have your view non nil ie make them implicitly unwrapped optional, since you will always be connecting them to storyboard.
#IBOutlet var tableView: UITableView!
#IBOutlet var arenaHeaderView: ArenaHeaderView!
Also, you have arrays which can go to nil, why do you want it to be nil. You could simply initialize an empty array and dictionaries. That way you can reduce some more comparison code like so,
arenaEventItems: [String: String] = [:]
With that changes and a bit of refactoring, you could possibly rewrite your code to something like this,
guard !arenaEventItems.isEmpty,
let arenaEvents = arenaEvents,
let indexPath = tableView.indexPathsForVisibleRows?.first,
let dict = arenaEvents.monthsDictObjList[indexPath.row],
let selectedMonthTitle = dict.keys.first
else {
return
}
arenaHeaderView.setMonthTitle(string: selectedMonthTitle)
for month in arenaEvents.uniqueMonthOnlyList where selectedMonthTitle.contains(month) {
if let selectedIndex = arenaEvents.uniqueMonthOnlyList.index(of: month) {
selectedMonthIndex = selectedIndex
break
}
}

How can I use a # symbol instead of ? in URLQueryItem?

If I create a URL like this:
guard var urlComponents = URLComponents(url: "https://example.com/something", resolvingAgainstBaseURL: false) else {
return
}
let queryItems = URLQueryItem(name: "token", value: token)
urlComponents.queryItems = [queryItems]
guard let urlWithQueryItem = urlComponents.url else {
return
}
I want the end result to be something like https://example.com/something#token=7as78f6asd678768asd768asd678
Instead of the default https://example.com/something?token=7as78f6asd678768asd768asd678
(I'm looking for something smarter than a search and replace of the ? character)
Thanks
As others have noted, URLQueryItem object is more intended for use with querystrings vs anchors/fragments. With that said, below is a function that helps you accomplish what your questions is asking.
func constructURL(withURL urlBase: String, andFragmentFromQueryItem queryItem: URLQueryItem) -> URL?{
guard let url = URL(string:urlBase) else {
return nil
}
guard var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
return nil
}
let fragmentKey = queryItem.name
let fragmentValue = queryItem.value ?? ""
urlComponents.fragment = "\(fragmentKey)=\(fragmentValue)"
guard let urlWithFragment = urlComponents.url else {
return nil
}
return urlWithFragment
}
let url = constructURL(withURL:"https://example.com/something",
andFragmentFromQueryItem: URLQueryItem(name: "token", value: "tokenValue"))
let urlString = url?.absoluteString
print("\(urlString!)")
Here is a link to a working Swift fiddle of the code.
http://play.swiftengine.io/code/8JKI3/2
As #rmaddy points out, there is a definite difference between the meaning of ? and #. '?' introduces query parameters, which are part of the URL. '#' introduces the "fragment" which is not considered part of the URL.
You can, however use URLComponents to add a fragment to a URL. Just use the fragment property instead:
urlComponents.fragment = "token=\(token)"

Found nil, after nil-check

I'm getting the unexpectedly found nil error even though I'm checking for nil before using the value. Have I missed something here?
if self.orders[indexPath.row]["distanceToHouseHold"] != nil {
cell.distanceTextField.text = "\(self.orders[indexPath.row]["distanceToHouseHold"] as! String)m"
}
The error is on the second line of course.
Probably distanceToHouseHold is not a String, and it's failing when type casting. Try using the if-let check or the new guard check.
if let distance = self.orders[indexPath.row]["distanceToHouseHold"] as? String {
cell.distanceTextField.text = "\(distance)m"
}
Do like this instead:
if let distance self.orders[indexPath.row]["distanceToHouseHold"] as? String {
cell.distanceTextField.text = distance + "m"
}
This will both check its not nil AND cast into a String
Use guard let
guard let whatever = self.orders[indexPath.row]["distanceToHouseHold"] as? String else {
return
} 
cell.distanceTextField.text = whatever + "m"
}
If it is not working try this:
guard let whatever = self.orders[indexPath.row]["distanceToHouseHold"] else {
return
} 
cell.distanceTextField.text = String(whatever) + "m"
}
Try this:
let whatValue = self.orders[indexPath.row]["distanceToHouseHold"]
println(" value is \(whatValue)");
This will help you see what the output. After that you can decide what's going wrong.
Cheers!!