Multiple unwrap of optionals - swift

I have an object, let's say its called "Event". Event has several optionals.
struct Event {
var name: String!
var location: String?
var attendees: [String]?
var dresscode: String?
var startDate: NSDate!
var endDate: NSDate?
var description: String {
return "Name: \(name). Location: \(location). Attendees: \(attendees). Dresscode: \(dresscode). Start date: \(startDate). End date: \(endDate)."
}
}
When I call for the description, it will return a string, and depending on if they exist or not the optional values will return nil or "Optional(Event name)". I want the option for some of the properties to be nil and return the unwrapped value if they exist.
I have looked at this option:
var description: String {
switch (location, attendees, dresscode, endDate) {
//All available
case let (.Some(location), .Some(attendees), .Some(dresscode), .Some(endDate)):
return "Name: \(name). Location: \(location). Attendees: \(attendees). Dresscode: \(dresscode). Start date: \(startDate). End date: \(endDate)."
case let (.None, .Some(attendees), .Some(dresscode), .Some(endDate)):
return "Name: \(name). Location: Not Set. Attendees: \(attendees). Dresscode: \(dresscode). Start date: \(startDate). End date: \(endDate)."
default: return "Something."
}
This works, but for me to cover all the cases it is going to take forever. It may contain hundreds of cases.
So my question is: Is there an easier way to do this?
Return nil if not available, and unwrap if it is.
Thank you!

You’re in need of the nil-coalescing operator, ??:
// s will be Not Set if name == nil,
// unwrapped value of s otherwise
let s = name ?? "Not set"
You can then use that inside the string interpolation:
var description: String {
let notSet = "Not set"
let attendeesString = attendees.map { ",".join($0) }
return "Name: \(name ?? notSet). Location: \(location ?? notSet). Attendees: \(attendeesString ?? notSet). Dresscode: \(dresscode ?? notSet). Start date: \(startDate ?? notSet). End date: \(endDate ?? notSet)."
}
Two things to note - you can’t put quotes in string interps so you have to create a notSet variable (of course, you can use multiple different default values depending on your need). And you can’t use arrays in them either so need to convert the array to a string (in this case by joining entries with a comma – that other map is optional map – if the array is non-nil, return an optional string, otherwise nil).

Related

Swift function returning empty data

i'm new to coding in Swift, I have been looking around for some time for a solution to this issue and as of yet unable to find anything that solves my problem.
I am using a function to return some data from Firebase. After many errors and much research, I have managed to get the function 'working' without throwing errors, but the data that is returned is blank. Essentially I am trying to return multiple values and hold them in an array when I call the function.
The data is returning fine from Firebase, when I print out the variable just after setting it, it will print out correctly but when I do the same just before returning the function, it returns blank. If I try to return the function just after setting the data, I get the error "Unexpected non-void return value in void function".
Here is the full code:
func loadClientData(client_id: String) -> (address_1: String, address_2: String, city: String, company_name: String, contact_name: String, county: String, email: String, phone: String, postcode: String, country: String) {
let db = Firestore.firestore()
let userID : String = (Auth.auth().currentUser!.uid)
print("\(userID)/clients/existing/\(client_id)")
let docRef = db.collection("\(userID)/clients/existing/").document(client_id)
var address_1 = ""
var address_2 = ""
var city = ""
var company_name = ""
var contact_name = ""
var county = ""
var email = ""
var phone = ""
var postcode = ""
var country = ""
docRef.getDocument { (document, error) in
if let document = document, document.exists {
let data = document.data()
address_1 = data?["address_1"] as? String ?? ""
address_2 = data?["address_2"] as? String ?? ""
city = data?["city"] as? String ?? ""
company_name = data?["company_name"] as? String ?? ""
contact_name = data?["contact_name"] as? String ?? ""
county = data?["county"] as? String ?? ""
email = data?["email"] as? String ?? ""
phone = data?["phone"] as? String ?? ""
postcode = data?["postcode"] as? String ?? ""
country = data?["country"] as? String ?? ""
print("Company name is \(company_name)") // <---- THIS prints out the company name
} else {
print("client does not exist")
return
}
}
print("Company name is \(company_name)") // <---- THIS prints the company name as blank
return (address_1: address_1, address_2: address_2, city: city, company_name: company_name, contact_name: contact_name, county: county, email: email, phone: phone, postcode: postcode, country: country)
}
This is being called like so:
let companyInfo = loadClientData(client_id: self.items[indexPath.item].company)
print(companyInfo)
And prints out the following:
(address_1: "", address_2: "", city: "", company_name: "",
contact_name: "", county: "", email: "", phone: "", postcode: "",
country: "")
Thanks in advance for your input.
There are many questions on this site that are fundamentally the same as yours, and you should take a look at them. To avoid redundancy, I'll explain in simple terms, but I urge you to read up on similar questions.
In a nutshell, what's happening is that loadClientData fetches data asynchronously, meaning that it happens in the background while other stuff happens in the foreground. The foreground stuff includes your code that calls loadClientData and prints it out. The foreground stuff doesn't wait for the background stuff to finish.
You're getting nothing because the background stuff isn't finished processing by the time you print(companyInfo).
Real world example:
Your mom asks you to go buy a lemon for a dish she is cooking for dinner.
She starts cooking, but she has no lemon!
Why? Because you haven't yet returned from the supermarket, and your mom didn't wait.
If you want to make the foreground stuff wait, you have a lot of options, which you can consider by looking at other similar questions.
You need to refactor your function to not return anything, and take a completion handler. The completion handler is a block of code that the caller passes to your function. Your function will call the comletion handler once the results are available. This is a common coding pattern in Swift, and your firebase getDocument() call is written to use a completion handler.
Search on Swift completion handler design pattern to learn more.
**Note:
Using a completion handler is only one way to do this. You could also use the delegate design pattern, but completion handers are a more modern way of handling async events, and the way I would suggest handling your problem.

How do I unwrap an optional within a struct and use an if statement to print the optional?

I am getting an error message for my use of nil, and I am confused on how to properly unwrap the optional while placing it in an if statement to print if it is non-nil.
The following is my code:
struct DatingProfile{
var Name: String
var Age: Int
var City: String
var FavoriteMovie: String?
var Hobbies: String?
}
let myDatingProfile = DatingProfile(
Name: "William",
Age: 30,
City: "North Hollywood",
FavoriteMovie: nil,
Hobbies: "Basketball"
)
if let myFavoriteMovie = FavoriteMovie = nil {
print("\(Name) does not have a favorite movie")
} else {
print(myDatingProfile.FavoriteMovie)
}
}
The following is an error log:
exit status 1
main.swift:12:26: error: use of unresolved identifier 'FavoriteMovie'
if let myFavoriteMovie = FavoriteMovie = nil{
^~~~~~~~~~~~~
main.swift:13:12: error: use of unresolved identifier 'Name'
print("\(Name) does not have a favorite movie")
^~~~
main.swift:17:11: warning: expression implicitly coerced from 'String?' to 'Any'
print(myDatingProfile.FavoriteMovie)
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
main.swift:17:27: note: provide a default value to avoid this warning
print(myDatingProfile.FavoriteMovie)
~~~~~~~~~~~~~~~~^~~~~~~~~~~~~
?? <#default value#>
main.swift:17:27: note: force-unwrap the value to avoid this warning
print(myDatingProfile.FavoriteMovie)
~~~~~~~~~~~~~~~~^~~~~~~~~~~~~
!
main.swift:17:27: note: explicitly cast to 'Any' with 'as Any' to silence this warning
print(myDatingProfile.FavoriteMovie)
~~~~~~~~~~~~~~~~^~~~~~~~~~~~~
as Any
main.swift:20:1: error: extraneous '}' at top level
}
^
You need to access properties of a struct via the instance of the struct. You can only use the property names directly inside instance methods of that struct.
if let myFavoriteMovie = myDatingProfile.FavoriteMovie {
print(myFavoriteMovie)
} else {
print("\(myDatingProfile.Name) does not have a favorite movie")
}
You should also be conforming to the Swift naming convention, which is lowerCamelCase for variable names and properties (city, name, favoriteMovie, etc.).
Try the following, and please use camelCase for swift properties.
Also, you should really consider reading The Swift Programming Language. Start from here.
struct DatingProfile {
var name: String
var age: Int
var city: String
var favoriteMovie: String?
var hobbies: String?
}
let myDatingProfile = DatingProfile(
name: "William",
age: 30,
city: "North Hollywood",
favoriteMovie: nil,
hobbies: "Basketball"
)
// Use if-let syntax
if let myFavoriteMovie = myDatingProfile.favoriteMovie {
print(myFavoriteMovie)
} else {
print("\(myDatingProfile.name) does not have a favorite movie")
}
// Or use guard-let syntax
guard let myFavoriteMovie = myDatingProfile.favoriteMovie else {
print("\(myDatingProfile.name) does not have a favorite movie")
return
}
print(myFavoriteMovie)

