Swift update value inside Dictionary<String, Any> in an Array - swift

I have an array of Dictionaries, like so
var myArray: [Dictionary<String, Any>] = [Dictionary<String, Any>]()
Somewhere in my class i add values there, like so:
var myDict = Dictionary<String, Any>()
myDict["Id"] = "someUniqueId"
myDict["v1"] = -42
self.myArray.append(myDict)
Then later i try to update the v1 inside this dict like so:
self.myArray.first(where: { $0["Id"] == "someUniqueId" })?["v1"] = -56
However, the compiler tells me:
Cannot assign through subscript: function call returns immutable value
Any way i can modify the value in array outside of copying the whole dictionary, removing it from array, and re-inserting?
UPDATE This dictionary is not strongly typed because what is inside the dictionary, both types and fieldnames, are controlled by the user in an external system. E.g. we download some metadata and then render controls for this dynamic data.

This is because Swift Dictionary is a value type.
Use a custom class or you can use NSMutableDictionary for this purpose (because NSDictionary is immutable).
var myArray = [NSMutableDictionary]()
var myDict = NSMutableDictionary()
myDict["Id"] = "someUniqueId"
myDict["v1"] = -42
myArray.append(myDict)
myArray.first(where: { $0["Id"] as? String == "someUniqueId" })?["v1"] = -56
print(myArray)
// [{
// Id = someUniqueId;
// v1 = "-56";
// }]

You can make it easier for yourself by wrapping your dictionary in a simple class
class Config {
private var dict: [String: Any]
init(_ dict: [String: Any]) {
self.dict = dict
}
func valueFor(_ key: String) -> Any? {
return dict[key]
}
func setValue(_ value: Any, forKey key: String) {
dict[key] = value
}
}
and use that in your array
var myArray = [Config]()
And use it like this
array.filter { $0.valueFor("Id") as? String == "someUniqueId" }
.first?.setValue(-56, forKey: "v1")
That way you can avoid using classes like NSMutableDictionary and keeping it more pure Swift. Another advantage with a custom class is that you can add functionality to it to simplify your code using it, so for instance if you will be looking up dictionaries by "Id" a lot we can add a computed property for it
var id: String? {
return valueFor("Id") as? String
}
and then the example above would become
array.filter { $0.id == "someUniqueId" }.first?.setValue(-56, forKey: "v1")

Related

Swift error extending Dictionary Any? is not convertible to Profiles

I am getting an error: 'Any?' is not convertible to 'Profiles' I am not sure how to correct. I am trying to extend the dictionary. I am needing the structure show in the Profile Struct
struct Profiles {
var id: Int
var name: String
}
extension NSDictionary {
var profileDictionary: [String : Profiles] {
var dictionary: [String : Profiles] = [:]
let keys = self.allKeys.compactMap { $0 as? String }
for key in keys {
let keyValue = self.value(forKey: key) as Profiles
dictionary[key] = keyValue
}
return dictionary
}
}
Not sure why you need to use NSDictionary when coding with Swift. I will post the Dictionary approach but NSDictionary would be exactly the same. You can use reduce method and convert all key value pairs to (String, Profiles) and add it to the resulting dictionary:
extension Dictionary {
var profileDictionary: [String : Profiles] {
reduce(into: [:], { result, keyValue in
if let kv = keyValue as? (String, Profiles) {
result[kv.0] = kv.1
}
})
}
}

How to update key of dictionary in Swift 3

if (userFollowingArray[indexPath.row]["watcher_following"] as! Bool) == false {
let dict : NSMutableDictionary = userFollowingArray[indexPath.row] as! NSMutableDictionary
print(dict)
dict["watcher_following"] = 1
self.userFollowingArray[indexPath.row] = dict as! Dictionary<String, Any>
}
I want to update watcher_following key with true/false but it throws an exception.
[_TtGCs26_SwiftDeferredNSDictionarySSP__
_swift_setObject:forKeyedSubscript:]: unrecognized selector sent to instance
Do not use NSMutable... collection types in Swift
Declare userFollowingArray as Swift Array
var userFollowingArray = [[String:Any]]()
Get the Dictionary as variable
var dict = userFollowingArray[indexPath.row]
Check the value
if dict["watcher_following"] as! Bool == false {
Update it if necessary and assign the dictionary back to the array.
dict["watcher_following"] = true
userFollowingArray[indexPath.row] = dict
}
The last step is necessary due to value semantics
It's much more convenient to use a custom struct or class rather than a dicionary.
Declare var for mutable types, let is only for immutable/constant types. not needed to use objective c NSMutableDictionary type.
if dict["watcher_following"] as! Bool == false {
var dict : Dictionary<String, Any> = userFollowingArray[indexPath.row] as! Dictionary<String, Any>
dict["watcher_following"] = 1
self.userFollowingArray[indexPath.row] = dict as! Dictionary<String, Any>
}

How to write inside a dictionary entry in NSUserDefaults without using temporary variables?

