Use JSON data offline Swift 3 - ios

I'm a beginner so please be gentle.
Ok so I successfully parse my JSON from the server and can access all of its contents but know I'm kind of stuck here:
//Functionn for fetching API data
func fetchRates(){
//Set EndPoint URL
let url = URL(string: endPoint)
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil {
print(error as Any)
}
do {
let json = try? JSONSerialization.jsonObject(with: data!, options: [])
let dictonary = json as? [String: Any?]
let ratesJson = dictonary?["rates"] as? [String: Double]
print(ratesJson as Any)
} catch let jsonError {
print(jsonError)
}
}.resume()
}
Now I want to get my ratesJson Dictionary out of this function and actually use its contents to do some calculations, but after hours of trying and searching I still have no idea how.
I just want to:
Get JSON data stored presistently (for offline use)
Update the Data when the app launches and overwrite the old data
The possibilities I've come across so far are CoreData, which seems really confusing and complicated and Realm which I don't really know at all.
Help would be very much appreciated and sorry if this seems really stupid

Since you have the JSON parsing, I would create a object class to store the data using Core Data. There is a lot that goes into core data usage at first, but it is well worth the time investment to learn if you plan on continuing iOS development. Core Data Tuts
I also do not know what you plan on doing with your app, or the purpose, and Core Data might be a little overkill according to your purpose, but you could always save the updated JSON each time, overwriting a file in the device's document's dir, then read the data back in and parse it again.
You can write the JSON data to a outputstream using:
JSONSerialization.writeJSONObject(_ obj: Any, to stream: OutputStream, options opt: JSONSerialization.WritingOptions = [], error: NSErrorPointer)
//Write JSON data into a stream. The stream should be opened and configured.
//The return value is the number of bytes written to the stream, or 0 on error.
//All other behavior of this method is the same as the dataWithJSONObject:options:error: method.
UPDATE: Ok I think I see your question now. There are two ways that you can go about getting your data back to your view controller: 1. Delegate pattern 2.Closures. I personally like using delegates over closures.
With delegates you will need to create a protocol:
protocol SessionDataTaskComplete {
func dataDownloaded(parsedJson : [String : Double])
}
And inside of your URLSession class you will need a class variable to hold that protocol delegate:
var dataTaskCompleteDelegate : SessionDataTaskComplete?
Then you will need to have your view controller implement that protocol:
class MyViewController: UIViewController, SessionDataTaskComplete {
override func viewDidLoad() {
super.viewDidLoad()
//Also assign your viewController as your URLSession's SessionDataTaskComplete delegate
myURLSession.dataTaskCompleteDelegate = self
}
func dataDownloaded(parsedJson : [String : Double]) {
//Handle data as you wish here
}
}
Now in your fetchRates() you can use that delegate to pass the data back to your viewController:
func fetchRates(){
//Set EndPoint URL
let url = URL(string: endPoint)
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil {
print(error as Any)
}
do {
let json = try? JSONSerialization.jsonObject(with: data!, options: [])
let dictonary = json as? [String: Any?]
let ratesJson = dictonary?["rates"] as? [String: Double]
//print(ratesJson as Any)
if (dataTaskCompleteDelegate != nil) {
dataTaskCompleteDelegate!.dataDownloaded(parsedJson: ratesJson)
}
} catch let jsonError {
print(jsonError)
}
}.resume()
}
If you are not comfortable with using the delegate pattern, that is something else I would suggest spending time learning, as it is used all over with the iOS SDK. Try to think of them as an object assigning a duty/task to another object.

Related

Alamofire ignoring closure that sets/handles data