Whats wrong with my convenience initializer when I attempt to call self.init?

I wrote the code below. The error I'm getting is at the end of my convenience initializer when I attempt to call self.init. What's wrong with my logic or syntax? Or how would I debug this? The error Xcode is giving is "cannot invoke with an argument list of type".
Thank you for any help on this
import Foundation
import UIKit
class Item: NSObject {
var name: String
var valueInDollars: Int
var serialNumber: String?
let dateCreated: NSDate
var stolen: Bool?
init(name: String, valueInDollars: Int, serialNumber: String?, dateCreated: NSDate, stolen: Bool?) {
self.name = name
self.valueInDollars = valueInDollars
self.serialNumber = serialNumber
self.dateCreated = NSDate()
self.stolen = stolen
//below why did I have to call super.init for this custom class that inherits from NSObject? Doesn't it automatically get called anyway once the custom object is initialized since it inherits from NSObject? It just seems like more work on my behalf which isn't fair. it should do it automatically. Why wouldn't it do it automatically if it inherits from NSObject?
super.init()
}
convenience init(random: Bool = false) {
if random {
let adjectives = ["Fluffy", "Rusty", "Shiny"]
let nouns = ["MacBook Pro", "Red Tribe Bike", "Vegan Pizzas"]
//take a variable that's random; the highest value for this random number will be the number of ojbects in the adjectives array
var idx = arc4random_uniform(UInt32(adjectives.count))
//now use this random variable and let it be the index of the adjectives array...so basically it'll be a random object from the adjectives array
let randomAdjective = adjectives[Int(idx)]
//AWESOME!! Now that the random adjective is stored in the randomAdjective constant, let's re-use the idx variable...Ayyyyeeeee re-use!
//we'll re-use it by doing the same process or close to the same process for nouns
idx = arc4random_uniform(UInt32(nouns.count))
let randomNoun = nouns[Int(idx)]
//now let's concatenate these two clever words, shall we!!
let randomName = "\(randomAdjective) \(randomNoun)"
//yayyy we're programmmminnngg!
//now let's ....whad de fuk....
let randomValue = Int(arc4random_uniform(100))
let randomSerialNumber = NSUUID().uuidString.components(separatedBy: "-").first!
let betterNotBeStolen: Bool = false
self.init(name: randomName, valueInDollars: randomValue, serialNumber: randomSerialNumber, stolen: betterNotBeStolen)
}
}
}
You got the error
"Cannot invoke 'Item.init' with an argument list of type '(name:
String, valueInDollars: Int, serialNumber: String, stolen: Bool)'"
because you missed the the dateCreated param in the self.init(params...).
So you need to replace this line
self.init(name: randomName, valueInDollars: randomValue, serialNumber: randomSerialNumber, stolen: betterNotBeStolen)
with this one
self.init(name: randomName, valueInDollars: randomValue, serialNumber: randomSerialNumber,dateCreated: NSDate(), stolen: betterNotBeStolen)
The next error which you will see after is
Self.init isn't called on all paths before returning from initializer
So you need to add else statement because the initializer don't know what to do when the random param is false.
convenience init(random: Bool = false) {
if random {
let adjectives = ["Fluffy", "Rusty", "Shiny"]
let nouns = ["MacBook Pro", "Red Tribe Bike", "Vegan Pizzas"]
//take a variable that's random; the highest value for this random number will be the number of ojbects in the adjectives array
var idx = arc4random_uniform(UInt32(adjectives.count))
//now use this random variable and let it be the index of the adjectives array...so basically it'll be a random object from the adjectives array
let randomAdjective = adjectives[Int(idx)]
//AWESOME!! Now that the random adjective is stored in the randomAdjective constant, let's re-use the idx variable...Ayyyyeeeee re-use!
//we'll re-use it by doing the same process or close to the same process for nouns
idx = arc4random_uniform(UInt32(nouns.count))
let randomNoun = nouns[Int(idx)]
//now let's concatenate these two clever words, shall we!!
let randomName = "\(randomAdjective) \(randomNoun)"
//yayyy we're programmmminnngg!
//now let's ....whad de fuk....
let randomValue = Int(arc4random_uniform(100))
let randomSerialNumber = NSUUID().uuidString.components(separatedBy: "-").first!
let betterNotBeStolen: Bool = false
self.init(name: randomName, valueInDollars: randomValue, serialNumber: randomSerialNumber,dateCreated: NSDate(), stolen: betterNotBeStolen)
} else {
self.init(name: "SomeName", valueInDollars: 3, serialNumber: "123", dateCreated: NSDate(), stolen: true)
}
}