I have this code:
var address = Dictionary<String, AnyObject?>();
address["address1"] = "Here";
address["address2"] = "There";
...
let defaults = NSUserDefaults.standardUserDefaults();
var data = defaults.valueForKey("active_user")! as! Dictionary<String, AnyObject?>
data["address"] = address;
defaults.setValue(data, forKey: "active_user");
defaults.synchronize();
I want to change it into like this:
var address = Dictionary<String, AnyObject?>();
address["address1"] = "Here";
address["address2"] = "There";
...
let defaults = NSUserDefaults.standardUserDefaults();
defaults["active_user"]!["address"]! = address;
defaults.synchronize();
Is this possible? How can I do that?
This is somewhat possible by extending NSUserDefaults to have a subscript. See below:
extension NSUserDefaults {
subscript(key: String) -> Any {
get {
return value(forKey: key)
}
set {
setValue(newValue, forKey: key)
}
}
}
This can then be used like so:
let defaults = NSUserDefaults.standardUserDefaults()
defaults["myKey"] = "someValue"
let myValue = defaults["myKey"]
One limitation is that you won't be able to modify nested collections as you've done in your example. You'll always have to make a manual assignment back to the user defaults object to save something.
// this won't work
defaults["active_user"]!["address"]! = address;
// do this instead
let user = defaults["active_user"]!
user["address"] = address
defaults["active_user"] = user
Edit:
I figured out a way to overload the subscript so you can modify nested collections, with slightly different but very clean syntax. Only works at one level of nesting, but I think it could be taken further. Add this to your NSUserDefaults extension:
subscript(firstKey: String, secondKey: String) -> Any? {
get {
if let dict = value(forKey: firstKey) as? [String: Any] {
return dict[secondKey]
}
return nil
}
set {
if let dict = value(forKey: firstKey) as? [String: Any] {
dict[secondKey] = newValue
setValue(dict, forKey: firstKey)
}
}
}
Then you can do this:
defaults["active_user", "address"] = "some address"
let address = defaults["active_user", "address"]

Append Firebase Data into [String]() in Swift

I have data like below
I want to get the value of all objectIds and append it to a [String]() in Swift. Though when I use the append function, it first adds one, then two, and then three and so on. Below is the code I'm using right now.
var ObjectID: [String]?
override func viewDidLoad() {
super.viewDidLoad()
self.ObjectID = [];
let ref = Firebase(url:"https://blazing-heat-3676.firebaseio.com/results")
ref.queryOrderedByChild("objectId").queryLimitedToLast(201).observeEventType(.ChildAdded) { (snap: FDataSnapshot!) -> Void in
let objectId = snap.value["objectId"] as! String
self.ObjectID?.append(objectId)
print(self.ObjectID)
}
}
What modifications should I make for all objectIds to be in the array.
Firebase have no arrays but if the data looks like an array, Firebase clients will render it as an array. Therefore you can simply convert the result into an array and work with each individual object of this array.
let firebase = Firebase(url: "https://blazing-heat-3676.firebaseio.com/results")
firebase.observeSingleEventOfType(.Value) { (snapshot: FDataSnapshot!) -> Void in
guard let jsonArray: [JSON] = snapshot.value as? [JSON] else {
return
}
var objectIds: [String] = []
for json in jsonArray {
if let id = json["objectId"] as? String {
objectIds.append(id)
}
}
// Print result
print(objectIds)
}
Where JSON is
public typealias JSON = [String : AnyObject]
As an alternative solution - you can model this into query but you get the idea.
var myString: String = ""
ref.observeEventType(.Value, withBlock: { snapshot in
for child in snapshot.children {
let name = child.value.objectForKey("ObjectId") as! String
myString += name
}
print(myString)
})
Also, you may want to re-think your keys (node names) as numeric sequential indexes are hard to work with. You should check into childByAutoId.
Also, Firebase does support arrays via NSArray however, there are usually much better alternatives.

Swift: Variable of Optional Dictionary inside Class: Shows Brackets in Output

I would like to make an Optional Dictionary Variable inside of a Class so that when I create an instance I don't HAVE to add that argument...
example
class Stuff {
var dictionary: [String: Double]?
init(dictionary: [String:Double]?=["":0]){
}
}
var instance1 = Stuff(dictionary:["length":1.5])
var array: [Stuff] = [instance1, instance2]
let arraySet = (array[indexPath.row])
let X = arraySet.dictionary!
cell.Label.text = "\(X)"
This works but when i assign it to a cell.label.text the output shows Brackets...
"[length:1.5]"
How can i get rid of the brackets???
It's quite complicated to get rid of the brackets, because the key/value pair must be extracted out of the dictionary:
class Stuff {
var dictionary: [String: Double]?
init(dictionary: [String:Double]?=["":0.0]){
self.dictionary = dictionary
}
}
let instance = Stuff(dictionary:["length":1.5])
if let dict = instance.dictionary where dict.count > 0 {
let key = dict.keys.first!
let value = dict[key]!
label.text = "\(key) : \(value)"
}
If you create the dictionary with non-optional literals, consider to declare the variable dictionary as non-optional