How to set a value on computed property - ios

I want to set a value to a key in a computed property of type dictionary. I want to do something like this:
class Foo {
var dict: [String: String] {
dict["a"] = "b"
}
}
However that doesn't compile.
Cannot assign through subscript: 'dict' is a get-only property
I can do something like this but it's a bit verbose:
class Foo {
var values: [String: String] {
var tempValues = [String: String]()
tempValues["a"] = "b"
return tempValues
}
}
Is there a better way of doing this? For context, what I want to achieve is having dict by default to be an empty [String: String]() in a base class and when I override the property in a subclass I want to assign values to said dictionary.

The syntax
var dict: [String: String] {
dict["a"] = "b"
}
is the short form for
var dict: [String: String] {
get {
dict["a"] = "b"
}
}
which indicates a read-only property.
You need to add the setter
var dict: [String: String] {
get {
return [:]
}
set {
dict["a"] = "b"
}
}
But be careful, you can easily run into a infinite loop when you call the setter by itself (which it does in this example).
Actually this kind of computed property is only useful if you are going to map another value.

and when I override the property in a subclass I want to assign values to said dictionary.
Ehh... Just do it:
class A {
var dict: [String: String] {
return [:]
}
}
class B: A {
override var dict: [String : String] {
return ["Hello":"Hi"]
}
}
Did you forget the override keyword?

Related

How to add array of [String] to dictionary

Please find below the code for proper and correct understanding:
var dictUpdateTasks = [String: String]()
var arrAddSteps = [String]()
self.dictUpdateTasks["dDay"] = self.btnAddDate.titleLabel?.text
self.dictUpdateTasks["rem"] = self.btnRemindMe.titleLabel?.text
self.dictUpdateTasks["steps"] = arrAddSteps
Now , this is the error, on line
"self.dictUpdateTasks["steps"] = arrAddSteps"
// Error: Cannot assign value of type '[String]' to type 'String?'
Please guide. Thanks.
Reason:
dictUpdateTasks is of type [String:String], i.e. it can accept values only of type String.
dictUpdateTasks["steps"] = arrAddSteps
But in the above code, you are trying to add [String] type value to dictUpdateTasks.
Solution:
Change the type of dictUpdateTasks from [String: String] to [String: Any],
var dictUpdateTasks = [String: Any]()
If you are looking for something like checking if the value exists or not by using isEmpty property of both the String and [String], on a higher level you could create a protocol separately for that. But for this scenario I would not recommend this.
protocol EmptyIdentifiable {
var isEmpty: Bool { get }
}
extension String: EmptyIdentifiable { }
extension Array: EmptyIdentifiable where Element == String { }
var dictionary = [String: EmptyIdentifiable]()
dictionary["string"] = "value"
dictionary["array"] = ["values1", "values2"]
print(dictionary["string"]?.isEmpty)
print(dictionary["array"]?.isEmpty)
Normal solution in this current situation would be to use casting from Any to String or [String].
if let array = dictionary["array"] as? [String] {
print(array, array.isEmpty)
}
if let string = dictionary["string"] as? String {
print(string, string.isEmpty)
}
Here is the correct way to create a dictionary.
Correct syntax
var dictUpdateTasks = [String: Any]()
You have created a dictionary with [String: String]() and you are assigned String Array arrAddSteps instead of String.
Please try this :
Need to make your dictionary type [String : Any]() instead of [String : String]()
In your case it will accept only String type in value
var dictUpdateTasks = [String: Any]()
var arrAddSteps = [String]()
self.dictUpdateTasks["dDay"] = self.btnAddDate.titleLabel?.text
self.dictUpdateTasks["rem"] = self.btnRemindMe.titleLabel?.text
self.dictUpdateTasks["steps"] = arrAddSteps
print(self.dictUpdateTasks)
You have to use key value pair to assign string to dictionary.

Swift iOS - How to dynamically add properties to an existing class then access them