I am using Alamofire to perform a network request to the dummy data source https://jsonplaceholder.typicode.com/posts and render it in my application.
I have a file called NetworkingClient.swift that abstracts most of this logic out and allows is to be reused.
public class NetworkingClient {
typealias WebServiceResponse = ([[String: Any]]?, Error?) -> Void
func execute(_ url: URL, completion: #escaping WebServiceResponse) {
Alamofire.request(url).validate().responseJSON { response in
print(response)
if let error = response.error {
completion(nil, error)
} else if let jsonArray = response.result.value as? [[String: Any]] {
completion(jsonArray, nil)
} else if let jsonDict = response.result.value as? [String: Any] {
completion([jsonDict], nil)
}
}
}
}
I call the execute in a set up function I have in my main view controller file:
func setUpView() {
let networkingClient = NetworkingClient()
let posts_endpoint = "https://jsonplaceholder.typicode.com/posts"
let posts_endpoint_url = URL(string: TEST_URL_STRING)
networkingClient.execute(posts_endpoint_url) { (json, error) in
if let error = error {
print([["error": error]])
} else if let json = json {
print(json)
}
}
}
Where I call this inside viewDidLoad() under super.viewDidLoad()
I've set breakpoints inside the response in closure and I wasn't able to trigger any of them, in fact I think it's skipping the entire thing completely and I don't know why.
I am following this youtube video where the video guide does the exact same thing except their request goes through.
What am I missing?
I am using Swift 4, XCode 10, running on iOS 12.1 and my AlamoFire version is 4.7.
It's all about async stuff.your are declaring NetworkingClient object in func called setupView and Alamofire using .background thread to do stuff.so time executing of networkingClient.execute is not clear and after that setUpView deallocate from memory and all it's objects are gone including NetworkingClient.so for preventing this just declare let networkingClient = NetworkingClient() outside of function

how do I array from a GET request function in viewdidload function in swift

I'm very new to swift, so I will probably have a lot of faults in my code but what I'm trying to achieve is send a GET request to a server with paramters inside a function. I want to use the array I receive from the server in my viewdidload and in other functions but cant seem to find a way to store the array so i can use it. in my function it is filled, but out of my function it is empty
var scenarioArray: Array<Any> = []
let idPersoon = UserDefaults.standard.object(forKey: "idPersoon") as! String
override func viewDidLoad() {
super.viewDidLoad()
ScenarioArray()
print(scenarioArray)
print(self.scenarioArray)
}
func ScenarioArray() {
var request = URLRequest(url: URL(string: "http://dtsl.ehb.be/app&web/ios_php/getAllScenariosByPersoon.php?persoonID="+idPersoon)!)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else { // check for fundamental networking error
print("error=\(error)")
return
}
do {
if let jsonResult = try JSONSerialization.jsonObject(with: data, options: []) as? NSDictionary {
self.scenarioArray = (jsonResult["Scenarios"] as! NSArray) as! Array<Any>
print("ASynchronous\(self.scenarioArray)")
}
} catch let error as NSError {
print(error.localizedDescription)
}
}
task.resume()
}
Your "problem" is that you are trying to GET data from a server, meaning that you are doing a network call.
Now...you don't know how long that network call will take when you launch it, if you are on a good network then it might be fast, but if you are on 3G network it might take a while.
If the call to your server was done synchronously, the result would be that each and every time you'd try to fetch data your code would focus on doing just that, meaning that nothing else would go on... that is not what you want :)
Instead, when you use URLSession, and call task.resume() that method is executed asynchronously, meaning that it starts on another thread in the background where it will fetch data.
In the meantime, your main thread is free to handle UI rendering and so on. At some point in the near future your network call finishes and you now have valid data and must inform whoever needs to know.
So when you do a call to dataTask(with: completionHandler:), what you are actually saying is something along the lines of:
"hey...go fetch this data in the background please, and when you're done, I'd like to execute the code I've passed you here in the completionHandler with the parameters you tell me about".
Hope that makes just a little sense :)
Now...you have this code:
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else { // check for fundamental networking error
print("error=\(error)")
return
}
do {
if let jsonResult = try JSONSerialization.jsonObject(with: data, options: []) as? NSDictionary {
self.scenarioArray = (jsonResult["Scenarios"] as! NSArray) as! Array<Any>
print("ASynchronous\(self.scenarioArray)")
}
} catch let error as NSError {
print(error.localizedDescription)
}
}
That last part of the function call ({ data, response, error in...) is the completionHandler, which is not executed straight away. It is not executed until the retrieval of data has completed.
And therefore when you do a call to your ScenarioArray() function in viewDidLoad, what will happen is that the asynchronous call to fetch data will start in the background and your viewDidLoad will continue what it is doing, meaning that when you say:
print(scenarioArray)
print(self.scenarioArray)
then you can not expect scenarioArray to be populated yet as your task is busy fetching that data in the background.
So...what you need to do, as #vadian says, is to update your UI once the data has been fetched, meaning, in the completionHandler.
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else { // check for fundamental networking error
print("error=\(error)")
return
}
do {
if let jsonResult = try JSONSerialization.jsonObject(with: data, options: []) as? NSDictionary {
self.scenarioArray = (jsonResult["Scenarios"] as! NSArray) as! Array<Any>
print("ASynchronous\(self.scenarioArray)")
//Now you have data, reload the UI with the right scenarioArray
}
} catch let error as NSError {
print(error.localizedDescription)
}
}
Hope that makes sense and helps you.

