I am trying to form an URL dynamically.
where I am using Struct to construct it form resultant url
Constants.Swift
import Foundation
private struct Domains {
static let Local = "localhost IP"
static let QA = "QA.Environment"
static let UAT = "http://test-UAT.com"
}
// HardCoded URLRoutes
private struct URLRoutes{
static let query = "query=bitcoin&"
static let date = "date=2019-04-04&"
static let sortBy = "sortBy=Date&"
}
private static let constructionURL = Domain.local+URLRoutes.query + URLRoutes.date + URLRoutes.sortBy + API.Key
static var ResultantURL: String {
return constructionURL
}
I am trying to make this dynamic to pass value from other class to form the dynamic url.
private struct URLRoutes{
var query : String
var date : String?
var sortBy : String?
}
From another Class trying to access the resultant URL
url = URL(string: URLRoutes.ResultantURL)!
but how can I construct the formate of url from the another class?
static let query = "query=bitcoin&"
static let date = "date=2019-04-04&"
static let sortBy = "sortBy=Date&"
Your inputs will guide me.
Here's playground code that does what you want:
struct API {
static let Key = "ABC123"
}
struct URLRoutes{
var query : String
var date : String?
var sortBy : String?
var constructionURL: String {
return query + (date ?? "") + (sortBy ?? "") + API.Key
}
}
let query = "query=bitcoin&"
let date = "date=2019-04-04&"
let sortBy = "sortBy=Date&"
let myRoute = URLRoutes(query: query, date: date, sortBy: sortBy)
print(myRoute.constructionURL)
However, this isn't really ideal and doesn't use the constructs that Apple provides. Here's another approach:
struct URLRoute {
var queryItems:[URLQueryItem]
init(query: String, date:String?, sortBy:String?) {
queryItems = [
URLQueryItem(name: "query", value: query),
URLQueryItem(name: "date", value: date),
URLQueryItem(name: "sortBy", value: sortBy),
URLQueryItem(name: "api_key", value: API.Key)
]
}
var constructionURL:String {
get {
var component = URLComponents(string: "")
component?.queryItems = queryItems
return component?.string ?? ""
}
}
}
let betterRoute = URLRoute(query: "bitcoin", date: "2019-04-04", sortBy: "Date")
print(betterRoute.constructionURL)
You can use URLComponents to do lots of heavy lifting for you in creating valid URLs.
Related
I have various types of Worksheet structs that I use to populate a view, where the user fills out entries and these are then saved.
So far I've just used each struct individually, but I realized it would be more efficient to have a main 'Worksheet' struct that holds the common properties (id, date) so that I can reuse some views rather than creating a different view for every single type of Worksheet.
For example, here is how I read data of a certain worksheet:
if let diaryArray = vm.readData(of: [Diary](), from: "diary_data") {
let sortedArray = diaryArray.sorted(by: {$0.date > $1.date})
vm.diaries = sortedArray
}
I'd like this to be something like
if let diaryArray = vm.readData(of: [T](), from: "\(path)_data") {
let sortedArray = diaryArray.sorted(by: {$0.date > $1.date})
vm.worksheets = sortedArray
}
So, I won't know what type of worksheet it is yet, but that it is a worksheet that has a date property for sure, so I can sort it by that, with this approach I could just reuse this view and function instead of having to write this for every type.
I've tried two ways so far to achieve this with my structs:
firstly, using protocols:
protocol Worksheet: Codable, Identifiable, Hashable {
var id: String { get set }
var date: Date { get set }
}
extension Worksheet {
static func parseVehicleFields(id: String, date: Date) -> (String, Date) {
let id = ""
let date = Date()
return (id, date)
}
}
struct Diary: Worksheet {
var id: String
var date: Date
var title: String
var situation: String
var thoughts: String
var emotions: String
var physicalSensations: String
var behaviours: String
var analysis: String
var alternative: String
var outcome: String
init() {
self.id = ""
self.date = Date()
self.title = ""
self.situation = ""
self.thoughts = ""
self.emotions = ""
self.physicalSensations = ""
self.behaviours = ""
self.analysis = ""
self.alternative = ""
self.outcome = ""
}
}
struct Goal: Worksheet {
var mainGoal: String
var notificationActive: Bool
var notificationDate: Date
var obstacles: String
var positiveBehaviours: String
var immediateActions: String
var goalAchieved: Bool
init() {
self.mainGoal = ""
self.notificationActive = false
self.notificationDate = Date()
self.obstacles = ""
self.positiveBehaviours = ""
self.immediateActions = ""
self.goalAchieved = false
}
}
It seems close, but I get the error of 'Type 'any Worksheet' cannot conform to 'Decodable''
when trying this:
if let array = vm.readData(of: [any Worksheet](), from: "\(path)_data") {
let sortedArray = array.sorted(by: {$0.date > $1.date})
vm.worksheets = sortedArray
}
Here is the save function for reference:
func saveWorksheetProtocol<T: Worksheet>(for type: [T], to path: String) {
let directoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
let documentURL = (directoryURL?.appendingPathComponent(path).appendingPathExtension("json"))!
let jsonEncoder = JSONEncoder()
let data = try? jsonEncoder.encode(type)
do {
try data?.write(to: documentURL, options: .noFileProtection)
} catch {
print("Error...Cannot save data!!!See error: \(error.localizedDescription)")
}
}
It works if I use Diary as the parameter, but that defeats the point of trying to make this Generic to Worksheets, not their specific types.
Alternatively, I tried a method that creates a major Struct type of Worksheet, that contains the other types. This actually works nicely, except I have to hold empty data for EVERY type of worksheet, if I want to create an array of only 'Diary' entries for example, and this seems redundant and inefficient:
struct Worksheet: Codable, Identifiable, Hashable {
var id: String
var date: Date
var diary: Diary2
var goal: Goal2
var experiment: Experiment2
init() {
self.id = ""
self.date = Date()
self.diary = Diary2()
self.goal = Goal2()
self.experiment = Experiment2()
}
}
struct Diary: Codable, Hashable {
var title: String
var situation: String
var thoughts: String
var emotions: String
var physicalSensations: String
var behaviours: String
var analysis: String
var alternative: String
var outcome: String
init() {
self.title = ""
self.situation = ""
self.thoughts = ""
self.emotions = ""
self.physicalSensations = ""
self.behaviours = ""
self.analysis = ""
self.alternative = ""
self.outcome = ""
}
}
struct Goal: Codable, Hashable {
var mainGoal: String
var notificationActive: Bool
var notificationDate: Date
var obstacles: String
var positiveBehaviours: String
var immediateActions: String
var goalAchieved: Bool
init() {
self.mainGoal = ""
self.notificationActive = false
self.notificationDate = Date()
self.obstacles = ""
self.positiveBehaviours = ""
self.immediateActions = ""
self.goalAchieved = false
}
}
So here I can just find a specific 'Worksheet', sort it, and then later access the values of whichever one it is, i.e. worksheet.diary.title, worksheet.goal.title etc. But this means each entry has redundant values of every other, and I tried making each type optional, but I could not then use them with bindings, as I was getting unresolvable unwrap errors, if the type i.e. 'Diary?' was optional in the Worksheet struct:
TextField("", text: $entry.diary!.title, axis: .vertical) <- error would be here, even when checking if diary exist first.
.lineLimit(20)
.disabled(!editMode)
So I am looking for a way to handle my Worksheet types more efficiently, so that I can reduce repetitive code for functions and views where possible.
I have struct like this:
struct Request {
var page: Int
var name: String?
var favoriteName: String?
var favoriteId: Int?
}
Then I convert it to Dictionary
func toDict() -> [String:Any] {
var dict = [String:Any]()
let otherSelf = Mirror(reflecting: self)
for child in otherSelf.children {
if let key = child.label {
dict[key] = child.value
}
}
return dict
}
If I loop it to modify and concatenate using those Dict key & value to create query string:
var queryString: String {
var output: String = ""
for (key,value) in toDict() {
output += "\(key)" + "=\(value)&"
}
output = String(output.dropLast())
return output
}
Does anyone know how to prevent nil, Optional(""), Optional(25) string added in the concatenation process ?
current result: page=20&name=nil&favoriteName=Optional("")&favoriteId=Optional(25)
expected result: page=20&name=&favoriteName=&favoriteId=25
Edit: Thank you everyone , wasn't really expecting so much answer tho. Let me edit the title to help future developers search.
The best practice is to use the URLComponents and URLQueryItem structs.
This is my approach to solving your problem.
First I added an enum to avoid having hardcoded strings.
struct Request {
var page: Int
var name: String?
var favoriteName: String?
var favoriteId: Int?
}
enum RequestValues: String {
case page
case name
case favoriteName
case favoriteId
}
Then I made this helper function to return the non nil vars from the Request instance as an array of URLQueryItem.
func createQuery(request: Request) -> [URLQueryItem] {
var queryItems: [URLQueryItem] = []
queryItems.append(URLQueryItem(name: RequestValues.page.rawValue, value: "\(request.page)"))
if let name = request.name {
queryItems.append(URLQueryItem(name: RequestValues.name.rawValue, value: name))
}
if let favoriteName = request.favoriteName {
queryItems.append(URLQueryItem(name: RequestValues.favoriteName.rawValue, value: favoriteName))
}
if let favoriteId = request.favoriteId {
queryItems.append(URLQueryItem(name: RequestValues.favoriteId.rawValue, value: "\(favoriteId)"))
}
return queryItems
}
Then you can get the query string like this:
let queryString = queryItems.compactMap({ element -> String in
guard let value = element.value else {
return ""
}
let queryElement = "\(element.name)=\(value)"
return queryElement
})
this will give you the expected result in your question.
page=20&name=&favoriteName=&favoriteId=25
But you should use the URLComponents struct to build your url as such.
func buildURL() -> URL? {
var urlComponents = URLComponents()
urlComponents.scheme = "https"
urlComponents.host = "google.com"
urlComponents.queryItems = queryItems
urlComponents.path = "/api/example"
urlComponents.url
guard let url = urlComponents.url else {
print("Could not build url")
return nil
}
return url
}
This would will give you the url with the query.
It would look like this :
https://google.com/api/example?page=5&name=nil&favoriteName=Hello&favoriteId=9
In model have optioanl var.
First, make your dictionary value optional
func toDict() -> [String: Any?] {
and then use the default value
var queryString: String {
var output: String = ""
for (key,value) in toDict() {
output += "\(key)" + "=\(value ?? "")&" // <== Here
}
output = String(output.dropLast())
return output
}
You can also prevent nil value in string
var queryString: String {
var output: String = ""
for (key,value) in toDict() where value != nil { //< Here
output += "\(key)" + "=\(value ?? "")&" //< Here
}
output = String(output.dropLast())
return output
}
Check if value is nil before insert it in the output, if the value is nil you can provide a default value:
var queryString: String {
var output: String = ""
for (key,value) in toDict() {
output += "\(key)"
if let value = value{
output += "=\(value)&"
}else{
output += "=DefaultValue&" // Replace DefaultValue with what is good for you
}
}
output = String(output.dropLast())
return output
}
This is more concise:
var queryString: String {
var output: String = ""
for (key,value) in toDict() {
output += "\(key)" + "=\(value ?? "DefaultValue")&"
}
output = String(output.dropLast())
return output
}
Instead of manually creating your string you should use URLComponents to generate your URL and use URLQueryItems for your parameters. I would make your structure conform to Encodable and encode NSNull in case the value is nil. Then you just need to pass an empty string to your query item when its value is NSNull:
This will force encoding nil values:
extension Request: Codable {
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(page, forKey: .page)
try container.encode(name, forKey: .name)
try container.encode(favoriteName, forKey: .favoriteName)
try container.encode(favoriteId, forKey: .favoriteId)
}
}
let request = Request(page: 20, name: nil, favoriteName: nil, favoriteId: 25)
do {
let dictiony = (try JSONSerialization.jsonObject(with: JSONEncoder().encode(request))) as? [String: Any] ?? [:]
let queryItems: [URLQueryItem] = dictiony.reduce(into: []) {
$0.append(.init(name: $1.key, value: $1.value is NSNull ? "" : String(describing: $1.value)))
}
print(queryItems)
var components = URLComponents()
components.scheme = "https"
components.host = "www.google.com"
components.path = "/search"
components.queryItems = queryItems
if let url = components.url {
print(url)
}
} catch {
print(error)
}
This will print
[name=, favoriteName=, page=20, favoriteId=25]
https://www.google.com/search?favoriteName=&name=&page=20&favoriteId=25
I am trying to build a URL query as something similar
limit=20&offset=0
if there is no limit then URL query should look like
offset=0
I tried to form the string as follows, but my struct items are optionals; therefore, I am confused to come up with a string.
struct Filter {
var limit: Int?
var offset: Int?
}
extension Filter:CustomStringConvertible {
var description: String {
return "limit=\(limit)&offset=\(offset)"
}
}
If you really want to stick to the String version, here is my take:
extension Filter: CustomStringConvertible {
var description: String {
[
limit.map { "limit=\($0)" },
offset.map { "offset=\($0)" }
].compactMap { $0 }.joined(separator: "&")
}
}
But as there already are comments that guide you toward the good solution, I figured I might provide you with an example. Let's start with building query items for the Filter object.
extension Filter {
var queryItems: [URLQueryItem] {
var items = [URLQueryItem]()
if let limit = limit {
items.append(URLQueryItem(name: "limit", value: limit))
}
if let offset = offset {
items.append(URLQueryItem(name: "offset", value: offset))
}
return items
}
}
and now its super easy to build URL using URLComponents
let filter: Filter
var components = URLComponents()
components.scheme = "https" // example scheme
components.host = "api.github.com" // example url
components.path = "/search/repositories" // example path
components.queryItems = filter.queryItems
let url = components.url
John Sundell has a great article on Constructing URLs in Swift
I have a struct that looks something like this:
internal class RemoteProfileModel: Decodable {
let userId: String
let company: String
let email: String
let firstName: String
let lastName: String
let department: String
let jobTitle: String
let pictureUri: URL?
let headerUri: URL?
let bio: String
let updatedDate: Date
}
I need to list out these properties in a UITableView. I also need to use different cell types for some of the properties.
I'm thinking perhaps I should convert this struct to a dictionary of key/value pairs, and use the key to determine the cell type.
Is this possible? Is there another way to achieve this? I am unsure if it possible to convert a struct to a dictionary so am not sure this is the best way?
to convert a class to a dictionary,
class RemoteProfileModel: Decodable {
let userId: String
let company: String
let email: String
let firstName: String
let lastName: String
let department: String
let jobTitle: String
let pictureUri: URL?
let headerUri: URL?
let bio: String
let updatedDate: Date
init() {
userId = "666"
company = "AAPL"
email = "hehe#163.com"
firstName = "user"
lastName = "test"
department = "guess"
jobTitle = "poor iOS"
pictureUri = URL(string: "wrong")
headerUri = URL(string: "none")
bio = "China"
updatedDate = Date()
}
func listPropertiesWithValues(reflect: Mirror? = nil) -> [String: Any]{
let mirror = reflect ?? Mirror(reflecting: self)
if mirror.superclassMirror != nil {
self.listPropertiesWithValues(reflect: mirror.superclassMirror)
}
var yourDict = [String: Any]()
for (index, attr) in mirror.children.enumerated() {
if let property_name = attr.label {
//You can represent the results however you want here!!!
print("\(index): \(property_name) = \(attr.value)")
yourDict[property_name] = attr.value
}
}
return yourDict
}
}
Call like this:
let profile = RemoteProfileModel()
profile.listPropertiesWithValues()
In Swift Debugging and Reflection,
A mirror describes the parts that make up a particular instance
How can I get rid of repetition here for the fields id and commonStaticField?
struct S1 {
let id: String //note - it's let
static let commonStaticField = "s1" //note - it's static
let someField1: String
let someField2: String
}
struct S2 {
let id: String
static let commonStaticField = "s2"
let someField3: String
let someField4: String
}
I've tried to use this with different variations:
protocol P1 {
let id: String
static let commonStaticField: String //static not allowed
}
but it didn't pan out. I'd like to be able to do this:
struct S1: P1 {
static let commonStaticField = "s1" //note - it's static and defined
let someField1: String
let someField2: String
}
let s1 = S1(id: "some id", someField1: "some field3", someField2: "some field2")