How to build a URL by using Dictionary in Swift - ios

So I am messing around with the iMessages Framework, and in order to send data to another user the data must be sent as a URLComponents URL. However I can't figure out how to send a dictionary to use as the messages.url value.
func createMessage(data: dictionary) {
/*
The Dictionary has 3 values, 1 string and two arrays.
let dictionary = ["title" : "Title Of Dictionary", "Array1" : [String](), "Array2" : [String]() ] as [String : Any]
*/
let components = URLComponents()
let layout = MSMessageTemplateLayout()
layout.image = UIImage(named: "messages-layout.png")!
layout.imageTitle = "\(dictionary["title"])"
let message = MSMessage()
message.url = components.url!
message.layout = layout
self.activeConversation?.insert(message, completionHandler: { (error: Error?) in
print("Insert Message")
})
}
Does anybody know how I can send the dictionary values as URLQueryItems in the URLComponents to save as the message.url ?
PS: I was wondering wether it would be possible to create an extension for the URL to store the dictionary in, that is what I am unsuccessfully trying atm.

Here is a code snippet to convert dictionary to URLQueryItems:
let dictionary = [
"name": "Alice",
"age": "13"
]
func queryItems(dictionary: [String:String]) -> [URLQueryItem] {
return dictionary.map {
// Swift 3
// URLQueryItem(name: $0, value: $1)
// Swift 4
URLQueryItem(name: $0.0, value: $0.1)
}
}
var components = URLComponents()
components.queryItems = queryItems(dictionary: dictionary)
print(components.url!)

You can use the following URLComponents extension:
extension URLComponents{
var queryItemsDictionary : [String:Any]{
set (queryItemsDictionary){
self.queryItems = queryItemsDictionary.map {
URLQueryItem(name: $0, value: "\($1)")
}
}
get{
var params = [String: Any]()
return queryItems?.reduce([:], { (_, item) -> [String: Any] in
params[item.name] = item.value
return params
}) ?? [:]
}
}
}
And set the url components:
var url = URLComponents()
url.queryItemsDictionary = ["firstName" : "John",
"lastName" : "Appleseed"]

You can use iMessageDataKit library. It's a tiny, useful MSMessage extension for setting/getting Int, Bool, Float, Double, String and Array values for keys.
It makes setting and getting data really easy like:
let message: MSMessage = MSMessage()
message.md.set(value: 7, forKey: "user_id")
message.md.set(value: "john", forKey: "username")
message.md.set(values: ["joy", "smile"], forKey: "tags")
print(message.md.integer(forKey: "user_id")!)
print(message.md.string(forKey: "username")!)
print(message.md.values(forKey: "tags")!)
(Disclaimer: I'm the author of iMessageDataKit)

Related

Two different values from the same kSecAttrAccount in Keychain

I am really puzzled by this.
I have a class which initializes like this:
class MyClass {
let storage = Storage.sharedService
static let sharedInstance = MyClass()
fileprivate init() {
storage.dumpKeychain()
v1 = storage.loadNonimportantValue()
print("V1: \(v1 ?? "nil")")
v2 = storage.loadImportantValue()
print("V2: \(v2 ?? "nil")")
}
}
storage.dumpKeychain() is function (taken from internet) that prints content of the Keychain accessible to my app
func dumpKeychain() {
let query: [String: Any] = [
kSecClass as String : kSecClassGenericPassword,
kSecReturnData as String : kCFBooleanFalse!,
kSecReturnAttributes as String : kCFBooleanTrue!,
kSecReturnRef as String : kCFBooleanTrue!,
kSecMatchLimit as String: kSecMatchLimitAll
]
var result: AnyObject?
let lastResultCode = withUnsafeMutablePointer(to: &result) {
SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0))
}
var values = [String:String]()
if lastResultCode == noErr {
let array = result as? Array<Dictionary<String, Any>>
for item in array! {
if let key = item[kSecAttrAccount as String] as? String, let value = item[kSecValueData as String] as? Data {
values[key] = String(data: value, encoding:.utf8)
}
}
}
print(values)
}
And storage.loadImportantValue() is function that loads a value from given kSecAttrAccount ("Important")
fileprivate func loadImportantValue() -> String? {
var readQuery : [String : Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "Important",
kSecReturnData as String: kCFBooleanTrue!
]
var storedData : AnyObject?
_ = SecItemCopyMatching(readQuery as CFDictionary, &storedData);
return String(data: storedData as? Data, encoding: String.Encoding.utf8)
}
What I see in the logs is that dumpKeychain returns:
[..., "Nonimportant": "correct value", "Important": "correct value", ...]
And the line print(value) in MyClass initializer prints:
V1: "correct value"
V2: "incorrect value"
How is it possible that these two calls made almost one after another return two different values from the same spot in Keychain for V2 and at the same time V1 is ok?
Thanks
Ok, I found the problem.
There really are two different values of the data.
This class is part of a framework which after updating was doing this. It is so that the prior version of framework was using another framework to manage keychain and that framework stored data with more attributes then my new version. And what I thought was replacing the data was actually adding new entry with less attributes.
So at the end after running the legacy code and then updating it with newer version there were two entries of the data at the "same" key.

