How do I condense unwrapping multiple optionals in Swift? - swift

I want to unwrap these 6 optional variables, and if they are null i want to give them a blank String value. This is so I can send these variables packaged into a parameters array that's sent to an API.
I'm still a beginner at Swift, and this is the only easiest way I have understood how to implement this, but the inner coder in me is saying this looks redundant and crappy as ****.
Can someone help me condense this or make it simpler?
if let fbEmail = self.fbEmail {
}else{
self.fbEmail = ""
}
if let fbDob = self.fbDob {
}else{
self.fbDob = ""
}
if let fbGender = self.fbGender {
}else{
self.fbGender = ""
}
if let fbUserIp = self.fbUserIp {
}else{
self.fbUserIp = ""
}
if let fbFacebookId = self.fbFacebookId {
}else{
self.fbFacebookId = ""
}
if let fbFacebookAccessToken = self.fbFacebookAccessToken {
}else{
self.fbFacebookAccessToken = ""
}

You can do that in exactly 6 lines of code:
self.fbEmail = self.fbEmail ?? ""
self.fbDob = self.fbDob ?? ""
self.fbGender = self.fbGender ?? ""
self.fbUserIp = self.fbUserIp ?? ""
self.fbFacebookId = self.fbFacebookId ?? ""
self.fbFacebookAccessToken = self.fbFacebookAccessToken ?? ""
Edit: what's up with the ?? syntax: It's a shortcut "if nil assign another value":
let c = a ?? b
will assign c = a if a != nil, otherwise c = b.

You can unwrap more than one at a time. But if you do, you will have no way of knowing which one is nil. You will only know that either some are nil or none are.
Aside from that do they all need to be optional? Can't you init them with the default values you are giving them when they are nil?
Snippets:
Just avoid the issue altogether.
// just init with a default value without them being an optional
var fbEmail : String = ""
var fbDob : String = ""
Replace checking for nil with .isEmpty
var string : String = ""
string.isEmpty // returns true
string = "SomeString"
string.isEmpty // returns false
Optionals with starting values.
// init wit default values while still optional
var fbEmail : String? = ""
var fbDob : String? = ""
Unwrap more than one at a time.
if let unwrfbEmail = fbEmail, let unwrfbDob = fbDob {
// stuff available here
} else {
// handle the error
}
guard let unwrfbEmail = fbEmail, let unwrfbDob = fbDob else {
// put return/break here and handle the error
}
// stuff available here
A container for all values. They are optionals but when set to nil they will reset to a default value. You can also declare them as forced unwrapped optionals !. Or unwrap them all at once with a method found above.
Obviously copy paste and alter the didSet to all variables.
// container stuct that uses didSet to avoid nil and reset to default values
struct container {
var fbEmail : String? = "" {
didSet {
if fbEmail == nil {
fbEmail = ""
}
}
}
var fbDob : String? = ""
var fbGender : String? = ""
var fbUserIp : String? = ""
var fbFacebookId : String? = ""
var fbFacebookAccessToken : String? = ""
}

Related

How to change only the difference Concisely in FireStore

When updating FirebaseStorage, I make processing to change only by change.
Instead of sending the value of textField as it is, we create an optional variable called newOO separately, so that the value entered in newOO is changed, and nil is entered if it is not.
Here is the process of adding to the dictionary when there is a change in that variable (if it is not nil) and not adding it to the dictionary if there is no change (nil).
var newName: String? = "name"
var newAge: Int? = 20
var newID: String? = "123456789"
var dict = [String: Any]()
if let newName = newName {
dict["newName"] = newName
}
if let newAge = newAge {
dict["newAge"] = newAge
}
if let newID = newID {
dict["newID"] = newID
}
However, the more items there are, the more descriptions there are.
Is there a way to write this process more concisely?

How do I set NSUserDefault settings as Instance Variables?

When I try to just set a constant based on the settings like below, it results in Optional("value").
let accesstoken = NSUserDefaults.standardUserDefaults().stringForKey("accessToken")
let userId = NSUserDefaults.standardUserDefaults().stringForKey("userId")
If I do it like the below, I get an error saying variable used within its own initial value. I can't seem to win here. What am I doing wrong?
var accesstoken = String()
var userId = Int()
if let atString = NSUserDefaults.standardUserDefaults().stringForKey("accessToken") {
accesstoken = atString
}
if let userIdString = NSUserDefaults.standardUserDefaults().stringForKey("userId") {
userId = userIdString
}
You can achieve what you want with a read only computed property combined with the nil coalescing operator "??". Try like this:
var accessToken: String {
return NSUserDefaults().stringForKey("accessToken") ?? ""
}
var userId: String {
return NSUserDefaults().stringForKey("userId") ?? ""
}
or if you need an Int for your userID
var userId: Int {
return NSUserDefaults().integerForKey("userId")
}

