Swift: More elegant way to write this array mapping? - ios

I would like to know if there is a more elegant way to write this:
struct S {
var state: [String: Any]
public var amounts: [Amount] {
var result: [Amount] = []
(self.state["amounts"] as? [Any]?)??.forEach({ a in
result.append(Amount(a))
})
return result
}
}
struct Amount {
init(_ any: Any?) {}
}
I have tried using map for array, but I can't find a way to do so.

You could also use guard let and early return, which would make it look bit better.
Here is how I did it,
struct S {
var state: [String: Any]
public var amounts: [Amount] {
guard let amounts = state["amounts"] as? [Any] else {
return []
}
return amounts.map(Amount.init)
}
}

struct S {
var state: [String: Any]
public var amounts: [Amount] {
return (self.state["amounts"] as? [Any] ?? []).map({ Amount($0) })
}
}
struct Amount {
init(_ any: Any?) {}
}

You are using too many unnecessary optionals here. You should always use as? with a non-optional type. And instead of forEach, use map:
public var amounts: [Amount] {
if let anyArray = self.state["amounts"] as? [Any] {
return anyArray.map(Amount.init)
} else {
return []
}
}

You can get that working in a single line,
public var amounts: [Amount] {
return (self.state["amounts"] as? [Any])?.map({ Amount($0) }) ?? []
}

init(_ state: [Amount]) {
self.init()
guard let amounts = state["amounts"] as? [Any] else {
return []
}
return amounts.map(Amount.init)
}

Related

Retrieving custom Object from NSUserDefaults

I have a dictionary of values
class Objects {
let values = [
"AAA": ["AAAAAAA", "111111111"],
"BBB": ["BBBBBBBB", "2222222"],
"CCC": ["CCCCCCCC", "3333333333"],
"DDD": ["DDDDDD", "44444444"],
]
}
Which I turn into custom objects and display in a tableview.
struct Object {
var heading : String!
var imageName: String!
}
Then the user can select two objects to store in UserDefaults
let defaults = UserDefaults.standard
func addObject(_ object1: String, object2: String) {
// Get objects for user
var userObjects = fetchObjectsFromUserDefaults()
// Add to user currencies
userObjects.append([object1,object2])
//Update user defaults value for key
// [ [Object1, Object2], [Object1, Object2] ]
defaults.set(userObject, forKey: "userCurrencies")
}
// Gets [[String]] values from user defaults for key
func fetchObjectsFromUserDefaults() -> [[String]] {
if let objects = UserDefaults.standard.value(forKey: "userObjects") {
return objects as! [[String]]
} else {
return []
}
}
// Uses [[String]] values and turns them into objects by using the dictionary to determine property values
func getObject() -> [[Object]] {
let userObject = fetchObjectsFromUserDefaults()
// [ [Object1, Object2], [Object1, Object2] ]
let object = Object()
var fetchedObject = [[Object]]()
if !userObjects.isEmpty {
for c in userObjects {
var set = [Object]()
if let val = object.available[c[0]] {
set.append(Currency(currencyTitle: c[0], imageName: val[0] ))
}
if let val2 = object.available[c[1]] {
set.append(Currency(currencyTitle: c[0], imageName: val2[0] ))
}
if !set.isEmpty {
fetchedObjects.append(set)
}
}
return fetchedObjects
}
return [[]]
}
View Controller
Here I get the objects to load into the TableView
let fetched = dataManager.getObjects
print(fetched)
self.objects = fetched()
However this prints out
(Function)
What am I doing wrong and is their a better method of storing and retrieving this data from user defaults ? I feel this is over kill and there is a swifter and safer approach.
Step 1.
Make your struct Codable. The compiler will write all of the functions for you if all of the members of your struct are Codable and fortunately String is Codable so its just:
struct Object: Codable {
var heading : String!
var imageName: String!
}
Step 2.
The problem with Codable is that it converts to and from Data, but you want to convert to and from a Dictionary. Fortunately JSONSerialization converts from Data to Dictionary so make a new protocol and give it a default implementation with a protocol extension:
protocol JSONRepresentable {
init?(json: [String: Any])
func json() -> [String: Any]
}
extension JSONRepresentable where Self: Codable {
init?(json: [String:Any]) {
guard let value = (try? JSONSerialization.data(withJSONObject: json, options: []))
.flatMap ({ try? JSONDecoder().decode(Self.self, from: $0) }) else {
return nil
}
self = value
}
func json() -> [String:Any] {
return (try? JSONEncoder().encode(self))
.flatMap { try? JSONSerialization.jsonObject(with: $0, options: []) } as? [String: Any] ?? [:]
}
}
Step 3.
Conform your struct to JSONRepresentable
struct Object: Codable, JSONRepresentable {
var heading : String!
var imageName: String!
}
Step 4.
Place your object into Userdefaults and get it out again:
let o = Object.init(heading: "s", imageName: "a").json()
UserDefaults.standard.set(o, forKey: "test")
print(Object.init(json: UserDefaults.standard.dictionary(forKey: "test") ?? [:]))
Here is the whole playground if you want to try:
import UIKit
struct Object: Codable, JSONRepresentable {
var heading : String!
var imageName: String!
}
protocol JSONRepresentable {
init?(json: [String: Any])
func json() -> [String: Any]
}
extension JSONRepresentable where Self: Codable {
init?(json: [String:Any]) {
guard let value = (try? JSONSerialization.data(withJSONObject: json, options: []))
.flatMap ({ try? JSONDecoder().decode(Self.self, from: $0) }) else {
return nil
}
self = value
}
func json() -> [String:Any] {
return (try? JSONEncoder().encode(self))
.flatMap { try? JSONSerialization.jsonObject(with: $0, options: []) } as? [String: Any] ?? [:]
}
}
let o = Object.init(heading: "s", imageName: "a").json()
UserDefaults.standard.set(o, forKey: "test")
print(Object.init(json: UserDefaults.standard.dictionary(forKey: "test") ?? [:]))

