String still optional in Swift - swift

I am trying to assign a string to a label like so:
self.MsgBlock4.text = "\(wsQAshowTagArray![0]["MsgBlock4"]!)"
But it display the label like so Optional(James) how do I remove the Optional() ?
The Dictionary inside the array comes from here:
self.wsQAshowTag(Int(barcode)!, completion: { wsQAshowTagArray in
})
Here is the method:
func wsQAshowTag(tag: Int, completion: ([AnyObject]? -> Void)) {
let requestString = NSString(format: "URL?wsQATag=%d", tag) as String
let url: NSURL! = NSURL(string: requestString)
let task = NSURLSession.sharedSession().dataTaskWithURL(url, completionHandler: {
data, response, error in
do {
let result = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.AllowFragments) as? [AnyObject]
completion(result)
}
catch {
completion(nil)
}
})
task.resume()
}

Since these are optionals and the type can be AnyObject, optional chaining / downcasting and providing a default value using the nil coalescing operator seem the safest approach. If you're expecting the item to be a String:
self.MsgBlock4.text = (wsQAshowTagArray?[0]["MsgBlock4"] as? String) ?? ""
Or I guess for any type that conforms to CustomStringConvertible you can use its description
self.MsgBlock4.text = (wsQAshowTagArray?[0]["MsgBlock4"] as? CustomStringConvertible)?.description ?? ""

Related

Saving JSON into Realm

