In my mainviewcontroller's viewDidLoad() method I am firing few processes.
let api = JsonData() // create instance of JsonData class
api.loadJson(nil) // this method receives json and writes it in the file.
//Function to find file location
func getFileURL(fileName: String) -> NSURL {
let manager = NSFileManager.defaultManager()
let dirURL = manager.URLForDirectory(.DocumentDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: false, error: nil)
return dirURL!.URLByAppendingPathComponent(fileName)
}
//file with this name was created in loadJson method
let filePath = getFileURL("JSONFromServer.txt").path!
//trying to read this file
let newDictionary = NSKeyedUnarchiver.unarchiveObjectWithFile(filePath) as! [Dictionary <String,AnyObject> ]
I was expecting that file will be created in api.loadJson and straight after it I will be able to read it. But for some reason when I use debugger I see that there is no file yet. And only when program goes out from viewdidload method I can see that file was created.
I wonder if program flow is specific?
Here is my JsonData class:
import Foundation
class JsonData {
var bankDict = [Dictionary <String,AnyObject> ]()
var arrayOfBanks: [[String:AnyObject]] = []
func loadJson(completion: ((AnyObject) -> Void)!) {
var urlString = "http://almaz.com/getjson.php"
let session = NSURLSession.sharedSession()
let sourceUrl = NSURL(string: urlString)
var task = session.dataTaskWithURL(sourceUrl!){
(data, response, error) -> Void in
if error != nil {
println(error.localizedDescription)
} else {
var error: NSError?
var jsonData = NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers, error: &error) as! NSArray
//println(jsonData)
//convert from JSON into array of dictionaries
var rates = [ExchangeRates]() //instance of class Rate
var bnkDct = ["bank": "", "currency": "","buyrate": "", "sellrate": ""] //template
var indx : Int = 0 //index for iteration
for rate in jsonData{
let rate = ExchangeRates(rate as! NSDictionary)
rates.append(rate)
bnkDct["bank"] = rates[indx].bank
bnkDct["buyrate"] = rates[indx].buyRate
bnkDct["sellrate"] = rates[indx].sellRate
bnkDct["currency"] = rates[indx].currency
self.bankDict.append(bnkDct)
indx += 1
}
//println(self.bankDict)
//Store data in file
//File path and name
if let dirs : [String] = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.AllDomainsMask, true) as? [String] {
let dir = dirs[0] //documents directory
let filePath = dir.stringByAppendingPathComponent("JSONFromServer.txt");
NSKeyedArchiver.archiveRootObject(self.bankDict, toFile: filePath)
// println(filePath)
}
}
}
task.resume()
}
}
Your loadJson function takes a completion handler as a parameter but your loadJson function doesn't call it when it is done. Fix that first.
Then in your viewDidLoad function, pass in a completion handler when you call loadJson. The completion code you provide is where you should read and process the file saved by loadJson.
Related
How to access my Model from ViewController and use the Model data to load in table view????
Source Code Link
My ViewController looks like this
import UIKit
class ViewController: UIViewController {
var cclm: CountryCodeListModel?
override func viewDidLoad() {
super.viewDidLoad()
Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(hello), userInfo: nil, repeats: true)
readLocalJSONFile(forName: "countryList")
// Do any additional setup after loading the view.
}
override func viewDidAppear(_ animated: Bool) {
}
#objc func hello()
{
print(cclm?.data?[0].flag)
}
}
and my model class look like this
struct CountryCodeList : Decodable {
var alpha2Code: String?
var alpha3Code: String?
var flag : String?
var name : String?
var code : String?
}
public struct CountryCodeListModel : Decodable {
var data : [CountryCodeList]?
}
var cclm: CountryCodeListModel?
//Method to load json
func readLocalJSONFile(forName name: String) {
do {
if let filePath = Bundle.main.path(forResource: name, ofType: "json") {
let fileUrl = URL(fileURLWithPath: filePath)
let data = try Data(contentsOf: fileUrl)
if let countryCodeObject = parse(jsonData: data) {
cclm = countryCodeObject
print(cclm?.data?[1].alpha2Code ?? "") //Printing Correct Value
}
}
} catch {
print("error: \(error)")
}
}
func parse(jsonData: Data) -> CountryCodeListModel?{
var dataArray : [Dictionary<String,Any>] = [[:]]
var country = Dictionary<String,Any>()
var modelData = Dictionary<String,Any>()
do {
// make sure this JSON is in the format we expect
if let json = try JSONSerialization.jsonObject(with: jsonData, options: []) as? Dictionary<String,Any> {
dataArray.removeAll()
for item in json["data"] as! [Dictionary<String, Any>] {
country = item
let url = URL(string: country["flag"] as? String ?? "")
let data = try? Data(contentsOf: url!) //make sure your image in this url does exist, otherwise unwrap in a if let check / try-catch
let image = UIImage(data: data!)
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let fileName = url?.lastPathComponent // name of the image to be saved
let fileURL = documentsDirectory.appendingPathComponent(fileName ?? "")
if let data = image?.jpegData(compressionQuality: 1.0){
do {
try data.write(to: fileURL)
country["flag"] = fileURL.absoluteString
//print("file saved")
//urlAsString = fileURL.absoluteString
} catch {
print("error saving file:", error)
}
}
dataArray.append(country)
country.removeAll()
}
modelData["data"] = dataArray
//print(modelData)
let jsonData1 = try JSONSerialization.data(withJSONObject: modelData, options: [])
do {
let decodedData = try JSONDecoder().decode(CountryCodeListModel.self, from: jsonData1)
return decodedData
} catch {
print("error: \(error)")
}
}
} catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
}
return nil
}
Problem statement:
Iam reading local json and take the url value of flag key and download corresponding images to local. Once i download then am taking the localpath and update in the dictionary and then create JSON object and update my model class.
Now, am trying to access my model class from ViewController like below
print(CountryCodeListModel?.data?[0].name) //check screenshot for error
print(cclm?.data?[0].flag) // this prints nil always
Please check the error screenshots attached2
My JSON look like this
{
"meta":{
"success":true,
"message":"Successfully retrieved country details",
"code":"200"
},
"data":[
{
"alpha2Code":"AF",
"alpha3Code":"AFG",
"flag":"https://raw.githubusercontent.com/DevTides/countries/master/afg.png",
"name":"Afghanistan",
"code":"+93"
},
{
"alpha2Code":"AX",
"alpha3Code":"ALA",
"flag":"https://raw.githubusercontent.com/DevTides/countries/master/ala.png",
"name":"Aland Islands",
"code":"+358"
},
{
"alpha2Code":"AL",
"alpha3Code":"ALB",
"flag":"https://raw.githubusercontent.com/DevTides/countries/master/alb.png",
"name":"Albania",
"code":"+355"
},
{
"alpha2Code":"DZ",
"alpha3Code":"DZA",
"flag":"https://raw.githubusercontent.com/DevTides/countries/master/dza.png",
"name":"Algeria",
"code":"+213"
},
{
"alpha2Code":"AS",
"alpha3Code":"ASM",
"flag":"https://raw.githubusercontent.com/DevTides/countries/master/asm.png",
"name":"American Samoa",
"code":"+1684"
}
]
}
You are trying to decode something that doesn't exist.
print(CountryCodeListModel?.data?[0].name) //check screenshot for error
print(cclm?.data?[0].flag) // this prints nil always
The above code states that you want:
the name of
the variable data at position 0 of
the struct CountryCodeListModel.
What you want to do is:
the name of
the variable at position 0 of
an INSTANCE of the struct CountryCodeListModel.
For example...
func readLocalJSONFile(forName name: String) {
do {
if let filePath = Bundle.main.path(forResource: name, ofType: "json") {
let fileUrl = URL(fileURLWithPath: filePath)
let data = try Data(contentsOf: fileUrl)
if let countryCodeObject = parse(jsonData: data) {
cclm = countryCodeObject
print(cclm?.data?[1].alpha2Code ?? "") //Printing Correct Value
print(cclm?.data?[0].flag ?? "")
print(countryCodeObject?.data[0].flag ?? "") // Same as the line above
}
}
} catch {
print("error: \(error)")
}
}
Unless you are trying to use a static variable (at which you would use CountryCodeListModel.data), you need to make sure you are actually using an instance of the structure or an object of a class to reference your properties.
CAVEAT
CountryCodeListModel is a structure. CountryCodeListModel() is an instance of the structure CountryCodeListModel. Since you can have multiple instances of a structure, you need to reference a specific structure when accessing data. Thus, CountryCodeListModel.data will not work and it needs to be CountryCodeListModel().data. In this case, you have cclm.data.
I've been following an guide in the hope of learning how to use MYSQL with IOS apps.
However the guide is a little bit outdated, and I'm using swift 3 and I been editing the code to fix a few bugs.
I have come down to a final problem, which is after I changed from using URL to NSURL, I can't use the "DataTask" anymore..
I have no idea how to replace this code of line.
import Foundation
protocol HomeModelProtocol: class {
func itemsDownloaded(items: NSArray)
}
class HomeModel: NSObject, NSURLSessionDataDelegate {
//properties
weak var delegate: HomeModelProtocol!
let urlPath = "http://iosquiz.com/service.php" //this will be changed to the path where service.php lives
func downloadItems() {
let url: NSURL = NSURL(string: urlPath)!
let defaultSession = Foundation.NSURLSessionConfiguration
let task = defaultSession.dataTask(with: url) { (data, response, error) in
if error != nil {
print("Failed to download data")
}else {
print("Data downloaded")
self.parseJSON(data!)
}
}
task.resume()
}
}
func parseJSON(_ data:Data) {
var jsonResult = NSArray()
do{
jsonResult = try NSJSONSerialization.jsonObject(with: data, options: NSJSONSerialization.ReadingOptions.allowFragments) as! NSArray
} catch let error as NSError {
print(error)
}
var jsonElement = NSDictionary()
let locations = NSMutableArray()
for i in 0 ..< jsonResult.count
{
jsonElement = jsonResult[i] as! NSDictionary
let location = LocationModel()
//the following insures none of the JsonElement values are nil through optional binding
if let name = jsonElement["Name"] as? String,
let address = jsonElement["Address"] as? String,
let latitude = jsonElement["Latitude"] as? String,
let longitude = jsonElement["Longitude"] as? String
{
location.name = name
location.address = address
location.latitude = latitude
location.longitude = longitude
}
locations.addObject(location)
}
I have come down to a final problem, which is after I changed from using URL to NSURL, I can't use the "DataTask" anymore..
Why did you switch from URL to NSURL? That's moving in the wrong direction. URL is the Swift bridge for NSURL. It should replace NSURL in all new code.
Switch back to URL. If you must use NSURL, you'll have to add an as URL when you use it in dataTask(with:), since that method expects an URL.
There's a deeper problem here. You're using a configuration as though it were a session. Here's the code I believe you mean:
// vvv Changed NSURLSessionDataDelegate to URLSessionDataDelegate
class HomeModel: NSObject, URLSessionDataDelegate {
weak var delegate: HomeModelProtocol? // <-- Avoid ! for this
let urlPath = "http://iosquiz.com/service.php"
func downloadItems() {
let url = URL(string: urlPath)! // <-- Changed NSURL to URL
let defaultSession = URLSession.shared // <-- Use URLSession, not URLSessionConfiguration
let task = defaultSession.dataTask(with: url) { (data, response, error) in
if error != nil {
print("Failed to download data")
}else {
print("Data downloaded")
self.parseJSON(data!)
}
}
task.resume()
}
}
I am using following Class to receive data from an external database:
import Foundation
protocol HomeModelProtocal: class {
func itemsDownloaded(items: NSArray)
}
class HomeModel: NSObject, NSURLSessionDataDelegate {
//properties
weak var delegate: HomeModelProtocal!
var data : NSMutableData = NSMutableData()
var mi_movil: String = ""
let misDatos:NSUserDefaults = NSUserDefaults.standardUserDefaults()
var urlPath: String = "http:...hidden here.."
let parametros = "?id="
func downloadItems() {
mi_movil = misDatos.stringForKey("ID_IPHONE")!
print ("mi_movil en HOMEMODEL:",mi_movil)
urlPath = urlPath + parametros + mi_movil
let url: NSURL = NSURL(string: urlPath)!
var session: NSURLSession!
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
print ("LA URL ES: ",url)
session = NSURLSession(configuration: configuration, delegate: self, delegateQueue: nil)
let task = session.dataTaskWithURL(url)
task.resume()
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
self.data.appendData(data);
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
if error != nil {
print("Failed to download data")
}else {
print("Data downloaded")
self.parseJSON()
}
}
func parseJSON() {
var jsonResult: NSMutableArray = NSMutableArray()
do{
jsonResult = try NSJSONSerialization.JSONObjectWithData(self.data, options:NSJSONReadingOptions.AllowFragments) as! NSMutableArray
} catch let error as NSError {
print(error)
}
var jsonElement: NSDictionary = NSDictionary()
let locations: NSMutableArray = NSMutableArray()
for(var i = 0; i < jsonResult.count; i++)
{
jsonElement = jsonResult[i] as! NSDictionary
print (jsonElement)
let location = MiAutoModel()
//the following insures none of the JsonElement values are nil through optional binding
if let id_mis_autos = jsonElement["id_mis_autos"] as? String,
let modelo = jsonElement["modelo"] as? String,
let ano = jsonElement["ano"] as? String,
let id_movil = jsonElement["id_movil"] as? String
{
location.id_mis_autos = id_mis_autos
location.modelo = modelo
location.ano = ano
location.id_movil = id_movil
}
locations.addObject(location)
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.delegate.itemsDownloaded(locations)
})
}
}
If there are received data, it works fine but if there are no data an exception is thrown:
Could not cast value of type '__NSArray0' (0x1a0dd2978) to 'NSMutableArray' (0x1a0dd3490)
What should I change to detect if there are no data to avoid the exception?
Since you don't seem to be modifying jsonResult anywhere, the obvious choice is to make it an NSArray instead of an NSMutableArray, and change the downcasting to match that.
I'm not sure why you're using NSDictionary and NSMutableArray but this is how I would do it:
for result in jsonResult {
guard let jsonElement = result as? [String:AnyObject] else { return }
let locations: [MiAutoModel] = []
let location = MiAutoModel()
//the following insures none of the JsonElement values are nil through optional binding
let id_mis_autos = jsonElement["id_mis_autos"] as? String ?? ""
let modelo = jsonElement["modelo"] as? String ?? ""
let ano = jsonElement["ano"] as? String ?? ""
let id_movil = jsonElement["id_movil"] as? String ?? ""
location.id_mis_autos = id_mis_autos
location.modelo = modelo
location.ano = ano
location.id_movil = id_movil
locations.append(location)
}
You might have to change some of the code depending on your situation.
func jsonParsing1(){
do{
let path : NSString = NSBundle.mainBundle().pathForResource("fileName", ofType: "json")!
let data : NSData = try! NSData(contentsOfFile: path as String, options: NSDataReadingOptions.DataReadingMappedIfSafe)
let jsonData = try! NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers)
let jsonArray = jsonData
** let templeArray = (jsonArray.valueForKey("temple-name") as? NSArray)!**
}catch {
print(error)
}
}
}
my json files is
[
{
“temple-name”: "aaa",
“image”: "image.png”,
“description”: “aaa“
},
{
“temple-name”: "bbb",
“image”: "image1.png”,
“description”: “bbb“
}
]
I am using json file in a separate class and trying to access the parsed array all through the project.
Used global array but it returns nil when calling from another class. Thanks in advance.
I need to use the templeArray globally.
You can create a singleton class with templeArray property, store the value in this array and access it through shared instance of the singleton class.
Or,
You can declare templeArray property in appDelegate and global access to it.
You can static variables to access data across your project.
class GlobalVariables {
static var templeArray : NSMutableArray?
}
You can use templeArray throughout your application via
GlobalVariables.templeArray?.objectAtIndex(index)
This code should work:
var templeArray: NSMutableArray = []
class Parser {
func jsonParsing1() {
do {
let path : NSString = NSBundle.mainBundle().pathForResource("fileName", ofType: "json")!
let data : NSData = try NSData(contentsOfFile: path as String, options: NSDataReadingOptions.DataReadingMappedIfSafe)
let jsonData = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers)
let jsonArray = jsonData
if let rawTempleArray = jsonArray.valueForKey("temple-name") as? NSMutableArray {
templeArray = rawTempleArray
} else {
print("temple-name is not array")
}
} catch {
print(error)
}
}
}
I would do like this:
struct Temple { //or class
// your fields here
init? (templeDictionary: NSDictionary) {
// your parser here
}
}
class TheParser {
static var theTempleArray: [Temple] = []
func jsonParsing1() {
do {
let path : NSString = NSBundle.mainBundle().pathForResource("fileName", ofType: "json")!
let data : NSData = try NSData(contentsOfFile: path as String, options: NSDataReadingOptions.DataReadingMappedIfSafe)
let jsonData = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers)
let jsonArray = jsonData
if let rawTempleArray = jsonArray.valueForKey("temple-name") as? NSArray {
for temple in rawTempleArray {
if let theTemple = temple as? NSDictionary { // or NSArray or String depending of your json structure
if let templeItem = Temple(templeDictionary: theTemple) {
TheParser.theTempleArray.append(templeItem)
}
}
}
} else {
print("temple-name is not array")
}
} catch {
print(error)
}
}
}
Please try this kind of approach:
class MyClass {
static var templeArray : NSArray?
func jsonParsing1(){
let path : NSString = NSBundle.mainBundle().pathForResource("fileName", ofType: "json")!
let data : NSData = try! NSData(contentsOfFile: path as String, options: NSDataReadingOptions.DataReadingMappedIfSafe)
let jsonData = try! NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers)
let jsonArray = jsonData
MyClass.templeArray = (jsonArray.valueForKey("temple-name") as? NSArray)!
/*
* You can now access to templeArray in this way:
* let myArray : NSArray? = MyClass.templeArray
*/
}
}
In addition, the do/catch block is useless in Swift2.
EDIT:
You are doing it wrong.
let templeArray = (jsonArray.valueForKey("temple-name") as? NSArray)!
will always be nil because the field temple name isn't an array, it is a string.
You have to do:
MyClass.templeArray = jsonArray
Then you can access the first temple this way:
let temple = MyClass.templeArray[0]
let templeName : String = temple.objectForKey("temple-name") as? String
I’m now stuck little bit. First, my code is as follows,
import UIKit
import CoreData
class ViewController: UIViewController {
var eTitles : [String] = []
var jTitles : [String] = []
var pCategories : [String] = []
var imgPaths : [String] = []
override func viewDidLoad() {
super.viewDidLoad()
let url = NSURL(string: "http://localhost:8888/post.php")
let allData = NSData(contentsOfURL: url!)
let allJsonData : AnyObject! = NSJSONSerialization.JSONObjectWithData(allData!, options: NSJSONReadingOptions(0), error: nil)
if let json = allJsonData as? Array<AnyObject>{
//println(json)
for index in 0...json.count-1{
let post : AnyObject? = json[index]
//println(post)
let collection = post! as Dictionary<String, AnyObject>
//println(collection)
//println(collection["Eng_Title"])
var eTitle : AnyObject? = collection["Eng_Title"]
var jTitle : AnyObject? = collection["Jam_Title"]
var pCategory : AnyObject? = collection["Category"]
var imgPath : AnyObject? = collection["Category_Img"]
eTitles.append(eTitle as String)
jTitles.append(jTitle as String)
pCategories.append(pCategory as String)
imgPaths.append(imgPath as String)
}
}
println(eTitles)
println(jTitles)
println(pCategories)
println(imgPaths)
for var i = 0; i < pCategories.count; i++
{
let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let managedContext = appDelegate.managedObjectContext!
let entity = NSEntityDescription.entityForName("Category", inManagedObjectContext: managedContext)!
let category = Category(entity: entity, insertIntoManagedObjectContext:managedContext)
category.title = pCategories[i]
category.path = fetchImg(imgPaths[i])
appDelegate.saveContext()
let en = NSEntityDescription.entityForName("Post", inManagedObjectContext: managedContext)!
let post = Post(entity: en, insertIntoManagedObjectContext:managedContext)
post.jtitle = jTitles[i]
post.etitle = eTitles[i]
post.category = category
appDelegate.saveContext()
}
}
func fetchImg(path : String) -> String{
var urlWebView = NSURL(string: path)!
println(urlWebView)
var requestWebView = NSURLRequest(URL: urlWebView)
var saveP : String = ""
NSURLConnection.sendAsynchronousRequest(requestWebView, queue: NSOperationQueue.mainQueue(), completionHandler: {
response, data, error in
if error != nil {
println("There was an error")
}
else {
// let musicFile = (data: data)
var documentsDirectory:String?
var paths:[AnyObject] = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)
if paths.count > 0 {
documentsDirectory = paths[0] as? String
var savePath = documentsDirectory! + "/" + NSUUID().UUIDString + ".jpg"
println(savePath)
saveP = savePath
NSFileManager.defaultManager().createFileAtPath(savePath, contents: data, attributes: nil)
}
}
})
return saveP
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Everytime I run this app, it stops at this line :
NSURLConnection.sendAsynchronousRequest(requestWebView, queue: NSOperationQueue.mainQueue(), completionHandler: {
(What I’m trying to do here is to parse Json data and put it into array, and then save it into CoreData. The problem is fetchImg() function.
I’m trying to pass paths, which come from Json data, to this function, and the function fetch real images from web, create a path and save the data onto device, and return the path to which images actually are saved.)
Any advice?
Sorry for my poor Eng explanation!!!
Thanks!