Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 2 years ago.
Improve this question
I have a computed property of type Parameters in my APIRouter
// MARK: - Parameters
private var parameters: Parameters? {
switch self {
case .searchForDoctors(let doctorsFilter):
var params: Parameters = ["main_category_id": doctorsFilter.0, "page": doctorsFilter.1, "specialty_id": doctorsFilter.2, "city_id": doctorsFilter.3, "region_id": doctorsFilter.4, "name": doctorsFilter.5, "company_id": doctorsFilter.6, "order_by": doctorsFilter.7]
return params
default:
return nil
}
}
some values in the Typealias called doctorsFilter are optional.
currently I have a warning asking me to provide default value for the optional values, and I don't want to provide default values , I want to check if the value exist to add it, otherwise i will not add the key and the value
how can I safely unwrap the optional values and add it to the parameters dictionary with out saying if let for all optional values?
example:
if let specialtyID = doctorsFilter.2 {
params["specialty_id"] = specialtyID
}
I don't want to unwrap it this way as I will check for all optional values and it will take more lines of code
EDIT:-
the DoctorsFilter type is documented, when I initialize an instance of type DoctorsFilter the autocomplete tells me which of them is what, I I've thought about making the DoctorsFilter class before but I'm looking for another way if any, maybe a built in reserved word can handle the whole situation! , I want to make it simple as much as possible.
making a function that handles the dictionary and returns it in DoctorsFilter class is an option. I'm thinking of adding this function to the APIRouter, is it fine to add it there? is it the rule of the APIRouter to handle the parameters ? or the APIRouter just interested in taking the parameters and will not handle it ?
There is no "one line" solution, but you can use KeyPaths to reduce the series of if let ... statements down to a loop.
Start by creating a struct for your filter rather than using a tuple.
To facilitate this, we define a protocol for Parameterable - This protocol requires a dictionary that maps parameter names (String) to the property (KeyPath) that holds that parameter name as well as a function to return the parameters dictionary.
protocol Parameterable {
var paramNames: [String:KeyPath<Self,String?>] {get}
func parameters() -> [String:Any]
}
Use an extension to create a default implementation of the parameters() function, as the code will be the same for all Parameterables. It iterates over the dictionary entries and uses the associated KeyPath to access the relevant property and put it in the output dictionary. If a given property is nil then it simply isn't added to the output dictionary, because that is how dictionaries work. No need to explicitly check.
(If you import Alamofire then you can use the typedef Parameters where I have used [String:Any])
extension Parameterable {
func parameters() -> [String:Any] {
var parameters = [String:Any]()
for (paramName,keypath) in self.paramNames {
parameters[paramName]=self[keyPath:keypath]
}
return parameters
}
}
Use this protocol to create a DoctorsFilter implementation:
struct DoctorsFilter: Parameterable {
var mainCategoryId: String?
var page: String?
var specialtyId: String?
var cityID: String?
var regionId: String?
var name: String?
var companyId: String?
var orderBy: String?
let paramNames:[String:KeyPath<Self,String?>] = [
"main_category_id":\.mainCategoryId,
"page":\.page,
"specialty_id":\.specialtyId,
"city_id":\.cityID,
"region_id":\.regionId,
"name":\.name,
"company_id":\.companyId,
"order_by":\.orderBy]
}
private var parameters: Parameters? {
switch self {
case .searchForDoctors(let doctorsFilter):
return doctorsFilter.parameters()
case .someOtherThing(let someOtherThing):
return someOtherThing.parameters()
default:
return nil
}
}
}
The other approach is to simply split your creation of the parameters dictionary into multiple lines; If you assign nil against a dictionary key then there is no key/value pair stored in the dictionary for that key. In this case I have left your tuple approach in place, but you could use the struct (and I strongly suggest you do so)
private var parameters: Parameters? {
switch self {
case .searchForDoctors(let doctorsFilter):
var params: Parameters()
params["main_category_id"] = doctorsFilter.0
params["page"] = doctorsFilter.1
params["specialty_id"] = doctorsFilter.2
params["city_id"] = doctorsFilter.3
params["region_id"] = doctorsFilter.4
params["name"] = doctorsFilter.5
params["company_id"] = doctorsFilter.6
params["order_by"] = doctorsFilter.7
return params
default:
return nil
}
}
If we want to handle mixed properties, rather than just optional strings, we need to modify the code slightly. We need to use PartialKeyPath. This makes the code a little more complex since the subscript operator for a PartialKeyPath returns a double optional. This needs to be handled.
protocol Parameterable {
var paramNames: [String:PartialKeyPath<Self>] {get}
func parameters() -> [String:Any]
}
extension Parameterable {
func parameters() -> [String:Any] {
var parameters = [String:Any]()
for (paramName,keypath) in self.paramNames {
let value = self[keyPath:keypath] as? Any?
if let value = value {
parameters[paramName] = value
}
}
return parameters
}
}
struct DoctorsFilter:Parameterable {
var mainCategoryId: String?
var page: String?
var specialtyId: String?
var cityID: Int
var regionId: String?
var name: String?
var companyId: String?
var orderBy: String?
let paramNames:[String:PartialKeyPath<Self>] =
["main_category_id":\Self.mainCategoryId,
"page":\Self.page,
"specialty_id":\Self.specialtyId,
"city_id":\Self.cityID,
"region_id":\Self.regionId,
"name":\Self.name,
"company_id":\Self.companyId,
"order_by":\Self.orderBy]
}
There are three primary ways to safely unwrap an optional. You can also provide default values if you wish to unwrap an optional.
Guard Statement Unwrapping
var firstString: String?
// In some cases you might performa a break, continue or a return.
guard let someString = firstString else { return }
print(someString)
If Let Unwrapping
var secondString: String?
var thirdString: String?
thirdString = "Hello, World!"
// Notice that we are able to use the same "IF LET" to unwrap
// multiple values. However, if one fails, they all fail.
// You can do the same thing with "Guard" statements.
if let someString = secondString,
let someOtherString = thirdString {
print(someString)
print(someOtherString)
} else {
// With this code snippet, we will ALWAYS hit this block.
// because secondString has no value.
print("We weren't able to unwrap.")
}
Default Value Unwrapping
var fourthString: String?
// The ?? is telling the compiler that if it cannot be unwrapped,
// use this value instead.
print(fourthString ?? "Hello, World")
In Swift it is recommended that anytime you see a ! that you use some form of unwrapping. Swift is very "Type Safe".
Here's a resource you can use for Optionals.
https://docs.swift.org/swift-book/LanguageGuide/TheBasics.html
Your Solution
Your solution might look something like this.
private var parameters: Parameters? {
switch self {
case .searchForDoctors(let doctorsFilter):
if let mainCatID = doctorsFilter.0,
let page = doctorsFilter.1,
let specialtyID = doctorsFilter.2,
let cityID = doctorsFilter.3,
let regionID = doctorsFilter.4,
let name = doctorsFilter.5,
let companyID = doctorsFilter.6,
let orderBy = doctorsFilter.7 {
params: Parameters = ["main_category_id": mainCatID,
"page": page,
"specialty_id": specialtyID,
"city_id": cityID,
"region_id": regionID,
"name": name,
"company_id": companyID,
"order_by": orderBy]
return params
} else {
//Unable to safely unwrap, return nothing.
return nil
}
default:
return nil
}
}
Related
Goal of the code:
To assign a struct dictionary with Strings as Keys and String Arrays as values to a variable and then pull one (can be at random) specific String key value in the String Array and return that one String element in the underlying String Array so that it can be used elsewhere (potentially assigned to a label.text)
Essentially (please reference code below), I want to access one value at random in myDictionary using a specific key ("keyOne"), and pull, let's say, "Value2" then return only the string "Value2" from the underlying String Array associated with "keyOne" using indexing.
Errors are in the code below.
The issue I'm thinking is that I haven't figured out how to turn my final var Testing = dict["keyOne"] into an Int compatible index... if it was an index, the code would pull an Int value and the corresponding String from the three Strings in the underlying value array (due to the three String values associated with "keyOne").
Also, variableView() just inherits the datasource from several other containers, but the var dataSource : Structure? is the main reference, so that is what I included.
Code so far:
let myDictionary = [Structure(name: "keyOne", text: ["Value1", "Value2", "Value3"]), Structure(name: "keyTwo", text: ["Value4", "Value5", "Value6"])]
lazy var dict = Dictionary(uniqueKeysWithValues: myDictionary.lazy.map { ($0.name, $0.text) })
struct Structure: Hashable {
var name: String
var text: [String]
init(name: String, text: [String]){
self.name = name
self.text = text
}
}
func variable(at index: Int) -> variableView {
let variable = variableView()
var Testing = dict["keyOne"]
variable.dataSource = Testing![index] <- Cannot assign value of type 'String' to type 'structure'
return variable
var dataSource : Structure? {
didSet {
label.text = "This is a test"
} else {
// n/a
}
}
Please note that the error message is above in the code for variable.dataSource = Testing![index].
I am also suspecting that my issue lies in the "looping" logic of how I am assigning a variable with a struct, to a datasource which references that same struct.
Any help is appreciated as I have been stuck on this for legitimately a week (I truly have exhausted every single StackOverflow answer/question pair I could find).
THANK YOU!
EDIT:
I found this documentation to assist me greatly with this, and I recommend anyone with a similar question as mine to reference this: https://swift.org/blog/dictionary-and-set-improvements/
Given the question and the discussion in the comments I would add a mutating func to the struct that removes and returns a random string
mutating func pullText() -> String? {
guard let index = text.indices.randomElement() else {
return nil
}
return text.remove(at: index)
}
Example
if let index = myDictionary.firstIndex(where: { $0.name == "keyOne" }),
let text = myDictionary[index].pullText() {
someLabel.text = text
}
Here is another example based on the code in the question
Assuming VariableView looks something like this
struct VariableView: View {
var dataSource : Structure?
var word: String?
var body: some View {
Text(word ?? "")
}
}
Then the func variable can be changed to
func variable() -> VariableView {
var variable = VariableView()
if let index = dict.firstIndex(where: { $0.name == "keyOne" }) {
variable.dataSource = dict[index]
variable.word = dict[index].pullText()
}
return variable
}
I am using newsApi to get list of news from it. I created model based on news's property, all property are optional in model and as i parse it printed to console getting result but all fields have data with optional text
I have created three struct based on news api fields, They are like
struct GoogleNews: Codable {
var status: String?
var totalResults: Int?
var articles: [Article]
}
struct Article: Codable {
var source: Source
var author: String?
var title: String?
var description: String?
var url: String?
var urlToImage: String?
var publishedAt: String?
var content: String?
}
struct Source: Codable {
var id: String?
var name: String?
}
Calling the appi
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else {return}
do {
let allNews = try JSONDecoder().decode(GoogleNews.self, from: data)
print(allNews.articles[0])
} catch let error {
print(error.localizedDescription)
}
}.resume()
After calling the api, in result all fields are having result with optional text
name: Optional("Venturebeat.com")), author: Optional("Dean Takahashi"), title: Optional("How Paymentwall’s Terminal3 lets game developers create their own online shops"), description: Optional("Paymentwall built a business as a global payments platform, with much of its focus on games. Last year, the company spun out its Terminal3 as a platform for monetizing and distributing games. Now it is making it easier for indie, small, and medium-size game c…")...ect
What should be the solution to remove the optional text from the results..
For the values in your struct that are optional, be sure they're optionals because you know for sure that there are cases where a value won't be returned. If you'd like to unwrap them, you have 2 ways of doing so.
The first way is to use an if-let statement, which would look something like this:
if let name = allNews.articles[0].name {
}
Within the curly braces is where you would use the variable name, which wouldn't be the optional value you're asking about because it's been unwrapped.
The second method you could use is a guard statement, which looks like this:
guard let name = allNews.articles[0].name else { return }
In this instance, the name variable would be unwrapped and can be used anywhere in the scope of your code. However, it's only valid if it can be successfully unwrapped. If it cannot, then the return statement is called and breaks out of whatever scope it's in.
The thing is you're trying to print an object which all properties are optionals. You have to unwrap the properties even if you unwrap the article . You can unwrap your optionals like this:
let article = allNews.articles[0]
if let source = article.source {
print(source)
}
Also you can unwrap more than one property at once:
if let source = article.source, let author = article.author, let title = article.title {
// do things
}
Just remove the sign of ? from the end of the Struct members declaration to become like this:
let author: String
I am converting my project Objective-C code to Swift. The thing fine but while having its for in loop i am facing error something like its type conversion issue. Thanks In Advance.
class GymUserSession: NSObject {
var passes = [AnyHashable]()
func getPassList() -> [AnyHashable]? {
var list = [AnyHashable]()
for pass: GymPass? in passes {
if pass?.isGift == nil || pass?.activated != nil {
if let aPass = pass {
list.append(aPass)
}
}
}
return list
}
}
GymPass is another NSObject Class
class GymPass: NSObject {
var gymID : String
var passID : String
var isGift : Bool
var activated : Bool
var dateCreated : Date?
var dateActivated : Date?
}
The short answer is that Swift can't implicitly downcast an AnyHashable to a GymPass, which is what you have asked it to do.
You could fix the error by explicitly downcasting, but really that is just addressing one small issue that would let the code compile
In Swift you should always use the most explicit type you can, when it is known. Types such as Any, AnyObject and AnyHashable should only be used when you don't know the type or there may be multiple types. For example a dictionary obtained decoding JSON could be [String:Any] since you know it will have String keys, but the value types will be varied.
In this case, presumably, you know that passes will contain GymPass instances. A more "Swifty" version of your code could look something like this (but it is hard to be specific as I don't have enough detail on where the data is coming from and what you are trying to achieve, exactly):
struct GymPass {
var gymID: String
var passID: String
var isGift: Bool
var dateCreated: Date
var dateActivated: Date?
var isActivated: Bool {
get {
return self.dateActivated != nil
}
}
}
class GymUserSession: NSObject {
var passes = [GymPass]()
func getPassList() -> [GymPass] {
return passes.filter ( { $0.isGift || $0.isActivated } )
}
}
I am new to Swift and trying to write my first function that calls a closure that the user passes in. I am having trouble calling my function (which I named fun). I also was unable to find any examples of this online. I just want to call my closure (I am unsure what to pass to it?) and then make a decision based on the boolean result? This seems very easy yet I am not sure.
The goal of the method is to remove duplicates in an array based on the users specifications. In my case I may want to pass in a certain class and an array of it and then remove all classes that have the same name property (ie a name string that matches).
extension Array{
func removeDuplicates<T: Comparable>(fun: (elem: T, arr: [T]) -> Bool) -> [T]
{
var array = [T]()
for element in self
{
if fun(elem: T(), arr: [T])
{
println("hello")
}
}
return array
}
}
This is a slight generalization of Does there exist within Swift's API an easy way to remove duplicate elements from an array? and might be what you are
looking for:
extension Array {
func withoutDuplicates<U : Hashable>(attribute : T -> U) -> [T] {
var result : [T] = []
var seen : Set<U> = Set()
for elem in self {
let value = attribute(elem)
if !seen.contains(value) {
result.append(elem)
seen.insert(value)
}
}
return result
}
}
The attribute closure is applied to each array element, and
a Set is used to keep track of values that occurred already.
Therefore the value type U is required to be Hashable (which
is the case for strings).
Example:
struct Person : Printable {
let firstName : String
let lastName : String
var description: String {
return "\(firstName) \(lastName)"
}
}
let array = [
Person(firstName: "John", lastName: "Doe"),
Person(firstName: "Joe", lastName: "Miller"),
Person(firstName: "Jane", lastName: "Doe")
]
let result = array.withoutDuplicates( { $0.lastName } )
println(result)
// [John Doe, Joe Miller]
An alternative implementation is
func withoutDuplicates<U : Hashable>(attribute : T -> U) -> [T] {
var seen : [U : Bool] = [:]
return self.filter { seen.updateValue(true, forKey: attribute($0)) == nil }
}
which utilizes the fact that the updateValue() method of Dictionary
returns the previous value for the key, and in particular returns nil if the key was not set previously. This is also just a slight generalization of #rintaro's answer to iOS Swift: Filter array to unique items.
I'm writing an extension to Dictionary so that when I give it a String key, it'll return me a String only if the value associated with the key is non-nil and not empty.
extension Dictionary {
subscript(key: String) -> String? {
if let string = super.subscript(key) {
if string.isEmpty == false {
return string
}
}
return nil
}
}
However, at the if let string = super.subscript(key) { line, I get the following compile error and I don't know what it means--neither is there a Google result that explains it:
Expected -> for subscript element type
I am doing this because I'm working with an API that returns a JSON where a key's value may be an empty string--which is an invalid value to the app by our requirements, and hence, as good as nil.
Of course the longer way works, but I'm looking for a way to make this shorter.
if let value = dict["key"] as? String {
if value.isEmpty == false {
// The value is non-nil and non-empty.
}
}
You're going to think this is very silly, but my suggestion would be: do more or less exactly what you're doing, but encapsulate it as a separate function rather than trying to deal with the implications of defining a new subscript:
extension Dictionary {
func nes(key:Key) -> String? {
var result : String? = nil
if let s = self[key] as? String {
if !s.isEmpty {
result = s
}
}
return result
}
}
(nes stands for "non-empty string".)
Now call it like d.nes("foo").