Guard statment in Swift - 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 }

Related

Swift Firestore prevent checking if Dictionary key exists

I have this chunk of code which returns the results from a Firestore query. Because I want to make sure that the values exists I'm checking every single one of them like if let driverLat = packageDetails["driverLat"] as? Double.. etc and also casting them. It is getting really annoying and I was wondering if there is a better solution to this?
db.collection("packages").document(documentID).getDocument() { (document, error) in
if let document = document, document.exists {
if let packageDetails = document.data() as [String: AnyObject]? {
if let driverLat = packageDetails["driverLat"] as? Double, let driverLon = packageDetails["driverLon"] as? Double {
if let destinationLat = packageDetails["destinationLat"] as? Double, let destinationLon = packageDetails["destinationLon"] as? Double {
// more code
}
}
}
}
}
I would say that you should use multiple guard-let statements. This prevents the pyramid shaped code which decreases the readability.
It would look like so:
typealias Json = [String: AnyObject]
db.collection("packages").document(documentID).getDocument() { (document, error) in
guard let document = document, document.exists else { return }
guard let packageDetails = document.data() as Json? else { return }
guard let driverLat = packageDetails["driverLat"] as? Double else { return }
guard let driverLon = packageDetails["driverLon"] as? Double else { return }
guard let destinationLat = packageDetails["destinationLat"] as? Double else { return }
guard let destinationLon = packageDetails["destinationLon"] as? Double else { return }
// more code
}

Can't extract var from firebase reading method

How can I get the numberOfMarkers out of the reading method of Firebase in Swift?
if I use the function in the {} this will save and I will be can use it not in the {}?
docRef = Firestore.firestore().document("Markol/Markers")
docRef.getDocument{ (docSnapshot, error) in
guard let docSnapshot = docSnapshot, docSnapshot.exists else {return}
let myData = docSnapshot.data()
let numberOfMarkers = myData?["NumberofMarkers"] as? Int ?? 0
}
//Here i want to get the let numberOfMarkers
var markerArrayList = [GMSMarker]()
func makeAMarker(_ Latitude:Double , _ Longitude:Double , _ Title:String,Snippet:String) -> GMSMarker{
let GmMarker = GMSMarker()
GmMarker.position = CLLocationCoordinate2D(latitude: CLLocationDegrees(Latitude), longitude: CLLocationDegrees(Longitude))
GmMarker.title = Title
GmMarker.snippet = Snippet
GmMarker.icon = UIImage(named: "smallStoreIcon")
return GmMarker
}
getDocument is an asynchronous task, so numberOfMarkers is only accessible before the closing }.
Do whatever you want with numberOfMarkers inside the getDocument listener, you may need to refactor your existing code to accommodate this. For example:
docRef = Firestore.firestore().document("Markol/Markers")
docRef.getDocument{ (docSnapshot, error) in
guard let docSnapshot = docSnapshot, docSnapshot.exists else {return}
let myData = docSnapshot.data()
let numberOfMarkers = myData?["NumberofMarkers"] as? Int ?? 0
processMarkers(numberOfMarkers, myData)
}
If this approach isn't clear, try posting more of your code in your question so others can help you restructure.
No you can't. Variable/constant is always visible just inside scope where is declared, between curly braces {...}.
What you probably want to do is to get this value to return it or use somewhere else. Don't do it, since getting data from Firestore is asynchronus task, use completion handler instead and return value (or nil if you don’t have value) as completion's parameter when you have it
func call(completion: #escaping (Int?) -> Void) {
...
docRef.getDocument{ docSnapshot, error in
guard let docSnapshot = docSnapshot, docSnapshot.exists else {
completion(nil)
return
}
let myData = docSnapshot.data()
let numberOfMarkers = myData?["NumberofMarkers"] as? Int
completion(numberOfMarkers)
}
}
then when you need to call it
call { numberOfMarkers in // code inside this closure is called with parameter of type `Int?` when you receive data and call completion from inside `call`
if let number = numberOfMarkers {
... // do something with it
}
}
... here you can use it for next purpose

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 does one change the value of a global variable in a function to be used later on?