iOS programming, fetching JSON data

I have added to my project the SwiftyJSON.swift file and I am trying to get some data from the web. Now my project runs but only until the line where I am trying to get the array from json in a dictionary. I cannot understand where the problem is, but I am guessing it has to be something very stupid as I am just in the beginning with learning swift.
I am just trying to print in the console the name of all the movies from that url and after I manage to achieve this performance, I will try to get the summary of the movie as well and then put them in a TableView.
import UIKit
class FirstViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//grab the status code and check if the transfer was successful == 200
let requestURL: NSURL = NSURL(string: "https://itunes.apple.com/us/rss/topmovies/limit=50/json")!
let urlRequest: NSMutableURLRequest = NSMutableURLRequest(URL: requestURL)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(urlRequest) {
(data, response, error) -> Void in
let httpResponse = response as! NSHTTPURLResponse
let statusCode = httpResponse.statusCode
if (statusCode == 200) {
//sort through the stations key and cast the data into an array of dictionaries
do{
let json = try NSJSONSerialization.JSONObjectWithData(data!, options:.AllowFragments)
print("bbbbb")
// From here on, it doesn't print anything anymore
if let movies = json["entry"] as? [[String: AnyObject]] {
print(movies)
print("test")
for movie in movies {
if let name = movie["name"] as? String {
print("mmmm")
print("%# (Built %#)",name)
}
}
}
}catch {
print("Error with Json: \(error)")
}
}
}
task.resume()
entry is an json array, Use .array
if let movies = json["entry"].array {
for movie in movies {
// Do stuff
}
}
Also a general tip. Do not cast the values e.g.
movie["something"] as? String
Rather use the built in features:
movie["something"].string
Update
Looking closer on your code I see that you are acctually not using SwiftyJSON.swift at all.
To use Swifty you parse the json text like this and get a JSON object:
let jsonObj = JSON(data: yourData) // data is a NSData
Please have another look at the documentation:
https://github.com/SwiftyJSON/SwiftyJSON
I think you are reading the section "Why is the typical JSON handling in Swift NOT good?". That section explains the native and "bad" way of managing json in Swift, the real documentation is further down.

JSON parsing and returning data in Swift

i have a Swift code for retrieving and parsing JSON data from the web. everything seems to work fine except one problem i am facing right now. I tried to solve it for some time, have check all sources online.
I have created global dictionary "dicOfNeighbours" that would like to return as a result of parse to other class by calling "func startConnection".
dicOfNeighbours stores parsed data till it goes out of the closing bracket of the:
"let task = session.dataTaskWithRequest(urlRequest) { ... }"
After it stores just nil. And returned result is nil as well.
I have tried to pass "dicOfNeighbours" variable by reference using inout and it is still returns nil result.
there might some solution that i missed.
I would appreciate any help and suggestions.
Thanks!
var dicOfNeighbours = Dictionary<String, [Int]>()
func startConnection() -> Dictionary<String, [Int]>{
let requestURL: NSURL = NSURL(string: "http://www....data.json")!
let urlRequest: NSMutableURLRequest = NSMutableURLRequest(URL: requestURL)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(urlRequest) {
(data, response, error) -> Void in
let httpResponse = response as! NSHTTPURLResponse
let statusCode = httpResponse.statusCode
if (statusCode == 200) {
do{
let json = try NSJSONSerialization.JSONObjectWithData(data!, options:.AllowFragments)
if let neighbours = json["neighbours"] as? [String: Array<Int>] {
var i = 0
for (index, value) in neighbours {
self.dicOfNeighbours[index] = value
}
}
}catch {
print("Error with Json: \(error)")
}
}
}
task.resume()
return self.dicOfNeighbours
}
You are using return instead of using a callback. You are doing your parsing when the network connection is done; asynchronously.
To synchronize it, you'd need to use semaphores, but that is highly discouraged on the main thread.
Instead, do the appropriate things with the result when your completion block is executed. Think of the data task as 'do stuff, come back to me when you're done'.