I need to dynamically add properties to an existing class then access them. I found this answer using objc_setAssociatedObject but there’s no context on how to use it. How can I achieve this?
let dict = ["orderId":"abc", "postId+0":"zero", "postId+1":"one", "postId+2":"two"] // postIds can go on
let order = Order(dict: dict)
let dynamicProperties = order.accessDynamicProperties()
print(dynamicProperties)
Class:
class Order {
var orderId: String?
// I have a main initilizer that I use for something else that's why I'm using a convenience init
convenience init(dict: [String: Any]) {
self.init()
orderId = dict["orderId"] as? String
dynamicallyCreateProperties(dict: dict)
}
func dynamicallyCreateProperties(dict: [String: Any]) {
for (key, value) in dict {
if key.contains("+") {
// dynamically add property and value to class
}
}
}
// returns something???
func accessDynamicProperties() -> ??? {
}
}
Using the suggestion from #DonMag in the comments he gave me a great alternative. He suggested I create a dictionary as a property on the class then add the key,value pairs to it.
class Order {
var orderId: String?
var dynamicDict = [String: Any]()
convenience init(dict: [String: Any]) {
self.init()
orderId = dict["orderId"] as? String
createDynamicKeyValues(dict: dict)
}
func createDynamicKeyValues(dict: [String: Any]) {
for (key, value) in dict {
if key.contains("+") {
dynamicDict.updateValue(value, forKey: key)
}
}
}
}
To use it:
let dict = ["orderId":"abc", "postId+0":"zero", "postId+1":"one", "postId+2":"two"] // postIds can go on
let order = Order(dict: dict)
for (key, value) in order.dynamicDict {
print(key)
print(value)
}

How do I define extension for Dictionary of specific type?