I am trying to create a weather application, and in order to do that I need to parse out JSON. This works, (which I know because I tested it by printing), but I cannot change the value of the data I get to an already initialized global variable. A similar question was asked before, but even using the provided answers, I was not able to solve my own problem.
The following lines are where I initialize my global variables:
var currentTemperature = 88
var currentTime = 789
And the following Lines are where I parse the JSON and test by printing in my viewDidLoad function:
let url = URL(string: "https://api.darksky.net/forecast/c269e5928cbdadde5e9d4040a5bd4833/42.1784,-87.9979")
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil {
print("error")
}
else {
if let content = data {
do {
let json = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject
if let jsonCurrently = json["currently"] as? NSDictionary {
if let jsonCurrentTime = jsonCurrently["time"] {
currentTime = jsonCurrentTime as! Int
print(currentTime)
}
if let jsonCurrentTemperature = jsonCurrently["temperature"] {
currentTemperature = jsonCurrentTemperature as! Int
print(currentTemperature)
}
}
} catch {
}
}
}
}
task.resume()
I use the global variables when setting the text of a label in a different class: (however, only the initial value I set to the variable shows up, not the one from the parsed JSON)
let currentTemperatureLabel: UILabel = {
//label of the current temperature
let label = UILabel()
label.text = String(currentTemperature) + "°"
label.textColor = UIColor(red: 150/255, green: 15/255, blue: 15/255, alpha: 0.8)
label.textAlignment = NSTextAlignment.center
label.font = UIFont(name: "Damascus", size: 130)
label.font = UIFont.systemFont(ofSize: 130, weight: UIFontWeightLight)
return label
}()
The JSON example request can be found here: https://darksky.net/dev/docs#api-request-types
No matter what I do, I am not able to use the data from the JSON when I attempt to access the two global variables mentioned before.
var currentTemperature : Double = 88
var currentTime = 789
//...
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "https://api.darksky.net/forecast/c269e5928cbdadde5e9d4040a5bd4833/42.1784,-87.9979")
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil {
print("error")
}
else {
if let content = data {
do {
let json = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject
if let jsonCurrently = json["currently"] as? NSDictionary {
if let jsonCurrentTime = jsonCurrently["time"] as? Int {
currentTime = jsonCurrentTime
print(currentTime)
}
if let jsonCurrentTemperature = jsonCurrently["temperature"] as? Double {
currentTemperature = jsonCurrentTemperature
print(currentTemperature)
}
}
} catch {
}
}
}
}
task.resume()
}
I did these edits above to your code:
Changed your currentTemperature to be Double (Look at your JSON response to see what kind of response you get and what kind of data type it can be)
When trying to get "time" and "temperature" added optional wrapping to get the data correctly from the response with correct data type so that when assigning to your variables you wont need to do explicit unwrapping
EDIT:
Updated answer based on your comment about the global variables not being part of the class

Unexpectedly found nil while unwrapping an Optional value (AppleScript result)