How To Fetch and Parse JSON Using Swift 2 + XCode 7 + iOS 9 [duplicate]

This question already has answers here:
How to parse JSON in Swift using NSURLSession
(5 answers)
Closed 7 years ago.
Was looking for useful tutorials for networking with Swift 2 and iOS 9, but it seems that topic has no content online. I have watched the WWDC session Networking with NSURLSession But I couldn't adapt the new API of iOS9 to have an asynchronous way to fetch and parse JSON data. What I have tried:
do {
if let url = NSURL(string: "site/api.php?option=fetchOps"),
let data = NSData(contentsOfURL: url),
let jsonResult = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? [[String:AnyObject]] {
}
} catch let error as NSError {
print(error.description)
}
Please share your experience with networking on iOS9 and Swift2, any best practices maybe a Singleton class for networking and parsing maybe an ORM ?
Easy way to make a request for you:
How to make an HTTP request in Swift?
Or If you want to send request by your own:
HTTP POST error Handling in Swift 2
Converting a String to JSON Object:
Just as an example, here I've converted a NSString
First of all convert the NSString to NSDictionary
static func convert(src: NSString) -> NSDictionary {
// convert String to NSData
let data = src.dataUsingEncoding(NSUTF8StringEncoding)
var error: NSError?
// convert NSData to 'AnyObject'
let anyObj: AnyObject?
do {
anyObj = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions(rawValue: 0))
} catch let error1 as NSError {
error = error1
anyObj = nil
}
if(error != nil) {
// If there is an error parsing JSON, print it to the console
print("JSON Error \(error!.localizedDescription)")
//self.showError()
return NSDictionary()
} else {
return anyObj as! NSDictionary
}
}
And then, use it like this
if let name = dictionary["name"] as? String {
self.name = name
}
You can use the converted JSON to make an object like this
import Foundation
class Student {
var name="";
init(dictionary: NSDictionary) {
let _NAME = "name"
if let name = dictionary[_NAME] as? String {
self.name = name
}
}
}
oha, well ... search for async network calls for swift. You will find many things.
But yea... Create an NSMutableRequest, init eith the URL, set the method to GET or POST.
Now make:
let task = NSURLSession.sharedSession().dataTaskWithRequest(yourMutableRequest) {
data, response, error in
// do your stuff here
}
task.resume()
This will run asynchron, so you need to think about a way to handle the data. At first your response data is in the variable data this is a type of NSData. You will need this to create your dictionary:
let dictionaryOrArray = NSJSONSerialization.JSONObjectWithData(data, options:.MutableContainers, error: nil)
Maybe you need to put this in a guard or if let statemant.
Now you need to pass the data to the function caller... you can do this in many ways, i prefer success and failure blocks
You call your Request function and you provide a success and failure block. This blocks will do what should happen in the end:
let successBlock:[AnyObject]->Void = { response in
// handle your dict
}
let failureBlock:NSError->Void = { error in
// error handling
}
YourClass.fetchSomething(someURL, parameters: params, requestMethod: method, success : successBlock, failure: failureBlock)
Something like that. In your request block with data, response, error you just do the following:
If error {
failure(error)
return
}
success(dictionaryOrArray)

Resources