I have created typealias:
typealias Multilang = [String: Any]
Now I would like to extend that Dictionary with property localized. Is it possible?
How do I need to use it?
let dictionary: Multilang = ["en": "b", "pl": "d"]()
let value = dictionary.localized
I try to do it like this:
extension Dictionary where Dictionary: Multilang {
var localized: String {
print("self: \(self)")
return "hello"
}
}
but it doesn't work. Why?
Type 'Dictionary(Key, Value)' constrained to non-protocol, non-class type 'Multilang' (aka 'Dictionary(String, Any)')
You can write an extension like this:
extension Dictionary where Key == String, Value == Any {
var localized: String {
print("self: \(self)")
return "hello"
}
}
or:
extension Dictionary where Dictionary == Multilang
It is (unfortunately) not possible to use typealias in this way, because it does not create a new type. For example, you can have typealias Foo = [String: Any] and typealias Bar = [String: Any], but Foo and Bar still both the same type.
So either you have to extend all [String: Any] dictionaries, or you need to create a wrapper type (you also cannot subclass Dictionary because it is a struct). For example:
struct Multilang<Element> {
var dictionary: [String: Element]
var localized: Element? {
return dictionary[currentLanguage]
}
}
The downside is you need to define all the wrapper methods and properties that you want to use through multilang.foo syntax, but if it suffices to have just multilang.localized, you can do all the rest through multilang.dictionary.foo.
You can also implement ExpressibleByDictionaryLiteral to make creating these objects easy:
extension Multilang: ExpressibleByDictionaryLiteral {
init(dictionaryLiteral elements: (String, Element)...) {
dictionary = .init(elements) { $1 }
}
}
let multilang: Multilang = [ "en": "English", "pl": "Polish" ]
For Swift 3 and above you can try this method to extend a dictionary of type [String: Any].
extension Dictionary where Key: ExpressibleByStringLiteral, Value: Any {
// Your extension code
var localized: String {
print("self: \(self)")
return "hello"
}
ExpressibleByStringLiteral is a protocol that String and StaticString types conform to.
You cannot apply it just to the type alias, because Multilang is just a label for [String:Any], so even if you could extend it, it would extend all dictionaries of this type anyway.

Create an Array of Object from an Array of Dictionaries with Swift

I'm receiving a JSON dictionary from a web service and I need to map the return values to existing values. Here's essentially what I'm trying to do:
class Contract {
var contractID: String?
var ebState: String?
var ibState: String?
var importerState: String?
var exportersBankRefNo: String?
var importersBankRefNo: String?
}
let contract1 = Contract()
contract1.contractID = "001"
let contract2 = Contract()
contract2.contractID = "002"
// This is the JSON return dictionary
let exportAppnStatusList: [[String: String]] = [["contractID":"001",
"ExporterBankRefNo":"ExporterBankRefNo001",
"ExporterBankState":"ACCEPTED",
"ImporterBankRefNo":"",
"ImporterBankState":"UNKNOWN",
"ImporterState":"UNKNOWN" ],
["contractID":"002",
"ExporterBankRefNo":"ExporterBankRefNo002",
"ExporterBankState":"ACCEPTED",
"ImporterBankRefNo":"ImporterBankRefNo002",
"ImporterBankState":"ACCEPTED",
"ImporterState":"UNKNOWN" ]]
I need to take the exportAppnStatusList and fill in the associated values in the existing contract1 and contract2, mapping by the contractID
This fills the contracts with available information, it ignores contracts where the id could not be found:
for contract in [contract1, contract2] {
if let contractDict = exportAppnStatusList.filter({$0["contractID"] == contract.contractID}).first {
contract.exportersBankRefNo = contractDict["ExporterBankRefNo"]
contract.ebState = contractDict["ExporterBankState"]
contract.importersBankRefNo = contractDict["ImporterBankRefNo"]
contract.ibState = contractDict["ImporterBankState"]
contract.importerState = contractDict["ImporterState"]
}
}
Why not generate the contract object by mapping over the array of dictionaries like this? You'll need to write a custom initializer that takes all these params
exportAppnStatusList.map { (dict:[Stirng:String]) -> Contract in
return Contract(contractID:dict["contractID"],
ebState:dict["ExporterBankState"],
ibState:dict["ImporterBankState"],
importerState:dict["ImporterState"],
exportersBankRefNo:dict["ExporterBankRefNo"],
importersBankRefNo:dict["ImporterBankRefNo"]
}
Try using this init (your class must inherit from NSObject):
init(jsonDict: [String: String]) {
super.init()
for (key, value) in jsonDict {
if class_respondsToSelector(Contract.self, NSSelectorFromString(key)) {
setValue(value, forKey: key)
}
}
}
Then you can do this:
exportAppnStatusList.forEach {
print(Contract(jsonDict: $0))
}

Filtering NSDictionary with empty strings in Swift and iOS Support >= 8.0

I have a Dictionary that contains values in such a way that some values are empty string.
let fbPostDic: [String: AnyObject] = [
"title":"",
"first_name”:”Ali“,
"last_name”:”Ahmad“,
"nick_name”:”ahmad”,
"gender":1,
"gender_visibility":2,
"dob":"1985-08-25",
"dob_visibility":2,
"city":"Lahore",
"city_visibility":2,
"bio”:”Its bio detail.”,
"bio_visibility":2,
"nationality":"Pakistan",
"nationality_visibility":2,
"relationship_status”:”Single”,
"rel_status_visibility":2,
"relation_with":"",
"relation_with_visibility":2,
"social_media_source":"Facebook",
]
I want to filter this dictionary such a way that new dictionary should just contains elements without empty strings.
let fbPostDic: [String: AnyObject] = [
"first_name”:”Ali“,
"last_name”:”Ahmad“,
"nick_name”:”ahmad”,
"gender":1,
"gender_visibility":2,
"dob":"1985-08-25",
"dob_visibility":2,
"city":"Lahore",
"city_visibility":2,
"bio”:”Its bio detail.”,
"bio_visibility":2,
"nationality":"Pakistan",
"nationality_visibility":2,
"relationship_status”:”Single”,
"rel_status_visibility":2,
"relation_with_visibility":2,
"social_media_source":"Facebook",
]
There are present ways like
let keysToRemove = dict.keys.array.filter { dict[$0]! == nil }
But its support iOS9.0 or above. I want to give support of iOS8.0 as well.
Any suggestions?
Because the above dict is a constant, add an extra init method in Dictionary extension can simplify the process:
extension Dictionary where Key: StringLiteralConvertible, Value: AnyObject {
init(_ elements: [Element]){
self.init()
for (key, value) in elements {
self[key] = value
}
}
}
print(Dictionary(dict.filter { $1 as? String != "" }))
However, if above dict can be declared as a variable. Probably try the one below without above extra Dictionary extension:
var dict: [String : AnyObject] = [...]
dict.forEach { if $1 as? String == "" { dict.removeValueForKey($0) } }
print(dict)
This solution will also work; But Allen's solution is more precise.
public class func filterDictionaryFromEmptyString(var dictionary:[String: AnyObject]) -> [String: AnyObject]
{
print(dictionary.keys.count)
for key:String in dictionary.keys
{
print(key)
print(dictionary[key]!)
if String(dictionary[key]!) == ""
{
dictionary.removeValueForKey(key)
}
}
print(dictionary.keys.count)
return dictionary
}

Resources