I am trying to make a program in Swift 2 that runs and gets the result of an AppleScript script.
Here is my code:
import Foundation
func runAppleScript(script:String) -> String
{
let errorInfo = AutoreleasingUnsafeMutablePointer<NSDictionary?>()
let startAtLoginScript: NSAppleScript = NSAppleScript(source: script)!
let theDiscriptor:NSAppleEventDescriptor = startAtLoginScript.executeAndReturnError(errorInfo)
let theResult:String = theDiscriptor.stringValue! //This is whats causing the error
return theResult
}
let scriptResult = runAppleScript("tell app \"Spotify\" to playpause")
NSLog("\(scriptResult)")
The problem is the program crashes and outputs:
fatal error: unexpectedly found nil while unwrapping an Optional value
in the console. I have also tried if let else, however that does not work either. How would I fix this issue?
This was tested using a OS X Command Line template using the swift language.
Actually the error could come from NSAppleScript(source: script)! so the proper solution is to return an Optional String and not use force unwrapping at all:
func runAppleScript(script:String) -> String? {
let errorInfo: AutoreleasingUnsafeMutablePointer<NSDictionary?> = nil
let startAtLoginScript = NSAppleScript(source: script)
let theDescriptor = startAtLoginScript?.executeAndReturnError(errorInfo)
return theDescriptor?.stringValue
}
if let scriptResult = runAppleScript("tell app \"Spotify\" to playpause") {
NSLog("\(scriptResult)")
} else {
print("the script execution failed")
}
If you prefer having a default value instead of nil when it fails, then no need to return an Optional:
func runAppleScript(script:String) -> String {
let errorInfo: AutoreleasingUnsafeMutablePointer<NSDictionary?> = nil
let startAtLoginScript = NSAppleScript(source: script)
let theDescriptor = startAtLoginScript?.executeAndReturnError(errorInfo)
return theDescriptor?.stringValue ?? "" // if nil, returns the default ""
}
let scriptResult = runAppleScript("tell app \"Spotify\" to playpause")
NSLog("\(scriptResult)")
As for using the new Swift 2 error handling system, none of the methods you're using inside runAppleScript are throwing errors, so it would only work if you used a custom error type and throw the errors yourself. Example:
enum MyAppleScriptError: ErrorType {
case ExecutingScriptFailed
case GettingStringValueFailed
}
func runAppleScript(script:String) throws -> String {
let errorInfo: AutoreleasingUnsafeMutablePointer<NSDictionary?> = nil
let startAtLoginScript = NSAppleScript(source: script)
guard let theDescriptor = startAtLoginScript?.executeAndReturnError(errorInfo) else {
throw MyAppleScriptError.ExecutingScriptFailed
}
guard let value = theDescriptor.stringValue else {
throw MyAppleScriptError.GettingStringValueFailed
}
return value
}
do {
let scriptResult = try runAppleScript("tell app \"Spotify\" to playpause")
NSLog("\(scriptResult)")
} catch {
print(error)
}
Swift 3
Same idea, but some implementation details are different.
func runAppleScript(_ script:String) -> String? {
let errorInfo: AutoreleasingUnsafeMutablePointer<NSDictionary?>? = nil
if let startAtLoginScript = NSAppleScript(source: script) {
let theDescriptor = startAtLoginScript.executeAndReturnError(errorInfo)
return theDescriptor.stringValue
}
return nil
}
if let scriptResult = runAppleScript("tell app \"Spotify\" to playpause") {
NSLog("\(scriptResult)")
} else {
print("no return value")
}
And with error handling:
enum MyAppleScriptError: ErrorProtocol {
case ExecutingScriptFailed
case GettingStringValueFailed
}
func runAppleScript(_ script:String) throws -> String {
let errorInfo: AutoreleasingUnsafeMutablePointer<NSDictionary?>? = nil
let startAtLoginScript = NSAppleScript(source: script)
guard let theDescriptor = startAtLoginScript?.executeAndReturnError(errorInfo) else {
throw MyAppleScriptError.ExecutingScriptFailed
}
guard let value = theDescriptor.stringValue else {
throw MyAppleScriptError.GettingStringValueFailed
}
return value
}
do {
let scriptResult = try runAppleScript("tell app \"Spotify\" to playpause")
NSLog("\(scriptResult)")
} catch {
print(error)
}
I have fixed my own code.
import Foundation
func runAppleScript(script:String) -> String
{
let theResult:String
let errorInfo = AutoreleasingUnsafeMutablePointer<NSDictionary?>()
let startAtLoginScript: NSAppleScript = NSAppleScript(source: script)!
let theDiscriptor:NSAppleEventDescriptor = startAtLoginScript.executeAndReturnError(errorInfo)
if let _ = theDiscriptor.stringValue
{
theResult = theDiscriptor.stringValue!
} else {
theResult = ""
}
return theResult
}
let scriptResult = runAppleScript("")
What I had to do was check if theDiscriptor.stringValue has a value before unwrapping it. The error that I was getting is because I was trying to check the value after I had unwrapped it. Simply removing the ! on the check fixed my problem.
Edit
When trying this in Swift 3, the code let errorInfo = AutoreleasingUnsafeMutablePointer<NSDictionary?>() no longer works. To fix this, I have updated the code.
func runAppleScript(script:String) -> String?
{
var theResult:String?
let startAtLoginScript: NSAppleScript = NSAppleScript(source: script)!
var errorInfo:NSDictionary? = nil
let theDiscriptor:NSAppleEventDescriptor = startAtLoginScript.executeAndReturnError(&errorInfo)
if let _ = theDiscriptor.stringValue {theResult = theDiscriptor.stringValue!}
return theResult
}
Bonus
By returning an optional string, it allows you to check if the code returned a value.
Example:
Old way
let output = runAppleScript("script")
if output != ""
{
//Script returned date
} else {
//Script did not return data
}
New way
if let output = runAppleScript("script")
{
//Script returned data
} else {
//Script did not return data
}