Concatenate literal with Optional String

Whats the proper way to implement this ? preferably in one line.
var name: String?
...
let username = "#" + name
Note: username must be String? I don't want to unwrap name for concatenation.
Edit: if name is nil, username should also be nil.
You can use the map method of Optional:
let username = name.map { "#" + $0 }
If name is nil then the closure is not executed and the result is nil. Otherwise the closure is evaluated with $0 set to the unwrapped name.
Try this:
let username = name.flatMap { "#\($0)" }
EDITED based on updated requirements:
You can do it a couple of ways. Here's one:
var name: String?
var username: String?
if let name = name {
username = "#" + name
}
Probably the simplest and more readable option is using if let but you could also define a method to prepend a String, opposed to String.append and then use optional chaining:
extension String {
func prepending(prefix: String) -> String {
return prefix + self
}
}
var name: String?
let username = name?.prepending("#")

How to use Swift struct implicit initializer?

Here is the code I have:
struct Algorithm {
let category: String = ""
let title: String = ""
let fileName: String = ""
}
let a = Algorithm(
The autocomplete shows the only valid initializer:
But I was expecting to use the implicit initializer like
Algorithm(category: "", title: "", fileName: "")
This gives the error:
Argument passed to call that takes no arguments
There are even screenshots on another issue that shows this call being successfully used.
What am I doing wrong?
The problem is the let. If you declare your properties with var, you'll get the memberwise initializer:
struct Algorithm {
var category: String = ""
var title: String = ""
var fileName: String = ""
}
let alg = Algorithm(category: "", title: "", fileName: "")
But since you supplied default values for all the properties, you don't get the memberwise initializer for let properties. The rule is that you get the implicit memberwise initializer for stored properties without a default value and for var properties (provided you have no explicit declared initializer).
You provided the default values for the properties that's why compiler won't add the default initialiser. When you remove the default values you will get what you are expecting:
struct Algorithm {
let category: String
let title: String
let fileName: String
}
use
struct Algorithm {
var category: String = ""
var title: String = ""
var fileName: String = ""
}
and autocomplete will show you both possibilities