Swift - How to get Raw Value from ENUM - ios

I have the following enum:
enum MembershipLevel: Int {
case basic = 25
case bronze = 50
case silver = 100
case gold = 500
case platinum = 1000
}
which then I have the following firebase lookup:
userRef.child(userId).child("memeberlevel").observeSingleEvent(of: .value, with: { (snapshot) in
self.userRef.child(userId).child("count").observeSingleEvent(of: .value, with: { (snap) in
if((snap.value!) < MembershipLevel.(snapshot.value!).rawValue) {
completion(false)
} else {
completion(true)
}
})
})
The code above throws a complier error due to the following code:
MembershipLevel.(snapshot.value).rawValue
How can I reference the enum values dynamically, since the code snippet MembershipLevel.basic.rawValue is perfectly OK?

You should not use an enum here. Enum cases cannot be referred to dynamically.
While you could do something like this:
enum MembershipLevel: Int, CaseIterable {
case basic = 25
case bronze = 50
case silver = 100
case gold = 500
case platinum = 1000
init?(string: String) {
if let value = MembershipLevel.allCases.first(where: { "\($0)" == string }) {
self = value
} else {
return nil
}
}
}
// usage:
let userValue = snapValue.value as! Int
let membershipString = snapshot.value as! String
if userValue < MembershipLevel(string: membershipString)!.rawValue {
}
It might break in the future as I don't think "\($0)" producing the enum case name is guaranteed.
I would use a dictionary:
let membershipLevelNameDict = [
"basic": 25,
"bronze": 50,
"silver": 100,
"gold": 500,
"platinum": 1000
]
Usage:
let userValue = snapValue.value as! Int
let membershipString = snapshot.value as! String
if userValue < membershipLevelNameDict[membershipString] ?? 0 {
}
Using this dictionary, you can also create an instance of your enum:
membershipLevelNameDict[membershipString].flatMap(MembershipLevel.init(rawValue:))
But if you want to compare the raw values, just access the dictionary directly like in the first snippet.

You can make your enumeration conform to Comparable protocol and create an initializer that takes an Int:
extension MembershipLevel: Comparable {
init?(_ value: Any?) {
switch value {
case let int as Int:
self.init(rawValue: int)
case let string as String:
switch string {
case "basic": self = .basic
case "bronze": self = .bronze
case "silver": self = .silver
case "gold": self = .gold
case "platinum": self = .platinum
default: return nil
}
default: return nil
}
}
static func < (lhs: Self, rhs: Self) -> Bool {
lhs.rawValue < rhs.rawValue
}
}
Now you can initialize your enumeration from the snapshot values and compare them directly
if let lhs = MembershipLevel(snap.value),
let rhs = MembershipLevel(snapshot.value),
lhs < rhs {
completion(false)
}
} else {
completion(true)
}

Related

Cast Any to RawRepresentable where RawValue == String

I am trying to encode data. For the sake of the example I'll use JSON encoding even though there are other solutions for this out there.
I would like to handle enums that are backed by String or an Int as if they were simply String and Int:
struct UnhandledValueError: Error {}
enum Foo: String {
case bar
}
func encode(_ data: Any?) throws -> String {
guard let data = data else {
return "null"
}
if let string = data as? String {
return "\"\(string)\""
}
if let int = data as? Int {
return String(int)
}
// represent String and Int based enums there
if let array = data as? [Any?] {
return try "[\(array.map({ try encode($0) }).joined(separator: ","))]"
}
if let dict = data as? [String: Any?] {
return try "{\(dict.map({ "\"\($0.key)\": \(try encode($0.value))" }).joined(separator: ","))}"
}
throw UnhandledValueError()
}
let value: Any? = ["foo": Foo.bar]
try encode(value)
Because RawRepresentable is a generic protocol I don't think I can do either
if let value = data as? RawRepresentable
or
if let value = data as? RawRepresentable where RawValue == String
How can I access the raw value (as Any?) from an object that might be RawRepresentable ?
You can create a generic method and constrain its type to RawRepresentable where its RawValue conform to LosslessStringConvertible:
func encode<T>(object: T) -> String where T: RawRepresentable, T.RawValue: LosslessStringConvertible {
.init(object.rawValue)
}
Playground testing:
enum IntEnum: Int {
case a = 1, b, c
}
enum StringEnum: String {
case a, b , c
}
let int: IntEnum = .c
let string: StringEnum = .c
encode(object: int) // "3"
encode(object: string) // "c"

