Enum int with string value in swift - swift

I have enum of API endpoints which i use during the base url creation.
one of the API which has pagination is like pagination = "/api/pagination_test/%#?"
Now during pagination i want to pass value to enum initalizer and create an enum which will be accepted by the base-url creation function.
enum APIEndPoints{
case registerUser = "/register"
case login = "/login"
case getData = "/data?"
case pagination = "/api/pagination_test/%#?"
}
func callPaginationAPI(pagenumber: Int){
//make enum with pagenumber, i am stuck in this line.
//let enum =
//call main service method,pass enum as argument.
mainService(endPoint: .pagination)
// this expect an enum of pagination with proper pagenumber
}
func mainService(endpoint: APIEndpoints){
//create base url here
let url = requestUrl()
//do nsurlsession with prepared url
}
func requestUrl( endPoint: APIEndPoints) -> String {
let baseURL = self.requestBaseUrl()
return baseURL + endPoint.rawValue
}
How can i create a pagination enum with value one - am expecting enum as /api/pagination_test/1? , /api/pagination_test/2?

First of all the enum should be update so we can use String(format:)
enum APIEndPoints: String {
case registerUser = "/register"
case login = "/login"
case getData = "/data?"
case pagination = "/api/pagination_test/%d?"
}
To avoid having to pass the page number as a parameter through all methods it is probably better to wrap the enum inside a struct together with the (optional) page number and have a computed property that gets the endpoint as a string
struct EndPoint {
let apiEndPoint: APIEndPoints
let page: Int?
var endPointValue: String {
switch apiEndPoint {
case .pagination:
return String(format: apiEndPoint.rawValue, page ?? 1)
default:
return apiEndPoint.rawValue
}
}
init(_ endPoint: APIEndPoints, page: Int? = nil) {
apiEndPoint = endPoint
self.page = page
}
}
And then pass an instance of this struct instead
func callPaginationAPI(pagenumber: Int){
mainService(endpoint: EndPoint(.pagination, page: pagenumber))
}
And use the computed property when creating the url
func requestUrl(endPoint: EndPoint) -> String {
let baseURL = self.requestBaseUrl()
return baseURL + endPoint.endPointValue
}
And an example to use the struct without a page number
func callLoginAPI() {
mainService(endpoint: EndPoint(.login))
}

Related

Is it possible to have lazy behaviour in enum's function?

Swift provides a very handy lazy var
However, I was wondering, can we achieve similar lazy functionality for Enum's function?
For instance,
class Utils {
static let userDataDirectory = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]
}
enum UserDataDirectory : String {
case Extract = "extract"
case Camera = "camera"
case Mic = "mic"
case Attachment = "attachment"
case Recording = "recording"
case RestoreAttachment = "restore_attachment"
case RestoreRecording = "restore_recording"
func get() -> URL {
return Utils.userDataDirectory.appendingPathComponent(self.rawValue)
}
}
Is it ever possible, to turn enum UserDataDirectory's get function to have lazy evaluation behaviour.
Or, is there a way to avoid evaluation of appendingPathComponent every-time, since Utils.userDataDirectory and self.rawValue is constant?
You just mean that you want a lazily-evaluated static value. That's straightforward; add a static cache:
enum UserDataDirectory : String {
// ...
// Define storage
private static var urls: [Self: URL] = [:]
func url() -> URL {
// Check the cache
if let url = Self.urls[self] {
return url
} else {
// Compute and cache
let url = Utils.userDataDirectory.appendingPathComponent(self.rawValue)
Self.urls[self] = url
return url
}
}
}

Initialising model object without optionals

The model object is populated using JSONDecoder method. Since it is needed to pass it on tableview i need to initialise a local variable of type KBArticle on UI side. Is there any possible method of initialising an object of the KBArticle without optionals or accessing the inner array directly ?
var kbArticle = KBArticle()
Missing argument for parameter 'from' in call. Insert 'from: <#Decoder#>'
/// UI Side
var kbArticle = KBArticle()
override func viewDidLoad() {
super.viewDidLoad()
/// Web services call method
SDKCore.getInstance.getKbService().fetchKbArticles(with: topicID) { (result) in
switch result {
case .success(let kbArticle):
self.kbArticle = kbArticle
DispatchQueue.main.async {
self.tableView.reloadData()
}
case .failed(let e):
print("Error=\(e)")
}
}
}
// Model Class in SDK
public struct KBArticle: Codable {
public let id: Int
public let topic: String
public let articleCount: Int
public let articles: [Article]
}
public struct Article: Codable {
public let id: Int
public let subject: String
}
/// Method in SDK
public func fetchKbArticles(with topicId: Int, completionHandler: #escaping (ResultModel<KBArticle, Error>) -> Void) {
let request = GetKBArticles(topicId: topicId)
Networking.shared.performRequest(request) { (response) in
switch response {
case .success(let response):
do {
let decoder = JSONDecoder()
let result = try decoder.decode(KBArticle.self, from: response.data!)
completionHandler(.success(result))
} catch let error {
completionHandler(.failed(error))
}
case .failed(let error):
completionHandler(.failed(error))
}
}
}
I have to use this articles inside kbArticle struct to provide my tableview datasource. Is is possible to initialise kbArticle object without a primary initialisation using nil values???
If you need new object without optional , you can set a default value for all params
ex :
public struct KBArticle: Codable {
public var id: Int = 0
public var topic: String = ""
public var articleCount: Int = 0
public var articles: [Article]?
}
public struct Article: Codable {
public var id: Int = 0
public var subject: String = ""
}
Use: let myObj = KBArticle()
Please check this Answer

Swift enum associated values