Dictionary of swift classes with strings

I have this class Identity and a dictionary of instances of them with Strings as keys. I want to access one of the instances by a string and change some of its properties. I'm trying to use a switch statement to access the instances in the dictionary depending on the value of a string.
class Identity {
let provider: String
let uid: String?
let token: String?
let name: String?
init(provider: String){
self.provider = provider
self.uid = nil
self.token = nil
self.name = nil
}
}
var identities = [String:Identity]()
identities["twitter"] = Identity(provider: "twitter")
identities["twitter"].uid = "131241241241"
identities["twitter"].name = "#freedrull"
let provider: String = "twitter"
var i: Identity? {
switch provider {
case "twitter":
return identities["twitter"] as Identity?
case "facebook":
return identities["facebook"] as Identity?
case "soundcloud":
return identities["soundcloud"] as Identity?
default:
return nil
}
}
if i != nil {
i.name = "tony"
}
I get an error about assigning i.name to "tony". Do I need to cast i to an Identity somehow? I thought it already was.
You have declared i as an Optional:
var i: Identity? // ...
So it's still an Optional. It's not an Identity. It's an Optional wrapping an Identity. But you can't do anything to an Optional - until you unwrap it. Unwrap it, to get at the Identity. You have:
if i != nil {
i.name = "tony"
}
Instead:
if let i = i {
i.name = "tony"
}
Or:
if i != nil {
i!.name = "tony"
}
Both are ways of unwrapping the Optional.
Or, test and unwrap all in one move:
i?.name = "tony"
Then you'll have a new problem; you have declared name as a constant. You can't change a constant! You have:
let name: String?
Instead:
var name: String?
[By the way, much of this code is redundant:
init(provider: String){
self.provider = provider
self.uid = nil
self.token = nil
self.name = nil
}
uid, token, and name are all Optionals, so they are already nil. You can cut those three lines.]

'self' used before super.init call