Database fetch exists only in its own function

I am trying to fetch all the user names in my database but the nameArray only contains values while its inside that function, how can I fix this?
DataService.instance.getAllUserNamesPlease { (returnedNamesArray) in
self.nameArray = returnedNamesArray
}
for userName in nameArray {
if(userName.lowercased() == name!.lowercased()){
self.userNameTaken = true
self.progressView.progress = Float(progress / self.nameArray.count)
progress += 1/self.nameArray.count
break
}
}
nameArray is empty in this loop
func getAllUserNamesPlease(handler: #escaping (_ userNames: [String]) -> ()){
REF_USERS.observeSingleEvent(of: .value) { (userNameSnapshot) in
guard let userNameSnapshot = userNameSnapshot.children.allObjects as? [DataSnapshot] else {return}
var namesArray = [String]()
for names in userNameSnapshot {
let name = names.childSnapshot(forPath: "userName").value as? String ?? "No Name"
namesArray.append(name)
}
handler(namesArray)
}
}
Any code that needs access to the results of an asynchronous call, should be inside that callback/completion handler. So your loop over nameArray, needs to be inside the {} braces:
DataService.instance.getAllUserNamesPlease { (returnedNamesArray) in
self.nameArray = returnedNamesArray
for userName in nameArray {
if(userName.lowercased() == name!.lowercased()){
self.userNameTaken = true
self.progressView.progress = Float(progress / self.nameArray.count)
progress += 1/self.nameArray.count
break
}
}
}
}

Cast dictionary as object in swift (ios)

I have this function searchMoviesOnJson:
func searchMoviesOnJson(imdbTitle: String, completionHandler: #escaping (Dictionary<String, Any>?) -> ()) {
let urlByName: String = "https://www.omdbapi.com/?s=\(imdbTitle)&type=movie"
//returns a list of movies that contains the title searched
//------------------------------------------------
Alamofire.request(urlByName).responseJSON {
response in
switch response.result {
case .success(let value):
let moviesJSON = value
completionHandler(moviesJSON as? Dictionary<String, Any>)
case .failure(_):
completionHandler(nil)
}
}
//------------------------------------------------
}
That gives me this response from api (e.g.: imdbTitle = "arq"):
{
Response = True;
Search = (
{
Poster = "https://images-na.ssl-images-amazon.com/images/M/MV5BMjAxODQ2MzkyMV5BMl5BanBnXkFtZTgwNjU3MTE5OTE#._V1_SX300.jpg";
Title = ARQ;
Type = movie;
Year = 2016;
imdbID = tt5640450;
},
{
Poster = "N/A";
Title = Arq;
Type = movie;
Year = 2011;
imdbID = tt2141601;
},
{
Poster = "N/A";
Title = "A.R.Q.";
Type = movie;
Year = 2015;
imdbID = tt3829612;
}
);
totalResults = 3;
}
So I created a this class MovieByTitle
class MovieByTitle {
var poster : String?
var title : String?
var year : Int?
var imdbID : String?
init(poster: String?, title: String?, year: Int?, imdbID: String?) {
//validation
if let isPoster = poster { self.poster = isPoster }
else { self.poster = nil }
if let isTitle = title { self.title = isTitle }
else { self.title = nil }
if let isYear = year { self.year = isYear }
else { self.year = nil }
if let isImdbID = imdbID { self.imdbID = isImdbID }
else { self.imdbID = nil }
}
}
And now my doubt, I also create this MovieDAO:
class MovieDao {
func getMovies(imdbTitle: String, completionHandler: #escaping (([MovieByTitle]) -> ())) {
//function that conects to the api
searchMoviesOnJson(imdbTitle: imdbTitle, completionHandler: {
moviesJSON in
//array to keep the attributes received by the dictionary
var moviesArray = [MovieByTitle]()
//searchResults is the response from my request as an array
if let searchResults = moviesJSON?["Search"] as? NSArray{
for searchResult in searchResults {
let movieResult = searchResult as! Dictionary<String,Any>
let movieDetail = MovieByTitle()
movieDetail.poster = movieResult["Poster"] as? String
movieDetail.title = movieResult["Title"] as? String
movieDetail.year = movieResult["Year"] as? Int
movieDetail.imdbID = movieResult["imdbID"] as? String
moviesArray.append(movieDetail)
}
}
})
}
}
But the xcode returns an error in line:
let movieDetail = MovieByTitle()
Error message: missing argument for parameter 'poster' in call (and so on with the others)
What is the right sintax for that? What is the better way to cast my dictionary response as an object?
You MovieByTitle init function requires 4 parameters that are missing.
Solution 1: Add a secondary init:
init() {}
Solution 2: Define existing init parameters as optional by giving them default values:
init(poster: String? = nil, title: String? = nil, year: Int? = nil, imdbID: String? = nil)
Solution 3: Call the existing init with the parameters it needs:
let movieDetail = MovieByTitle(poster: movieResult["Poster"] as? String, title: movieResult["Title"] as? String, year: movieResult["Year"] as? Int, imdbID: movieResult["imdbID"] as? String)
Your init function requires 4 parameters. You haven't included any. Try the following
let poster = movieResult["Poster"] as? String
let title = movieResult["Title"] as? String
let year = movieResult["Year"] as? Int
let imdbID = movieResult["imdbID"] as? String
let movieDetail = MovieByTitle(poster:poster, title:title, year:year, imdbID:imdbDB)