Encode url string to Dictionary In my case: Some value have = inside

I'm trying to convert decode to dictionary from the below url encoded string. The normal method of doing so is given below. In my case it is not working. Also i need to remove any character like \u{05}
let params = str.components(separatedBy: "&").map({
$0.components(separatedBy: "=")
}).reduce(into: [String:String]()) { dict, pair in
if pair.count == 2 {
dict[pair[0]] = pair[1]
}
}
My url encoded string is
"id=sfghsgh=sbfsfhj&name=awsjdk_fs\u{05}"
I'm expecting result as
{
"id" = "sfghsgh=sbfsfhj",
"name" = "awsjdk_fs"
}
How it is possible to achive?
Piggyback on URLComponents:
var components = URLComponents()
components.query = "id=sfghsgh=sbfsfhj&name=awsjdk_fs"
components.queryItems
// => Optional([id=sfghsgh=sbfsfhj, name=awsjdk_fs])
let list = components.queryItems?.map { ($0.name, $0.value) } ?? []
// [("id", Optional("sfghsgh=sbfsfhj")), ("name", Optional("awsjdk_fs"))]
let dict = Dictionary(list, uniquingKeysWith: { a, b in b })
// ["name": Optional("awsjdk_fs"), "id": Optional("sfghsgh=sbfsfhj")]
If you need a [String: String] rather than [String: String?]:
let list = components.queryItems?.compactMap { ($0.name, $0.value) as? (String, String) } ?? []
// [("id", "sfghsgh=sbfsfhj"), ("name", "awsjdk_fs")]
let dict = Dictionary(list, uniquingKeysWith: { a, b in b })
// ["name": "awsjdk_fs", "id": "sfghsgh=sbfsfhj"]

how to add Json value into model Array to display into tableview in swift