For my project I want to save data I'm getting from an API in realm and then display it in a table view.
The JSON will look like this:
{"books":[{"author":"Chinua Achebe", "title":"Things Fall Apart","imageLink":"http://books.google.com/books/content?id=plk_nwEACAAJ&printsec=frontcover&img=1&zoom=5&source=gbs_api"}]}
I've tried a few different things, but I can't figure out how to decode and store the JSON properly. I have used this function before for parsing the JSON, but when I add the realm Code I'm getting errors.
My function for fetching the JSON is:
func fetchArticle(){
let urlRequest = URLRequest(url: URL(string: "https:/mocki.io/v1/89aa9fe9-fdba-463f-99b3-5d8b6bc1d32e")!)
let task = URLSession.shared.dataTask(with: urlRequest) { (data,response,error) in
if error != nil {
print(error)
return
}
self.books = [Books]()
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! [String: AnyObject]
if let booksFromJson = json["books"] as? [[String : AnyObject]]{
for bookFromJson in booksFromJson {
let book = Books()
if let title = bookFromJson["title"] as? String, let author = bookFromJson["author"] as? String, let imageLink = bookFromJson["imageLink"] as? String {
book.author = author
book.title = title
book.imageLink = imageLink
}
self.books?.append(book)
let realm = try! Realm()
for books in bookFromJson {
try! realm.write {
realm.add(books, update: .all)
}
}
DispatchQueue.main.async {
self.tableview.reloadData()
}
} catch let error {
print(error)
}
}
task.resume()
}
}
}
This is my Struct:
class Books: Object, Decodable {
#objc dynamic var author: String?
#objc dynamic var imageLink: String?
#objc dynamic var title: String?
convenience init(author: String, imageLink: String, title: String) {
self.init()
self.author = author
self.imageLink = imageLink
self.title = title
}
override static func primaryKey() -> String? {
return "author"
}
private enum CodingKeys: String, CodingKey {
case author
case imageLink
case title
}
}
This are the errors im getting in the func:
Invalid conversion from throwing function of type '(Data?, URLResponse?, Error?) throws -> Void' to non-throwing function type '(Data?, URLResponse?, Error?) -> Void'
'let' declarations cannot be computed properties
There are probably a 100 different ways to map your json to a realm object but let's keep it simple. First I assume your incoming json may be several books so it would look like this
let jsonStringWithKey = """
{
"books":
[{
"author":"Chinua Achebe",
"title":"Things Fall Apart",
"imageLink":"someLink"
},
{
"author":"another author",
"title":"book title",
"imageLink":"another link"
}]
}
"""
So encode it as data
guard let jsonDataWithKey = jsonStringWithKey.data(using: .utf8) else { return }
Then, using JSONSerialization, map it to an array. Keeping in mind the top level object is "books" and AnyObject will be all of the child data
do {
if let json = try JSONSerialization.jsonObject(with: jsonDataWithKey) as? [String: AnyObject] {
if let bookArray = json["books"] as? [[String:AnyObject]] {
for eachBook in bookArray {
let book = Book(withBookDict: eachBook)
try! realm.write {
realm.add(book)
}
}
}
}
} catch {
print("Error deserializing JSON: \(error)")
}
and the Realm object is
class Book: Object, Codable {
#objc dynamic var author = ""
#objc dynamic var title = ""
#objc dynamic var imageLink = ""
convenience init(withBookDict: [String: Any]) {
self.init()
self.author = withBookDict["author"] as? String ?? "No Author"
self.title = withBookDict["title"] as? String ?? "No Title"
self.imageLink = withBookDict["imageLink"] as? String ?? "No link"
}
}
Again, there are a LOT of different ways of handling this so this is kind of the basics that can be expanded on.
As a suggestion, Realm Results are live-updating objects that also have corresponding events. So a neat thing you can do is to make a results object your tableView datasource and add an observer to it.
Results objects work very much like an array.
As books are added, updated or deleted from realm, the results object will reflect those changes and an event will be fired for each one - that makes keeping your tableView updated very simple.
So in your viewController
class ViewController: NSViewController {
var bookResults: Results<PersonClass>? = nil
#IBOutlet weak var bookTableView: NSTableView!
var bookToken: NotificationToken?
self.bookResults = realm.objects(Book.self)
and then
override func viewDidLoad() {
super.viewDidLoad()
self.bookToken = self.bookResults!.observe { changes in
//update the tableView when bookResults change
You have an error in terms of where you've placed your catch statement -- it should be in line with the do { } block. This might be easier to see if you format/indent your code (Ctrl-i in Xcode).
func fetchArticle(){
let urlRequest = URLRequest(url: URL(string: "https:/mocki.io/v1/89aa9fe9-fdba-463f-99b3-5d8b6bc1d32e")!)
let task = URLSession.shared.dataTask(with: urlRequest) { (data,response,error) in
if let error = error {
print(error)
return
}
self.books = [Books]()
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! [String: AnyObject]
if let booksFromJson = json["books"] as? [[String : AnyObject]]{
for bookFromJson in booksFromJson {
let book = Books()
if let title = bookFromJson["title"] as? String, let author = bookFromJson["author"] as? String, let imageLink = bookFromJson["imageLink"] as? String {
book.author = author
book.title = title
book.imageLink = imageLink
}
self.books?.append(book)
let realm = try! Realm()
for books in bookFromJson {
try! realm.write {
realm.add(books, update: .all)
}
}
DispatchQueue.main.async {
self.tableview.reloadData()
}
}
}
} catch { //<-- no need for `let error`
print(error)
}
}
task.resume() //<-- Moved outside the declaration
}

How do I convert a WKScriptMessage.body to a struct?

I set up the WKScriptMessageHandler function userContentController(WKUserContentController, didReceive: WKScriptMessage) to handle JavaScript messages sent to the native app. I know ahead of time that the message body will always come back with the same fields. How do I convert the WKScriptMessage.body, which is declared as Any to a struct?
What about safe type casting to, for example, dictionary?
let body = WKScriptMessage.body
guard let dictionary = body as? [String: String] else { return }
Or as an option, you can send body as json string and serialise it using codable.
struct SomeStruct: Codable {
let id: String
}
guard let bodyString = WKScriptMessage.body as? String,
let bodyData = bodyString.data(using: .utf8) else { fatalError() }
let bodyStruct = try? JSONDecoder().decode(SomeStruct.self, from: bodyData)
In SwiftUI message.body is String object. You can convert the body in dictionary like this:
if let bodyString = message.body as? String {
let data = Data(bodyString.utf8)
do {
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
guard let body = json["body"] as? [String: Any] else {
return
}
//use body object
}
} catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
}
}
Your defined struct
struct EventType:Codable{
let status: Int!
let message: String!
}
WKScriptMessageHandler protocol method
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
do {
let jsonData = try JSONSerialization.data(withJSONObject: message.body)
let eventType = try JSONDecoder().decode(EventType.self, from: jsonData)
} catch {
print("fatalError")
}
}

Dictionary extension in Swift is not recognized