Swift: how to convert a [String:Any] array to a specific array

I have this simple Struct:
protocol DocumentSerializable {
init?(dictionary:[String:Any])
}
struct Item {
var title:String
var text:String?
var dictionary:[String:Any] {
return [
"title":title,
"text":text,
]
}
}
extension Item : DocumentSerializable {
init?(dictionary: [String : Any]) {
guard let title = dictionary["title"] as? String,
let text = dictionary["text"] as? String? else {return nil}
self.init(title: title, text: text)
}
}
When I recive my json, I put it in an array...
if let array = result?.data as? Array<[String:Any]> {...
How can I convert this array into an array of Items? var itemsArray = [Item]()
The two arrays have exactly the same structure
Thanks
Use
struct Item :Decodable {
let title:String
let text:String?
}
//
do {
let root = try JSONDecoder().decode([Item].self, from:jsonData)
print(root)
}
catch {
print(error)
}
Use compactMap, it handles also the nil cases:
itemsArray = array.compactMap{ Item(dictionary: $0) }
However in Swift 4 it's highly recommended to use the Codable protocol

IGListKit insert data to class from Alamofire request

So i have this model
class Event: NSObject {
var _eventName: String!
var _venueName : String!
var _eventImage: String!
var eventName: String {
if _eventName == nil {
_eventName = ""
}
return _eventName
}
var venueName: String {
if _venueName == nil {
_venueName = ""
}
return _venueName
}
var eventImage: String {
if _eventImage == nil {
_eventImage = ""
}
return _eventImage
}
init(eventsDict: Dictionary<String, AnyObject>) {
if let venue = eventsDict["venue"] as? Dictionary<String, AnyObject> {
if let venuname = venue["name"] as? String{
self._venueName = venuname
}
if let eventname = eventsDict["name"] as? String {
self._eventName = eventname
}
if let eventimage = eventsDict["coverPicture"] as? String {
self._eventImage = eventimage
}
}
}
And i make it IGListDiffable with this extension.
extension NSObject: IGListDiffable {
public func diffIdentifier() -> NSObjectProtocol {
return self
}
public func isEqual(toDiffableObject object: IGListDiffable?) -> Bool {
return isEqual(object)
}
}
So when I'm loading data from hardcoded code like this
var entries = [Event]()
func loadFakeEvents() {
let entries = [
Event(
eventName: "Ζωρζ Πιλαλι Και Η Soufra Band Στο AN Groundfloor - Live Stage!",
venueName: "AN Groundfloor - live stage",
eventImage: "https://scontent.xx.fbcdn.net/v/t31.0-8/s720x720/15936729_1867160333520142_8855370744955080264_o.jpg?oh=8198bc10a8ea61011d7ec1902b34aa01&oe=593D6BC4"
),
Event(
date: "2017-02-18T21:30:00+0200",
name: "Διονύσης Σαββόπουλος at Gazarte I Main Stage 18/02",
venuename: "Gazarte",
eventImage: "https://scontent.xx.fbcdn.net/v/t1.0-9/s720x720/16265335_1262826863809003_3636661375515976849_n.jpg?oh=5bb342321a65d33dbc1cc41de266b45e&oe=5907857C"
)
]
self.entries = entries
}
The events are loading fine. As they have to.
But when i'm making an alamofire request, of course, it takse some time to load the data and append them to the empty array of events.
This is the function that I have to call the events
func loadEvents() {
let parameters: Parameters = [
"Some" : "Parameters",
"Some" : "Parameters"
]
Alamofire.request(baseurl, method: .get, parameters: parameters)
.responseJSON { (responseData) -> Void in
if((responseData.result.value) != nil) {
let result = responseData.result
if let dict = result.value as? Dictionary<String, AnyObject>{
print(dict) // <-- Check this out
if let list = dict["events"] as? [Dictionary<String, AnyObject>] {
for obj in list {
let event = Event(eventsDict: obj)
self.entries.append(event)
}
}
}
}
}
}
So in the above code i have a print, which prints the json.
And in my
extension LocationViewController: IGListAdapterDataSource {
func objects(for listAdapter: IGListAdapter) -> [IGListDiffable] {
let items: [IGListDiffable] = loader.entries as [IGListDiffable]
print(items.count) // <--- another print of items that should be displayed
return items
}
func listAdapter(_ listAdapter: IGListAdapter, sectionControllerFor object: Any) -> IGListSectionController {
return NormalSectionController()
}
func emptyView(for listAdapter: IGListAdapter) -> UIView? { return nil }
}
Adapter i also print the items that should be displayed.
So when i load the fakeEvents function it prints 2 but when i load them with the normal function it prints 0 and then the JSON from the dict var from the previous code.
Normally i would reloadData() of the collection view.
But with IGListKit what is the trick of sending the Event Class to the CollectionView?
Thanks a lot for your time and i hope i'm not off topic !
Pasting my answer from this same issue on Github in case anyone finds this.
https://github.com/Instagram/IGListKit/issues/468
It looks like you're missing a call to self.adapter.performUpdates(animated: true) after the for-loop when appending to the entries dict:
func loadEvents() {
// ...
Alamofire.request(baseurl, method: .get, parameters: parameters)
.responseJSON { (responseData) -> Void in
if responseData.result.value != nil {
let result = responseData.result
if let dict = result.value as? Dictionary<String, AnyObject>{
if let list = dict["events"] as? [Dictionary<String, AnyObject>] {
for obj in list {
let event = Event(eventsDict: obj)
self.entries.append(event)
}
// missing this!
self.adapter.performUpdates(animated: true)
// missing that!
}
}
}
}
}

how to parse this json in swift?

I have a request
Alamofire.request(.GET,HttpHelper.baseURL+HttpHelper.tripsURL,encoding:.JSON).responseJSON {
response in
var json = JSON(data: response.data!)
print(json)
print(json["res"])
}
followed by the result
{
"res" : "[{\"name\":\"testName\",\"lastName\":\"testLastName\"},{\"name\":\"testName\",\"lastName\":\"testLastName\"}]",
"status" : "success",
"out" : "{\"name\":\"testName\",\"lastName\":\"testLastName\"}"
}
[{"name":"testName","lastName":"testLastName"},{"name":"testName","lastName":"testLastName"}]
how i can set data from res to struct or class User
struct User {
var name : String?
var lastName : String?
}
please help to solve this problem) thank you very much !!)
You can do something like that
var result: [User]()
for user in json["res"] {
let userTmp = User(name: user["name"], lastName: user["lastName"])
result.append(userTmp)
}
Regards
Basically, it would be:
class User {
var name : String?
var lastName : String?
}
var theUsers = [User]()
Alamofire.request(.GET,HttpHelper.baseURL+HttpHelper.tripsURL,encoding:.JSON)
.responseJSON { response in
var json = JSON(data: response.data!)
print(json)
theUsers = json["res"].map {
return User (name: $0["name"], lastName: $0.["lastName"])
}
})
However, along the way, you might need some typecasting. For example, maybe replace json["res"] with (json["res"] as Array<Dictionary<String,String>>) in order to keep the type checker and type inferencer happy.
I'm using native Codable protocol to do that:
class MyClass: Codable {
var int: Int?
var string: String?
var bool: Bool?
var double: Double?
}
let myClass = MyClass()
myClass.int = 1
myClass.string = "Rodrigo"
myClass.bool = true
myClass.double = 2.2
if let json = JsonUtil<MyClass>.toJson(myClass) {
print(json) // {"bool":true,"string":"Rodrigo","double":2.2,"int":1}
if let myClass = JsonUtil<MyClass>.from(json: json) {
print(myClass.int ?? 0) // 1
print(myClass.string ?? "nil") // Rodrigo
print(myClass.bool ?? false) // true
print(myClass.double ?? 0) // 2.2
}
}
And I created a JsonUtil to help me:
public struct JsonUtil<T: Codable> {
public static func from(json: String) -> T? {
if let jsonData = json.data(using: .utf8) {
let jsonDecoder = JSONDecoder()
do {
return try jsonDecoder.decode(T.self, from: jsonData)
} catch {
print(error)
}
}
return nil
}
public static func toJson(_ obj: T) -> String? {
let jsonEncoder = JSONEncoder()
do {
let jsonData = try jsonEncoder.encode(obj)
return String(data: jsonData, encoding: String.Encoding.utf8)
} catch {
print(error)
return nil
}
}
}
And if you have some issue with Any type in yours objects. Please look my other answer:
https://stackoverflow.com/a/51728972/3368791
Good luck :)

