Background
- our users complain about the performance of MDT
- addition of a new feature that could benefit from the performance gain
Problem: Parse json in Swift
Parse json in Swift
Solution
- A look at WebView framework in iOS 8
- Complete guide to implementing WKWebView
- Communicating between Javascript in WKWebView and Native Code
The simple web app
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
<script>
function housePrice() {
var houseStr = JSON.stringify(h1);
var h1 = {name:"Central Perk, NYC, NY", range: [2,3,4,5]};
function houseListing() {
window.webkit.messageHandlers.sendPrice.postMessage(houseStr);
}
var houses = [h1,h2];
var h1 = {name:"13 Spooner St", range: [2,3,4,5]};
var h2 = {name:"742 Evergreen Tc", range: [7,8]};
</script>
var housesStr = JSON.stringify(houses);
window.webkit.messageHandlers.sendListing.postMessage(housesStr);
}
</head>
<button onclick="houseListing()"> Send House listing </button>
<body ng-app="starter">
<div id="table">
<button onclick="housePrice()"> Send House Price </button>
</div>
</body>
</html>
- name of type String
- range which is an Integer array
import UIKit
import WebKit
class ViewController: UIViewController, WKUIDelegate, WKScriptMessageHandler {
var webView: WKWebView!
var oldWebView: UIWebView!
override func loadView() {
let webConfiguration = WKWebViewConfiguration()
let controller = WKUserContentController()
controller.add(self, name: "sendPrice")
controller.add(self, name: "sendListing")
webConfiguration.userContentController = controller
webConfiguration.preferences.setValue(true, forKey: "allowFileAccessFromFileURLs")
webView = WKWebView(frame: .zero, configuration: webConfiguration)
webView.uiDelegate = self
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
let pathString = Bundle.main.path(forResource: "test", ofType: "html", inDirectory:"www")
let url = URL(fileURLWithPath: pathString!)
let req = URLRequest(url: url)
webView.load(req)
}
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
extractJsonObjFromScriptMsg(message: message)
}
/* object sent
name: String
range: Int array ([Int])
*/
func extractJsonObjFromScriptMsg(message: WKScriptMessage) {
//get the object first
var housePrices = [HousePrice]()
//step 1: check if the obj is a string
if let objStr = message.body as? String {
//step 2: convert the string to Data
let data: Data = objStr.data(using: .utf8)!
do {
let jsObj = try JSONSerialization.jsonObject(with: data, options: .init(rawValue: 0))
if let jsonObjDict = jsObj as? Dictionary<String, Any> {
let housePrice = HousePrice(dict: jsonObjDict)
housePrices.append(housePrice)
} else if let jsonArr = jsObj as? [Dictionary<String, Any>] {
for jsonObj in jsonArr {
let hPrice = HousePrice(dict: jsonObj)
housePrices.append(hPrice)
}
}
} catch _ {
print("having trouble converting it to a dictionary")
}
}
}
}
}
ExtractJsonObjFromScriptMsg method
Step 1: Determine if it’s a json object
var housesStr = JSON.stringify(houses);
if let objStr = message.body as? String {
//step 2: convert the string to Data
let data: Data = objStr.data(using: .utf8)!
do {
let jsObj = try JSONSerialization.jsonObject(with: data, options: .init(rawValue: 0))
if let jsonObjDict = jsObj as? Dictionary<String, Any> {
let housePrice = HousePrice(dict: jsonObjDict)
housePrices.append(housePrice)
} else if let jsonArr = jsObj as? [Dictionary<String, Any>] {
for jsonObj in jsonArr {
let hPrice = HousePrice(dict: jsonObj)
housePrices.append(hPrice)
}
}
} catch _ {
print("having trouble converting it to a dictionary")
}
}
- First we check whether the message.body is indeed a string or not? I know we sent a string for sure, but it’s always safe to deal with Optionals in Swift this way.
- Next we try to determine is it’s a JSONObject via JSONSerialization
- Then we check if we received a Dictionary object, remember the HousePrices json object has a string property called name and an integer array called range. The json object returned by the JSONSerialization class is a dictionary of type String and Any
- The next step is to send the dictionary object to a HousePrice class in Swift which brings us to another piece of code not shared above…
class HousePrice {
var name = ""
var range = [Int]()
struct Keys {
static var NAME = "name"
static var RANGE = "range"
}
convenience init(dict: Dictionary<String,Any>) {
self.init()
if let name = dict[Keys.NAME] as? String {
self.name = name
}
if let range = dict[Keys.RANGE] as? [Int] {
self.range = range
}
}
}
Summary
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
<script src="js/scripts.js"></script>
</head>
<body ng-app="starter">
<div id="table">
<button onclick="housePrice()"> Send House Price </button>
<button onclick="houseListing()"> Send House listing </button>
</div>
</body>
</html>
scripts.js
function housePrice() {
var h1 = {name:"Central Perk, NYC, NY", range: [2,3,4,5]};
var houseStr = JSON.stringify(h1);
window.webkit.messageHandlers.sendPrice.postMessage(houseStr);
}
function houseListing() {
var h1 = {name:"13 Spooner St", range: [2,3,4,5]};
var h2 = {name:"742 Evergreen Tc", range: [7,8]};
var houses = [h1,h2];
var housesStr = JSON.stringify(houses);
console.log(housesStr);
window.webkit.messageHandlers.sendListing.postMessage(housesStr);
}
import UIKit
import WebKit
class ViewController: UIViewController, WKUIDelegate, WKScriptMessageHandler {
var webView: WKWebView!
var oldWebView: UIWebView!
var eventFunctions = Dictionary<String, (String) -> Void>()
override func loadView() {
let webConfiguration = WKWebViewConfiguration()
let controller = WKUserContentController()
controller.add(self, name: "sendPrice")
controller.add(self, name: "sendListing")
webConfiguration.userContentController = controller
webConfiguration.preferences.setValue(true, forKey: "allowFileAccessFromFileURLs")
webView = WKWebView(frame: .zero, configuration: webConfiguration)
webView.uiDelegate = self
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
let pathString = Bundle.main.path(forResource: "test", ofType: "html", inDirectory:"www")
let url = URL(fileURLWithPath: pathString!)
let req = URLRequest(url: url)
webView.load(req)
}
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
extractJsonObjFromScriptMsg(message: message)
}
func extractJsonObjFromScriptMsg(message: WKScriptMessage) {
//get the object first
var housePrices = [HousePrice]()
//step 1: check if the obj is a string
if let objStr = message.body as? String {
//step 2: convert the string to Data
let data: Data = objStr.data(using: .utf8)!
do {
let jsObj = try JSONSerialization.jsonObject(with: data, options: .init(rawValue: 0))
if let jsonObjDict = jsObj as? Dictionary<String, Any> {
let housePrice = HousePrice(dict: jsonObjDict)
housePrices.append(housePrice)
} else if let jsonArr = jsObj as? [Dictionary<String, Any>] {
for jsonObj in jsonArr {
let hPrice = HousePrice(dict: jsonObj)
housePrices.append(hPrice)
}
}
} catch _ {
print("having trouble converting it to a dictionary")
}
}
}
}
webConfiguration.preferences.setValue(true, forKey: "allowFileAccessFromFileURLs")
Conclusion
Anyway that wraps this post and until next time, if you find any of my posts useful and want to support me, try one of our products and if leave an App Store review that would really help us.
[appbox appstore 1020072048]
[appbox appstore 1066820078]
[appbox appstore 1367294518]
0 Comments