How to get enum from raw value in Swift?

I'm trying to get enum type from raw value:
enum TestEnum: String {
case Name
case Gender
case Birth
var rawValue: String {
switch self {
case .Name: return "Name"
case .Gender: return "Gender"
case .Birth: return "Birth Day"
}
}
}
let name = TestEnum(rawValue: "Name") //Name
let gender = TestEnum(rawValue: "Gender") //Gender
But it seems that rawValue doesn't work for string with spaces:
let birth = TestEnum(rawValue: "Birth Day") //nil
Any suggestions how to get it?
Too complicated, just assign the raw values directly to the cases
enum TestEnum: String {
case Name = "Name"
case Gender = "Gender"
case Birth = "Birth Day"
}
let name = TestEnum(rawValue: "Name")! //Name
let gender = TestEnum(rawValue: "Gender")! //Gender
let birth = TestEnum(rawValue: "Birth Day")! //Birth
If the case name matches the raw value you can even omit it
enum TestEnum: String {
case Name, Gender, Birth = "Birth Day"
}
In Swift 3+ all enum cases are lowercased
Full working example:
enum TestEnum: String {
case name = "A Name"
case otherName
case test = "Test"
}
let first: TestEnum? = TestEnum(rawValue: "A Name")
let second: TestEnum? = TestEnum(rawValue: "OtherName")
let third: TestEnum? = TestEnum(rawValue: "Test")
print("\(first), \(second), \(third)")
All of those will work, but when initializing using a raw value it will be an optional. If this is a problem you could create an initializer or constructor for the enum to try and handle this, adding a none case and returning it if the enum couldn't be created. Something like this:
static func create(rawValue:String) -> TestEnum {
if let testVal = TestEnum(rawValue: rawValue) {
return testVal
}
else{
return .none
}
}
With Swift 4.2 and CaseIterable protocol it is not that hard at all!
Here is an example of how to implement it.
import UIKit
private enum DataType: String, CaseIterable {
case someDataOne = "an_awesome_string_one"
case someDataTwo = "an_awesome_string_two"
case someDataThree = "an_awesome_string_three"
case someDataFour = "an_awesome_string_four"
func localizedString() -> String {
// Internal operation
// I have a String extension which returns its localized version
return self.rawValue.localized
}
static func fromLocalizedString(localizedString: String) -> DataType? {
for type in DataType.allCases {
if type.localizedString() == localizedString {
return type
}
}
return nil
}
}
// USAGE EXAMPLE
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let dataType = DataType.fromLocalizedString(localizedString: self.title) {
loadUserData(type: dataType)
}
}
You can easily modify it to return the DataType based on the rawValue. I hope it helps!
You can define enum like this -
enum TestEnum: String {
case Name, Gender, Birth
}
OR
enum TestEnum: String {
case Name
case Gender
case Birth
}
you can provide an init method which defaults to one of the member values.
enum TestEnum: String {
case Name, Gender, Birth
init() {
self = .Gender
}
}
In the example above, TestEnum.Name has an implicit raw value of "Name", and so on.
You access the raw value of an enumeration case with its rawValue property:
let testEnum = TestEnum.Name.rawValue
// testEnum is "Name"
let testEnum1 = TestEnum()
// testEnum1 is "Gender"
Display the rawvalue using Enum
import UIKit
enum car: String {
case bmw = "BMW"
case jaquar = "JAQUAR"
case rd = "RD"
case benz = "BENZ"
}
class ViewController: UIViewController {
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
label.text = car.bmw.rawValue
}
}
Here is example of more useable code in swift 4.1
import UIKit
enum FormData {
case userName
case password
static let array = [userName, password]
var placeHolder: String {
switch self {
case .userName:
return AppString.name.localized // will return "Name" string
case .password:
return AppString.password.localized // will return "Password" string
}
}
}
enum AppString: String {
case name = "Name"
case password = "Password"
var localized: String {
return NSLocalizedString(self.rawValue, comment: "")
}
}
I think this is a quick and clean solution for swift 4.2 (you can c&p to playground)
import UIKit
public enum SomeEnum: String, CaseIterable {
case sun,moon,venus,pluto
}
let str = "venus"
let newEnum = SomeEnum.allCases.filter{$0.rawValue == str}.first
// newEnum is optional
if let result = newEnum {
print(result.rawValue)
}
enum withdrawBalceTimeGenrateError : String , Error{
case insufficientBalance = "Plz Check Balance"
}
withdrawBalceTimeGenrateError.insufficientBalance.rawValue // Plz Check Balance