I am trying to extend Dictionary with the following code:
extension Dictionary where Key: ExpressibleByStringLiteral, Value: AnyObject {
var jsonString: String? {
if let dict = (self as AnyObject) as? Dictionary<String, AnyObject> {
do {
let data = try JSONSerialization.data(withJSONObject: dict, options: JSONSerialization.WritingOptions(rawValue: UInt.allZeros))
if let string = String(data: data, encoding: String.Encoding.utf8) {
return string
}
} catch {
print(error)
}
}
return nil
}
}
Where I write something like:
let x: [String: String] = ["": ""]
x.jsonString
I get this error:
Value of type '[String: String]' as no member 'jsonString'
Anything I am missing?
There is no need to constrain Dictionary Value type at all:
extension Dictionary where Key: ExpressibleByStringLiteral {
var jsonString: String? {
guard let data = try? JSONSerialization.data(withJSONObject: self)
else { return nil }
return String(data: data, encoding: .utf8)
}
}
Since String is a value type , look for it's
public struct String {
and AnyObject refers to any instance of a class only , and is equivalent to id in Objective-C , so this declaration of x
[String: String] doesn't fit with [String: AnyObject]
because Any refers to any instance of a class, struct, or enum so it'll fit perfectly

Access a dictionary (JSON format) in a function with a flexible variable

I can't find a solution for my programming issue. I want to create a function which will access a dictionary (data is coming from the internet) an I need the following code very often:
if let job_dict = json["data"] as? [String:Any] {
It would be great to be more flexible and to change the ["data"] part to a variable or something like that:
func get_JSON(Link: String, Value: String) -> [Double] {
let url = Link
let request = URLRequest(url: URL(string: url)!)
let myUrl = URL(string: basePath)!
var ValuestoReturn = [Double]()
let task = URLSession.shared.dataTask(with: myUrl) { (data, response, error) in
if let data = data {
do {
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String:Any] {
print(Value.description)
if let job_dict = json[Value.description] as? [String:Any] {
print(job_dict)
}
}
} catch {
}
}
}
task.resume()
json[Value.description] is always wrong and the json["data"] thing is always true.
Don't use Value.Description use just Value
print(Value)
if let job_dict = json[Value] as? [String:Any] {...}
P.D: Don't use "Value" for a variable's name. The first letter in uppercase is for types. You can use value instead.

Work with escaping closure value

Have this to fill my full fullJsonData array
and i want to work with value after closure.
func getNewsAsDic(completionHandler: #escaping ([fullJsonData]) -> [fullJsonData]) {
let session = URLSession.shared
let dataTask = session.dataTask(with: requestNewsForToday()) { data, _, error in
if error == nil {
if let dic = try? JSONSerialization.jsonObject(with: data!, options: []) as? NSDictionary {
var dataArray = [fullJsonData]()
let jdata = dic?["articles"] as! [[String:Any]]
for item in jdata {
let data = fullJsonData(author: item["author"] as? String,
description: item["description"] as? String,
publishedAt: item["publishedAt"] as? String,
title: item["title"] as? String,
url: item["url"] as! String,
imageURL: item["urlToImage"] as? String)
print(item["author"] as! String)
print(item["description"] as! String)
print(item["title"] as! String)
print(item["urlToImage"] as! String)
print(item["url"] as! String)
print(item["urlToImage"] as! String)
print(item["publishedAt"] as! String)
dataArray.append(data)
}
completionHandler(dataArray)
print(jdata.count)
}
} else {
return
}
}
dataTask.resume()
}
So problem is:
1)The xcode error, what Result of call is unused, but produces '[fullJsonData]'
2)Then I am trying to use it in project like this:
var dataArray = [fullJsonData]()
dataArray = NetworkManager.shared.getNewsAsDic(completionHandler: { dict in
return dict
})
Had an error too: Cannot assign value of type '()' to type '[fullJsonData]'
So is this real to take and use values from closure?
You should change the signature of your completion handler from:
func getNewsAsDic(completionHandler: #escaping ([fullJsonData]) -> [fullJsonData]) {
to:
func getNewsAsDic(completionHandler: #escaping ([fullJsonData]) -> Void) {
And then change your call to:
NetworkManager.shared.getNewsAsDic(completionHandler: { myArray in
// Do whatever you want with myArray here
})
Also, you should rename getNewsAsDic because you're really returning an array.
it seems you have to use
var dataArray: [fullJsonData] = [] instead of your code.