I'm new to swift and I don't understand how to initialize a class.
Succeeded is initialized in the class definition as false
if (succeeded && (time>1000)){
errormessage += ";connection slow"
}
Time is initialized as
time = data[3].toInt()
Where data is
var data = split(raw_data) {$0 == ","}
And raw_data is a string.
Class Definition:
class geocodeObject: NSObject {
init definition:
init(lat: String,long:String,userstate:String) {
(no super init of any kind)
Full code with things cut way:
class geocodeObject: NSObject {
//A type to store the data from the Reverse Geocoding API
//Not a retriever
//Options
let API_KEY_TEXAS = "9e4797c018164fdcb9a95edf3b10ccfc"
let DEV_MODE = true
//Loading status
var succeeded = false
var errormessage = "Not Initalized" //Not nesscarilly a failure, could be slow connection
var loadstate: String?
//Most important info
var street: String?; var housenumber: String?; var city: String?; var zip: String?
//Metadata
var time: IntegerLiteralType?; var statuscode: String?; var queryid: String?; var zip4: String?
//Other geographical data
var entirestreet: String?; var state: String?
init(lat: String,long:String,userstate:String) {
//userstate: State provided by user
//state: State provided by Reverse Geocoder
var url: String?
var extra: String?
if DEV_MODE{
extra = "&notStore=true"
}
else{
extra = ""
}
url = "http://geoservices.tamu.edu/Services/ReverseGeocoding/WebService/v04_01/HTTP/default.aspx?lat="+lat+"&lon="+long+"&apikey="+API_KEY_TEXAS+"&version=4.01"
if (userstate == "nil"){
url = url! + extra!
println("if")
}
else{
url = url! + "&state="+state!+extra!
println("else")
}
let raw_data = retrieveurl(url!)
var data = split(raw_data) {$0 == ","}
//data[1] is API version used.
statuscode = data[0]; queryid = data[2]; time = data[3].toInt(); entirestreet = data[4]; city = data[5]
state = data[6]; zip = data[7]; zip4 = data[8]
//Do street, housenumber, errormessage, succeeded
if (state != userstate){
println("user state not equal to state")
}
var splittedstreet = split(entirestreet!){$0 == " "}
housenumber = splittedstreet[0]
street = splittedstreet[1]
println(statuscode)
//Error message handling
switch String(statuscode!){
case "200":
errormessage = "Success"
case "400":
errormessage = "Unknown API key error"
case "401":
...
//Time handling
if (succeeded && (time>1000)){
errormessage += ";connection slow"
}
}
println("/GeocodingAPIWrapper.swift/.geocodeObject.init: Not Implemented")
}
}
It had been a while but the answer I found is that you should add super.init() as the first line inside your init block
init(lat: String,long:String,userstate:String) {
super.init()
...
This way I got rid of it and fulfills what the error is asking for.
As I understand this is that your variables are initialized during NSObject.init() so you can use assigned values inside your custom init(_) block
Swift 2.2 (still beta as of writing) currently displays this error if you accidentally forget to return nil from a guard's else:
required init?(dictionary: [String: AnyObject]) {
guard let someValue = dictionary["someValue"] as? Bool else { return /*nil*/ } //Nil should not be commented here
self.someValue = someValue
super.init(dictionary: dictionary) //`self` used before super.init call
}
Hopefully this helps someone

swift How to cast from Int? to String

In Swift, i cant cast Int to String by:
var iString:Int = 100
var strString = String(iString)
But my variable in Int? , there for error: Cant invoke 'init' with type '#Ivalue Int?'
Example
let myString : String = "42"
let x : Int? = myString.toInt()
if (x != null) {
// Successfully converted String to Int
//And how do can i convert x to string???
}
You can use string interpolation.
let x = 100
let str = "\(x)"
if x is an optional you can use optional binding
var str = ""
if let v = x {
str = "\(v)"
}
println(str)
if you are sure that x will never be nil, you can do a forced unwrapping on an optional value.
var str = "\(x!)"
In a single statement you can try this
let str = x != nil ? "\(x!)" : ""
Based on #RealMae's comment, you can further shorten this code using the nil coalescing operator (??)
let str = x ?? ""
I like to create small extensions for this:
extension Int {
var stringValue:String {
return "\(self)"
}
}
This makes it possible to call optional ints, without having to unwrap and think about nil values:
var string = optionalInt?.stringValue
If you need a one-liner it can be achieved by:
let x: Int? = 10
x.flatMap { String($0) } // produces "10"
let y: Int? = nil
y.flatMap { String($0) } // produces nil
if you need a default value, you can simply go with
(y.flatMap { String($0) }) ?? ""
EDIT:
Even better without curly brackets:
y.flatMap(String.init)
Apple's flatMap(_:) Documentation
Optional Int -> Optional String:
If x: Int? (or Double? - doesn't matter)
var s = x.map({String($0)})
This will return String?
To get a String you can use :
var t = s ?? ""
Hope this helps
var a = 50
var str = String(describing: a)
Crude perhaps, but you could just do:
let int100 = 100
println(int100.description) //Prints 100
Sonrobby, I believe that "Int?" means an optional int. Basically, by my understanding, needs to be unwrapped.
So doing the following works fine:
let y: Int? = 42
let c = String(y!)
That "!" unwraps the variable. Hope this helps!
As rakeshbs mentioned, make sure the variable won't be nill.
You need to "unwrap" your optional in order to get to the real value inside of it as described here. You unwrap an option with "!". So, in your example, the code would be:
let myString : String = "42"
let x : Int? = myString.toInt()
if (x != null) {
// Successfully converted String to Int
// Convert x (an optional) to string by unwrapping
let myNewString = String(x!)
}
Or within that conditional, you could use string interpolation:
let myNewString = "\(x!)" // does the same thing as String(x!)
For preventing unsafe optional unwraps I use it like below as suggested by #AntiStrike12,
if let theString = someVariableThatIsAnInt {
theStringValue = String(theString!))
}
Swift 3:
var iString:Int = 100
var strString = String(iString)
extension String {
init(_ value:Int){/*Brings back String() casting which was removed in swift 3*/
self.init(describing:value)
}
}
This avoids littering your code with the verbose: String(describing:iString)
Bonus: Add similar init methods for commonly used types such as: Bool, CGFloat etc.
You can try this to convert Int? to string
let myString : String = "42"
let x : Int? = myString.toInt()
let newString = "\(x ?? 0)"
print(newString) // if x is nil then optional value will be "0"
If you want an empty string if it not set (nil)
extension Int? {
var stringValue:String {
return self == nil ? "" : "\(self!)"
}
}