I'm using the tableview to display the Two Json value but the problem is I cant add value into model struct to displaying into tableview using two Api's. i want to show percentage value in one of the cell label and
here is my json
[
{
"Percentage": 99.792098999,
}
]
my second json value
{
"Categories": [
"Developer",
"ios "
],
"Tags": [
{
"Value": "kishore",
"Key": "Name"
},
{
"Value": "2",
"Key": "office"
},
]
}
and i need show the Categories value in Categories label in tableview
value and key on tableview
here is my Struct
struct info: Decodable {
let Categories: String?
let Tags: String?
let Value: String?
let Key: String?
var Name: String?
let percentage: Double?
}
here its my code
var List = [info]()
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers)
print(json as Any)
guard let jsonArray = json as? [[String: Any]] else {
return
}
print(jsonArray)
for dic in jsonArray{
guard let per = dic["percentage"] as? Double else { return }
print(per)
}
and second json
if let array = json["Tags"] as? [[String: String]] {
for dict in array {
let key = dict["Key"]
let value = dict["Value"]
switch key {
case "office":
case "Name":
default:
break;
}
}
here is my cell for row indexpath
cell.Categories.text = list[indexpath.row].percentage
cell.Name.text = list[indexpath.row].name
cell.office.text = list[indexpath.row].office
Please use Swift 4 Codable protocol to decode the value from JSON.
//1.0 Create your structures and make it conform to Codable Protocol
struct Tags: Codable{
var Key: String
var Value: String
}
struct Sample: Codable{
var Categories: [String]
var Tags: [Tags]
}
In your method, perform below steps:
//2.0 Get your json data from your API. In example below, i am reading from a JSON file named "Sample.json"
if let path = Bundle.main.path(forResource: "Sample", ofType: "json") {
do {
let jsonData = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
do {
//3.0 use JSONDecoder's decode method to decode JSON to your model.
let sample = try JSONDecoder().decode(Sample.self, from: jsonData)
//4.0 User the "sample" model to do your stuff. Example, printing some values
print("Sample.Category = \(sample.Categories)")
print("Sample.Name = \(sample.Tags[0].Value)")
print("Sample.Office = \(sample.Tags[1].Value)")
} catch let error {
print("Error = \(error)")
}
} catch {
// handle error
}
}
I prefer to use Codable all the time with JSON even for simpler types so for percentage I would do
struct ItemElement: Decodable {
let percentage: Double
enum CodingKeys: String, CodingKey {
case percentage = "Percentage"
}
}
and we need to keep these values in a separate array, declared as a class property
let percentageList: [Double]()
and json encoding would then be
let decoder = JSONDecoder()
do {
let result = try decoder.decode([ItemElement].self, from: data)
percentageList = result.map { item.percentage }
} catch {
print(error)
}
Similar for the second part
struct Item: Decodable {
let categories: [String]
let tags: [Tag]
enum CodingKeys: String, CodingKey {
case categories = "Categories"
case tags = "Tags"
}
}
struct Tag: Decodable {
let value, key: String
enum CodingKeys: String, CodingKey {
case value = "Value"
case key = "Key"
}
}
use a dictionary for the result, again as a class property
var values = [String: String]()
and the decoding
let decoder = JSONDecoder()
do {
let result = try decoder.decode(Item.self, from: data)
for item in result.tags {
values[item.key] = values.item.value
}
} catch {
print(error)
}
and then in the cell for row code
cell.Categories.text = percentageList[indexpath.row].percentage
cell.Name.text = values["name"]
cell.office.text = values["office"]
Note that this last code looks very strange since you don't have an array of name/office values judging by your json. Maybe you have simplified it some way but the code above is the best I can do with the information given even if it possibly wrong

Contextual type 'Void' cannot be used with dictionary literal

This Error comes up after I relaunched my Project without any changes, never heard of it before.
func toDictionary() -> [String : Any] {
let newPostRef = Database.database().reference().child("posts").childByAutoId()
let newPostKey = newPostRef.key
// 1. save image
if let imageData = image.jpegData(compressionQuality: 0.5) {
let storage = Storage.storage().reference().child("images/\(newPostKey)")
storage.putData(imageData).observe(.success, handler: { (snapshot) in
self.downloadURL = snapshot.metadata?.downloadURL()?.absoluteString
return [ <- The Error appears here!!!
"text" : text,
"imageDownloadURL" : downloadURL,
"numberOfLikes" : numberOfLikes,
"numberOfDislikes" : numberOfDislikes
]
}
)}
}
Maybe the following lines of Code help, as I only read something that this Error occurs because of any false String or something like that...
var text: String = ""
private var image: UIImage!
var downloadURL: String?
var numberOfLikes = 0
var numberOfDislikes = 0
let ref: DatabaseReference!
init(snapshot: DataSnapshot){
ref = snapshot.ref
if let value = snapshot.value as? [String : Any] {
text = value["text"] as! String
downloadURL = value["imageDownloadURL"] as? String
numberOfLikes = value["numberOfLikes"] as! Int
numberOfDislikes = value["numberOfDislikes"] as! Int
}
}
The issue is that the Firebase function observe is asynchronous, so you cannot use the synchronous return statement to return values from it. You need to declare your function to use a completion handler.
func toDictionary(completion: #escaping ([String:Any])->()) {
let newPostRef = Database.database().reference().child("posts").childByAutoId()
let newPostKey = newPostRef.key
// 1. save image
if let imageData = image.jpegData(compressionQuality: 0.5) {
let storage = Storage.storage().reference().child("images/\(newPostKey)")
storage.putData(imageData).observe(.success, handler: { (snapshot) in
self.downloadURL = snapshot.metadata?.downloadURL()?.absoluteString
let snapShotDict = ["text" : text, "imageDownloadURL" : downloadURL, "numberOfLikes" : numberOfLikes, "numberOfDislikes" : numberOfDislikes ]
completion(snapShotDict)
}
)}
}
Then access it like this:
toDictionary(completion: { dict in
// You can only use `dict` inside the completion handler closure
print(dict)
})

Adding query parameter to the GET url in iOS in Swift3

I have an URL with
https://api.asiancar.com/api/applicationsettings
It is basically a GET url so I need to pass a boolean "isMobile" and timestamp as query parameters . How to achieve this as the ultimate URL after passing the query will look like this:
https://api.asiancar.com/api/applicationsettings?timestamp=111122244556789879&isMobile=true
let queryItems = [
NSURLQueryItem(timestamp: "1234568878788998989", isMobile: true),
NSURLQueryItem(timestamp: "1234568878788998989", isMobile: true)
]
let urlComps = NSURLComponents(string: "www.api.asiancar.com/api/applicationsettings")!
urlComps.queryItems = queryItems
let URL = urlComps.URL!
Am I doing right or any other modification ? please tell.
Unless you have subclassed NSURLQueryItem, then your init method is not correct. Per Apple's documentation for NSURLQueryItem, the init method signature is:
init(name: String, value: String?)
This means your query items should be created like this:
let queryItems = [NSURLQueryItem(name: "timestamp" value: "1234568878788998989"), NSURLQueryItem(name: "isMobile", value: "true")]
This will properly add them to the url in the format you are expecting.
You can try an alternative way by using String
let baseUrlString = "https://api.asiancar.com/api/"
let timeStamp = 1234568878788998989
let isMobile = true
let settingsUrlString = "\(baseUrlString)applicationsettings?timestamp=\(timeStamp)&isMobile=\(isMobile)"
print(settingsUrlString)
let url = URL(string: settingsUrlString)
output : https://api.asiancar.com/api/applicationsettings?timestamp=1234568878788998989&isMobile=true
Try this :
let API_PREFIX = "www.api.asiancar.com/api/applicationsettings"
var url : URL? = URL.init(string: API_PREFIX + queryItems(dictionary: [name: "isMobile", value: "true"] as [String : Any]))
func queryItems(dictionary: [String:Any]) -> String {
var components = URLComponents()
print(components.url!)
components.queryItems = dictionary.map {
URLQueryItem(name: $0, value: $1 as String)
}
return (components.url?.absoluteString)!
}

Resources