I have API that have path and an int after it.
For example, /get/news/{id}.
For path endpoints i have enum like that:
enum Endpoints : String {
case news = "news"
}
Is there any convinient way to use associated values with it?
Something like :
case newsById(id: String) = "get/news/" + id
You can use this:
enum APIEndpoints {
case news(id: Int)
var path: String {
switch self {
case let .news(id):
return "/get/news/\(id)"
}
}
}
And use it like: APIEndpoints.news(id: 5).path
You can always add a function to the enum to get the URI:
enum Endpoints : String {
case news = "news"
func getUri(id: string) -> String {
return "get/\(self.rawValue)/\(id)"
}
}
you could try this:
enum Endpoints: String {
case news
func getNewsByStringId() -> String {
return "get/news/\(self.rawValue)"
}
}

Constant string with interpolation

I have a string with interpolation like this
let userID = 123
let userProfileUrl = "website.com/user/\(userID)"
I would like make website.com/user/\(userID) a constant but still remain its interpolation, so that I can interpolate it with an userID.
I wonder if anyone knows a good way to do that
You can make userProfileUrl a lazy var. In this case you would need to specify the type of the userProfileUrl (i.e. String) and would need to use self.userID instead of userID
let userID = 123
lazy var userProfileUrl: String = "website.com/user/\(self.userID)"
Or if both properties are constants, and don't depend on an instance of the class you can place them outside of the class definition and it should work:
let userID = 123
let userProfileUrl = "website.com/user/\(userID)"
class MyClass {
}
You can also make userProfileUrl a computed property
let userID = 123
var userProfileUrl: String {
return "website.com/user/\(userID)"
}
If you don't like the extra lines that the computed property adds you could format it so that it's all on one line
let userID = 123
var userProfileUrl: String { return "website.com/user/\(userID)" }
var userProfileURL: (String) -> String = {
return "website.com/user/\($0)"
}
userProfileURL(userID)
This works but I would consider using an enum. You can now create a new case per endpoint.
enum Website {
case UserProfile(Int)
var base: String { return "http://website.com" }
var path: String {
switch self {
case let .UserProfile(userID):
return "user/\(userID)"
}
}
var url: URL { return URL(string: "\(base)/\(path)")! }
}
let userProfileUrl = Website.UserProfile(123).url
This might be a place where you want to use NSString's bridging to String and it's -initWithFormat:
let userProfileUrl = String(format: "website.com/user/%d", userId)

Swift error when trying to access Dictionary: `Could not find member 'subscript'`

This won't compile:
I've tried a couple different things; different ways of declaring the Dictionary, changing its type to match the nested-ness of the data. I also tried explicitly saying my Any was a collection so it could be subscripted. No dice.
import UIKit
import Foundation
class CurrencyManager {
var response = Dictionary<String,Any>()
var symbols = []
struct Static {
static var token : dispatch_once_t = 0
static var instance : CurrencyManager?
}
class var shared: CurrencyManager {
dispatch_once(&Static.token) { Static.instance = CurrencyManager() }
return Static.instance!
}
init(){
assert(Static.instance == nil, "Singleton already initialized!")
getRates()
}
func defaultCurrency() -> String {
let countryCode = NSLocale.currentLocale().objectForKey(NSLocaleCountryCode) as String
let codesToCountries :Dictionary = [ "US":"USD" ]
if let localCurrency = codesToCountries[countryCode]{
return localCurrency
}
return "USD"
}
func updateBadgeCurrency() {
let chanCurr = defaultCurrency()
var currVal :Float = valueForCurrency(chanCurr, exchange: "Coinbase")!
UIApplication.sharedApplication().applicationIconBadgeNumber = Int(currVal)
}
func getRates() {
//Network code here
valueForCurrency("", exchange: "")
}
func valueForCurrency(currency :String, exchange :String) -> Float? {
return response["current_rates"][exchange][currency] as Float
}
}
Let's take a look at
response["current_rates"][exchange][currency]
response is declared as Dictionary<String,Any>(), so after the first subscript you try to call another two subscripts on an object of type Any.
Solution 1. Change the type of response to be a nested dictionary. Note that I added the question marks because anytime you access a dictionary item you get back an optional.
var response = Dictionary<String,Dictionary<String,Dictionary<String, Float>>>()
func valueForCurrency(currency :String, exchange :String) -> Float? {
return response["current_rates"]?[exchange]?[currency]
}
Solution 2. Cast each level to a Dictionary as you parse. Make sure to still check if optional values exist.
var response = Dictionary<String,Any>()
func valueForCurrency(currency :String, exchange :String) -> Float? {
let exchanges = response["current_rates"] as? Dictionary<String,Any>
let currencies = exchanges?[exchange] as? Dictionary<String,Any>
return currencies?[currency] as? Float
}
You can get nested dictionary data by following these steps:
let imageData: NSDictionary = userInfo["picture"]?["data"]? as NSDictionary
let profilePic = imageData["url"] as? String
func valueForCurrency(currency :String, exchange :String) -> Float? {
if let exchanges = response["current_rates"] as? Dictionary<String,Any> {
if let currencies = exchanges[exchange] as? Dictionary<String,Any> {
return currencies[currency] as? Float
}
}
return nil
}
response is declared as such:
var response = Dictionary<String,Any>()
So the compiler thinks response["current_rates"] will return an Any. Which may or may not be something that is subscript indexable.
You should be able to define you type with nested Dictionaries, 3 levels and eventually you get to a float. You also need to drill in with optional chaining since the dictionary may or may not have a value for that key, so it's subscript accessor returns an optional.
var response = Dictionary<String,Dictionary<String,Dictionary<String,Float>>>()
// ... populate dictionaries
println(response["current_rates"]?["a"]?["b"]) // The float