how to use updateValue to add an object in Swift

I have a User Struct that I'm casting to Json to be able to get into NSUserDefaults...
import Foundation
struct User {
var name = ""
var stores: [Store] = []
init?(json: [String: AnyObject]) {
if let name = json["name"] as? String,
storesJSON = json["stores"] as? [[String: AnyObject]]
{
self.name = name
self.stores = storesJSON.map { Store(json: $0)! }
} else {
return nil
}
}
init() { }
func toJSON() -> [String: AnyObject] {
return [
"name": name,
"stores": stores.map { $0.toJSON() }
]
}
}
and I am using a Data Manager class (Singleton) to add a new User. But I can't figure out what to pass into updateValue in my addPerson function below? Alternatively is there another way to get this object into NSUserDefaults?
import Foundation
class DataManager {
static let sharedInstance = DataManager()
var users = [String : User]()
init() {
let userDefaults = NSUserDefaults.standardUserDefaults()
if let var userFromDefaults = userDefaults.objectForKey("users") as? [String : User] {
users = userFromDefaults
}
else {
// add default values later
}
}
var userList: [String] {
var list: [String] = []
for userName in users.keys {
list.append(userName)
}
list.sort(<)
return list
}
func addPerson(newUserName: String) {
users.updateValue(User(), forKey: newUserName)
// saveData()
}
You should change your interface of the addPerson function, use addPerson(newUser: User) instead of using addPerson(newUserName: String) as #iosDev82 said:
// Because your addPerson function needs two parameters: a name and a user object
func addPerson(newUser: User) {
users.updateValue(newUser, forKey: newUser.name)
// saveData()
}
so you can:
let newName = textField.text.capitalizedString
let newUser = User(["name": newName, "stores" : []])
DataManager.sharedInstance.addPerson(newUser)
I think you already know how to create a User object. And that is what you should pass as an argument to your following function. Something like this.
var aUser = User(["name": textField.text. capitalizedString])
DataManager.sharedInstance.addPerson(aUser)
func addPerson(newUser: User) {
users[newUser.name] = newUser
// saveData()
}

Resources