Swift JSON with Alamofire - Unexpectedly found nil while unwrapping an Optional value

I'm trying to parse a JSON in my app and it's not working.
Here is a look at the JSON I'm trying to parse:
Check out the following screenshots. I get the error "Unexpectedly found nil while unwrapping an optional value" on the line
editorialElement.title = nodeElement!.valueForKey("title") as! String
Can anyone help me properly parse this JSON ?
EDIT 1: Here is more code. It shows what class I'm using (i.e. what objects I am creating based on the JSON file). I imagine this is what you meant by where I initialize JSON objects.
Here is my router as well (build using Alamofire). I feel like something might be wrong with it but at the same time, it makes a lot of sense and I don't know what missing:
EDIT 2: Here is the actual code:
func populateEditorials() {
if populatingEditorials {
return
}
populatingEditorials = true
Alamofire.request(GWNetworking.Router.Editorials(self.currentPage)).responseJSON() { response in
if let JSON = response.result.value {
/*
if response.result.error == nil {
*/
/* Creating objects for every single editorial is long running work, so we put that work on a background queue, to keep the app very responsive. */
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
/*
// Making an array of all the node IDs from the JSON file
let nodeIDArray : [String]
var nodeCounter : Int = 0 */
for node in JSON as! NSDictionary {
var nodeElement = JSON.valueForKey(String(node))
self.nodeIDArray.addObject(String(node.key))
var editorialElement : EditorialElement = EditorialElement(title: "init", nodeID: 0, timeStamp: 0, imageURL: "init", author: "init", issueNumber: "init", volumeNumber: "init", articleContent: "init")
editorialElement.title = nodeElement!.valueForKey("title") as! String
editorialElement.nodeID = nodeElement!.valueForKey("nid") as! Int
editorialElement.timeStamp = nodeElement!.valueForKey("revision_timestamp") as! Int
editorialElement.imageURL = nodeElement!.valueForKey("image_url") as! String
editorialElement.author = nodeElement!.valueForKey("author") as! String
editorialElement.issueNumber = nodeElement!.valueForKey("issue_int") as! String
editorialElement.volumeNumber = nodeElement!.valueForKey("volume_int") as! String
editorialElement.articleContent = nodeElement!.valueForKey("html_content") as! String
self.editorialObjects.addObject(editorialElement)
/*
nodeIDArray[nodeCounter] = jsonValue{nodeCounter}.string
let editorialInfos : EditorialElement = ((jsonValue as! NSDictionary).valueForKey("\(nodeIDArray[nodeCounter])") as! [NSDictionary]).map { EditorialElement(title: $0["title"] as! String, nodeID: $0["nid"] as! Int, timeStamp: $0["revision_timestamp"] as! Int, imageURL: $0["image_url"] as! String, author: $0["author"], issueNumber: $0["issue_int"] as! Int, volumeNumber: $0["volume_int"] as! Int, articleContent: $0["html_content"] as! String) /* I am going to try to break this line down to simplify it and fix the build errors */
*/
}
print(self.editorialObjects)
}
/* Sorting the elements in order of newest to oldest (as the array index increases) */
self.editorialObjects.sort({ $0.timeStamp > $1.timeStamp })
let lastItem = self.editorialObjects.count
let indexPaths = (lastItem..<self.editorialObjects.count).map { NSIndexPath(forItem: $0, inSection: $0) }
dispatch_async(dispatch_get_main_queue()) {
self.editorialsTableView.insertRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.Automatic) // Animation implemented for testing, to be removed for version 1.0
}
self.currentPage++
/* } */
}
self.populatingEditorials = false
}
}
Here is the code for my Class and Router:
class EditorialElement: NSObject {
var title: String // title
var nodeID: Int // nid
var timeStamp: Int // revision_timestamp
var imageURL: String? // image_url
var author: String // author
var issueNumber: String // issue_int
var volumeNumber: String // volume_int
var articleContent: String // html_content
/* To get an NSDate objec from Unix timestamp
var date = NSDate(timeIntervalSince1970: timeStamp) */
init(title: String, nodeID: Int, timeStamp: Int, imageURL: String, author: String, issueNumber: String, volumeNumber: String, articleContent: String) {
self.title = title
self.nodeID = nodeID
self.timeStamp = timeStamp
self.imageURL = imageURL
self.author = author
self.issueNumber = issueNumber
self.volumeNumber = volumeNumber
self.articleContent = articleContent
}
override func isEqual(object: AnyObject!) -> Bool {
return (object as! EditorialElement).nodeID == self.nodeID
}
override var hash: Int {
return (self as EditorialElement).nodeID
}
}
enum Router : URLRequestConvertible {
static let baseURLString = MyGlobalVariables.baseURL
case Issue
case Editorials(Int)
case News(Int)
case Random(Int)
case Pictures(Int)
var URLRequest: NSMutableURLRequest {
let path : String
let parameters: [String: AnyObject]
(path) = {
switch self {
case .Issue:
return ("/issue")
case .Editorials (let editorialsSection): /* If section == 0, this will return the first ten editorials. If section == 1, then section * 10 = 10, and we will get the ten editorials after that. */
return ("/list/editorials/\(editorialsSection * 10)")
case .News (let newsSection):
return ("/list/news/\(newsSection * 10)")
case .Random (let randomSection):
return ("/list/random/\(randomSection * 10)")
case .Pictures (let page):
return ("/list/pictures/\(page)")
}
}()
let URL = NSURL(string: Router.baseURLString)
let GoldenWordsURLRequest = NSMutableURLRequest(URL: URL!.URLByAppendingPathComponent(path))
/* let encoding = Alamofire.ParameterEncoding.URL */
return GoldenWordsURLRequest
/* return encoding.encode(URLRequest, parameters: parameters).0 */
}
}
}
That's because you take an optional reference (editorialElement returned from a failable initializer) and call valueForKey("title") on it. It stumbles over the access to "title" because it goes first in you code while the target of the call is nil. I would recommend organizing your code as follows:
if let editorialElement = EditorialElement(title:..., nodeID: and such)
{
... assigning new values to the properties
}
and you will notice that you don't enter the if-let scope. You will avoid the crash and make sure the problem is inside the arguments you initialize the editorialElement with.

Resources