How to populate my tableview with a mutablearray from json - ios

So I'm fetching data from a url which is in a json format. I'm trying to display the data in my tableview but, even though it feels simple, I can't figure out how to do it.
class CompanyModel {
func getJSON() {
let companyArray: NSMutableArray = NSMutableArray()
let requestURL: NSURL = NSURL(string: "http://localhost/Companies/JSON.php")!
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) {
print("Everyone is fine, file downloaded successfully.")
do{
let json = try NSJSONSerialization.JSONObjectWithData(data!, options:.AllowFragments)
if let companies = json["companies"] as? [[String: AnyObject]] {
for company in companies {
if let name = company["name"] as? String,
let phoneNumber = company["phone_number"] as? String,
let website = company["website"] as? String,
let email = company["email"] as? String,
let address = company["address"] as? String
{
let company = CompanyModel()
company.name = name
company.phoneNumber = phoneNumber
company.website = website
company.email = email
company.address = address
}
companyArray.addObject(company)
print(companyArray)
}
}
} catch {
print("Error with Json: \(error)")
}
}
print(companyArray) <- array is populated
}
print(companyArray) <- array is empty
task.resume()
}
}
I know i've done it before....I'm guessing in viewDidLoad() I'd call CompanyModel().getJSON() which would fetch the data, then store it in an empty array but my mind feels blank on how to do it.
I can't declare a variable of NSarray and store the data of it the variable directly for me to then populate the tableview. Nevertheless, I hope this explains what I'm trying to acheive.

Well first change the function to return your company array :
func getJSON() -> NSMutableArray {
}
By the end of the for loop return the company array
for company in companies {
}
After your array is populated, return the array inside this block:
dispatch_async(dispatch_get_main_queue(), {
return companyArray
})
And after task.resume() return the array:
return companyArray
From anywhere you wanna call this class and get the array :
Get a reference of the class
Let companyModal = CompanyModel()
And in anywhere you have your table view and the class let's say in viewDidLoad, you should first have NSMutableArray.
var arraySource = NSMutableArray()
And in viewDidLoad :
arraySource = companyModal.getJSON()
And to show the data in tableView do :
Mytableview.reloadData()

You can't use return within the closure of an asynchronous network request, you have to use a callback instead.
You need a NSMutableArray from the request, so first, let's make a callback for this:
completion: (array: NSMutableArray)->()
We add this callback to the method signature:
func getJSON(completion: (array: NSMutableArray)->())
And then at the location where the array will be available, we place this completion handler:
class CompanyModel {
func getJSON(completion: (array: NSMutableArray)->()) {
let companyArray: NSMutableArray = NSMutableArray()
let requestURL: NSURL = NSURL(string: "http://localhost/Companies/JSON.php")!
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 companies = json["companies"] as? [[String: AnyObject]] {
for company in companies {
if let name = company["name"] as? String,
let phoneNumber = company["phone_number"] as? String,
let website = company["website"] as? String,
let email = company["email"] as? String,
let address = company["address"] as? String {
let company = CompanyModel()
company.name = name
company.phoneNumber = phoneNumber
company.website = website
company.email = email
company.address = address
companyArray.addObject(company)
}
}
// CALLBACK HERE
completion(array: companyArray)
}
} catch {
print("Error with Json: \(error)")
}
}
}
task.resume()
}
}
Now to get the array from the network we use a trailing closure like this:
getJSON { (array) in
print(array)
}

Related

Swift JSON parsing and printing a specified value from an array

Hi I'm trying to get data from a certain JSON API. I can gat a snapshot of all values from the API, which is shown below. But I can't manage to put a specifiek row in a variable. This is the JSON form which I get. I want to print the "Description" value.Can someone help me with this?
And Hier is my code:
func apiRequest() {
let config = URLSessionConfiguration.default
let username = "F44C3FC2-91AF-5FB2-8B3F-70397C0D447D"
let password = "G23#rE9t1#"
let loginString = String(format: "%#:%#", username, password)
let userPasswordData = loginString.data(using: String.Encoding.utf8)
let base64EncodedCredential = userPasswordData?.base64EncodedString()
let authString = "Basic " + (base64EncodedCredential)!
print(authString)
config.httpAdditionalHeaders = ["Authorization" : authString]
let session = URLSession(configuration: config)
var running = false
let url = NSURL(string: "https://start.jamespro.nl/v4/api/json/projects/?limit=10")
let task = session.dataTask(with: url! as URL) {
( data, response, error) in
if let taskHeader = response as? HTTPURLResponse {
print(taskHeader.statusCode)
}
if error != nil {
print("There is an error!!!")
print(error)
} else {
if let content = data {
do {
let array = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject
print(array)
if let items = array["items"] {
if let description = items["Description"] as? [[String:Any]]{
print(description as Any)
}
}
}
catch {
print("Error: Could not get any data")
}
}
}
running = false
}
running = true
task.resume()
while running {
print("waiting...")
sleep(1)
}
}
First of all the array is not an array and not AnyObject, it's a dictionary which is [String:Any] in Swift 3.
let dictionary = try JSONSerialization.jsonObject(with: content) as! [String:Any]
print(dictionary)
I don't know why all tutorials suggest .mutableContainers as option. That might be useful in Objective-C but is completely meaningless in Swift. Omit the parameter.
The object for key itemsis an array of dictionaries (again, the unspecified JSON type in Swift 3 is Any). Use a repeat loop to get all description values and you have to downcast all values of a dictionary from Any to the expected type.
if let items = dictionary["items"] as? [[String:Any]] {
for item in items {
if let description = item["Description"] as? String {
print(description)
}
}
}
Looks like items is an array that needs to be looped through. Here is some sample code, but I want to warn you that this code is not tested for your data.
if let items = array["items"] as? [[String: AnyObject]] {
for item in items {
if let description = item["Description"] as? String{
print("Description: \(description)")
}
}
}
This code above, or some variation of it, should get you on the right track.
use the SwiftyJSON and it would be as easy as json["items"][i].arrayValue as return and array with items Values or json["items"][i]["description"].stringValue to get a string from a row

issue passing data to segue.destination within function

I'm trying to make 2 API calls on Segue invoke and ultimately pass Array of Data from Second Call to CollectionView. With first call I'm getting one value catID, which I need in order to make the other call:
let searchEndpoint: String = MY_ENDPOINT
// Add auth key
let serviceCallWithParams = searchEndpoint + "?PARAMETER"
guard let url = URL(string: serviceCallWithParams) else {
print("Error: cannot create URL")
return
}
let urlRequest = URLRequest(url: url)
// setting up the session
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
// making the request
let task = session.dataTask(with: urlRequest) {
(data, response, error) in
// error check
guard error == nil else {
print("error")
print(error)
return
}
// make sure we got data
guard let responseData = data else {
print("Error: did not receive data")
return
}
// parse JSON
do {
guard let catData = try JSONSerialization.jsonObject(with: responseData, options: []) as? [String: AnyObject] else {
print("error converting data to JSON")
return
}
if let data = catData["data"] as? [String: Any] {
if let array = data["categories"] as? [Any] {
if let firstObject = array.first as? [String: Any] {
if let catId = firstObject["catId"] as? Int {
getTitles(catId: catId)
}
}
}
}
} catch {
print("error converting data to JSON")
return
}
}
task.resume()
And then getTitles function looks like this:
func getTitles(catId: Int) {
let catIdString = String(catId)
let titlesEndPoint: String = MY_ENDPOINT + catIdString
// Add auth key
let titlesEndPointWithParams = titlesEndPoint + "?PARAMETER"
guard let titlesUrl = URL(string: titlesEndPointWithParams) else {
print("Error: cannot create URL")
return
}
let titlesUrlRequest = URLRequest(url: titlesUrl)
// set up the session
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
// make the request
let task = session.dataTask(with: titlesUrlRequest) {
(data, response, error) in
// check for any errors
guard error == nil else {
print("error calling GET on listCategoryTitles")
print(error)
return
}
// make sure we got data
guard let titlesData = data else {
print("Error: did not receive data")
return
}
// parse the JSON
do {
guard let allTitles = try JSONSerialization.jsonObject(with: titlesData, options: []) as? [String: AnyObject] else {
print("error converting data to JSON")
return
}
if let titlesJson = allTitles["data"] as? [String: Any] {
if let titlesArray = titlesJson["titles"] as? Array<AnyObject> {
self.books = []
for (index, value) in titlesArray.enumerated() {
var book = Book()
book.bookTitle = value["title"] as? String
book.bookAuthor = value["author"] as? String
if let imageSource = value["_links"] as? Array<AnyObject> {
book.bookImageSource = imageSource[1]["href"] as? String
}
self.books?.append(book)
}
}
}
} catch {
print("error converting data to JSON")
return
}
}
task.resume()
}
Now when I put:
let resultsVC = segue.destination as? CollectionViewController
resultsVC?.books = self.books
outside function, in target controller I'm getting an empty array as output on first click, but on every next one I'm getting proper data.
When I try putting this inside function "getTitles" the output in CollectionViewController is "nil" every time.
Worth mentioning could be that I have "books" variable defined like so:
Main Controller:
var books: [Book]? = []
Collection Controller:
var books: [Book]?
and I have created type [Book] which is basically object with 3 string variables in separate struct.
All of the code above is encapsulated in
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ShowResults" {
Any help/guideline would be much appreciated!
When you make api call it will execute in background means asynchronously where as prepare(for:sender:) will call synchronously.
Now from your question it is looks like that you have create segue in storyboard from Button to ViewController, so before you get response from your api you are moved to your destination controller, to solved your issue you need to create segue from your Source ViewController to Destination ViewController and set its identifier. After that inside getTitles(catId: Int) method after your for loop perform segue on the main thread.
for (index, value) in titlesArray.enumerated() {
var book = Book()
book.bookTitle = value["title"] as? String
book.bookAuthor = value["author"] as? String
if let imageSource = value["_links"] as? Array<AnyObject> {
book.bookImageSource = imageSource[1]["href"] as? String
}
self.books?.append(book)
}
DispatchQueue.main.async {
self.performSegue(withIdentifier: "ShowResults", sender: nil)
}
After that inside your prepare(for:sender:) make changes like below.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ShowResults" {
let resultsVC = segue.destination as? CollectionViewController
resultsVC?.books = self.books
}
}

swift: Alphabetiz data being parsed from a JSON

I wrote my own function in Swift2 to parse a JSON. Once the JSON is parsed, a list of data that was pulled from the JSON is displayed in a tableView on my app. I am trying to figure out how to display this data in alphabetical order. I think this needs to happen somewhere before the append method I call in the function. I would imagine this needs to be a sort function but I have not been able to figure out the correct sort function in Swift2 that will execute this properly. Any help I can get is appreciated!
Here is my parseJSON function:
func parseJSON(){
do{
let data = NSData(contentsOfURL: NSURL(string: "https://jsonblob.com/api/jsonBlob/580d0ccce4b0bcac9f837fbe")!)
let jsonResult = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)
for anItem in jsonResult as! [Dictionary<String, AnyObject>]{
let mifiName2 = anItem["name"] as! String
let mifiId = anItem["employeeId"] as! Int
let newName = Name(mifiName: mifiName2, mifiId: mifiId)
nameOfMifi.append(newName)
//print("Name: \(newName)")
}
}
catch let error as NSError{
print(error.debugDescription)
}
}
You need to sort your array after all the object is append in Array means after the for loop.
for anItem in jsonResult as! [Dictionary<String, AnyObject>]{
let mifiName2 = anItem["name"] as! String
let mifiId = anItem["employeeId"] as! Int
let newName = Name(mifiName: mifiName2, mifiId: mifiId)
nameOfMifi.append(newName)
//print("Name: \(newName)")
}
//Now you need to sort your array on the basis of name like this
nameOfMifi.sortInPlace { $0.mifiName < $1.mifiName }
Edit: As #vadian suggested do not use NSData(contentsOfURL:) because it will block your UI, so batter to use NSURLSession like this.
let session = NSURLSession.sharedSession()
let url = NSURL(string: "https://jsonblob.com/api/jsonBlob/580d0ccce4b0bcac9f837fbe")!
var task = session.dataTaskWithURL(url, completionHandler: {
(data, response, error) -> Void in
if error != nil {
return
}
if let jsonResult = try? NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as? [Dictionary<String, AnyObject>] {
for anItem in jsonResult {
let mifiName2 = anItem["name"] as! String
let mifiId = anItem["employeeId"] as! Int
let newName = Name(mifiName: mifiName2, mifiId: mifiId)
nameOfMifi.append(newName)
//print("Name: \(newName)")
}
//Now you need to sort your array on the basis of name like this
nameOfMifi.sortInPlace { $0.mifiName < $1.mifiName }
//Now reload tableView on main thread.
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
}
})
task.resume()

NSURLSession.sharedSession().dataTaskWithRequest runs slow in function

My problem arises when I want to populate data from my mysql database into a class object. I am trying to return an array of objects and it returns nil and then it fills itself somehow. How can I make it fill before returning the blank array?
Here is my code and a screenshot of code output
import Foundation
class Research
{
var mainResearchImageURL:String = ""
var userProfileImageURL:String = ""
var caption:String = ""
var shortDescription:String = ""
init(mainResearchImageURL :String, userProfileImageURL:String, caption:String, shortDescription:String)
{
self.mainResearchImageURL = mainResearchImageURL
self.userProfileImageURL = userProfileImageURL
self.caption = caption
self.shortDescription = shortDescription
}
class func downloadAllResearches()->[Research]
{
var researches = [Research]()
let urlString = "http://localhost/test/index.php"
let request = NSMutableURLRequest(URL: NSURL(string: urlString)!)
request.HTTPMethod = "POST"
let postString = "action=listresearches"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: {data, response, error in
if (error == nil) {
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as? NSArray
//let dictionary = json!.firstObject as? NSDictionary
var counter:Int = 0;
for line in json!{
let researchData = line as! NSDictionary
let researchLineFromData = Research(mainResearchImageURL: researchData["research_mainImageURL"] as! String, userProfileImageURL: researchData["research_creatorProfileImageURL"] as! String, caption: researchData["research_caption"] as! String, shortDescription: researchData["research_shortDescription"] as! String)
researches.append(researchLineFromData) //researches bir dizi ve elemanları Research türünde bir sınıftan oluşuyor.
counter += 1
print ("counter value \(counter)")
print("array count in loop is = \(researches.count)")
}
}catch let error as NSError{
print(error)
}
} else {
print(error)
}})
task.resume()
print("array count in return is = \(researches.count)")
return researches
}
}
And this is the output:
add this on you completionHandler ( it works if you update a view)
dispatch_async(dispatch_get_main_queue(), {
if (error == nil) { ...... }
})
Advice 1:
return the task and use a completion param in your method,
you can cancel the task if it's too slow.
Advice 2 :
Use alamofire and swiftyJson framework
What happen here is that you are returning the value before finish (remember that the call is Asynchronous), you can make something like this:
class func downloadAllResearches(success:([Research])->Void,failure:(String)->Void)
{
var researches = [Research]()
let urlString = "http://localhost/test/index.php"
let request = NSMutableURLRequest(URL: NSURL(string: urlString)!)
request.HTTPMethod = "POST"
let postString = "action=listresearches"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: {data, response, error in
if (error == nil) {
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as? NSArray
//let dictionary = json!.firstObject as? NSDictionary
var counter:Int = 0;
for line in json!{
let researchData = line as! NSDictionary
let researchLineFromData = Research(mainResearchImageURL: researchData["research_mainImageURL"] as! String, userProfileImageURL: researchData["research_creatorProfileImageURL"] as! String, caption: researchData["research_caption"] as! String, shortDescription: researchData["research_shortDescription"] as! String)
researches.append(researchLineFromData) //researches bir dizi ve elemanları Research türünde bir sınıftan oluşuyor.
counter += 1
print ("counter value \(counter)")
print("array count in loop is = \(researches.count)")
}
success(researches)
}catch let error as NSError{
print(error)
failure("Can be extract from NSERROR")
}
} else {
print(error)
failure("Error - Can be extract for NSERROR")
}})
task.resume()
}
And for call this Fuction use something like this:
Research.downloadAllResearches({ (objects:[Research]) -> Void in
dispatch_async(dispatch_get_main_queue(), { () -> Void in
//Do whatever you like with the content
})
}) { (failureLiteral:String) -> Void in
}

Need to return array from a function inside my class

I have a UIPickerView I want to fill with an array of values. The array of values is coming from a function inside of one of my classes (using json to grab the values and then put them into an array). The data is being grabbed successfully, and added to an array inside the function, but it's not returning for some reason.
Here's my class:
class Supplier {
var supplierId: Int
var supplierName: String
init(id: Int, name: String){
supplierId = id
supplierName = name
}
static func arrayOfSupplierNames() -> [String] {
let urlString = Constants.Urls.Suppliers.List;
let session = NSURLSession.sharedSession();
let url = NSURL(string: urlString)!;
var suppliers: Array<String> = []
session.dataTaskWithURL(url) { (data: NSData?, response:NSURLResponse?, error: NSError?) -> Void in
if let responseData = data {
do {
let json = try NSJSONSerialization.JSONObjectWithData(responseData, options: NSJSONReadingOptions.AllowFragments) as! Dictionary<String, AnyObject>;
if let suppliersDictionary = json["suppliers"] as? [Dictionary<String, AnyObject>] {
for aSupplier in suppliersDictionary {
if let id = aSupplier["id"] as? Int, let name = aSupplier["supplierName"] as? String {
let supplier = Supplier(id: id, name: name)
suppliers.append(supplier.supplierName)
}
}
}
}catch {
print("Could not serialize");
}
}
}.resume()
return suppliers
}
}
This seems to work because when I debug I can see the values being added to the array. I have another function in my ViewController that runs this function and adds it to a local array but the array returned from the function doesn't seem to get added to the array in the view controller:
func populateSuppliersArray() {
let sup:Array = Supplier.arrayOfSupplierNames()
for s in sup {
supplierArray.append(s) //supplierArray is at the top scope of view controller.
}
}
I even made the class function static so I wouldn't have to initialize the class just to use the function. I'm not sure this is the correct way. When I look at the sup variable while debugging it has zero values.
the json data is received inside an asynchronous block. your function returns as soon as you call resume on dataTaskWithURL. you should pass a completion block as an argument to your arrayOfSupplierNames and pass the array to that completion block instead. You could modify your function like this:
// take a completion block as an argument
func arrayOfSupplierNames(completion: (([String]) -> Void)) -> Void {
let urlString = Constants.Urls.Suppliers.List;
let session = NSURLSession.sharedSession();
let url = NSURL(string: urlString)!;
var suppliers: Array<String> = []
session.dataTaskWithURL(url) { (data: NSData?, response:NSURLResponse?, error: NSError?) -> Void in
if let responseData = data {
do {
let json = try NSJSONSerialization.JSONObjectWithData(responseData, options: NSJSONReadingOptions.AllowFragments) as! Dictionary<String, AnyObject>;
if let suppliersDictionary = json["suppliers"] as? [Dictionary<String, AnyObject>] {
for aSupplier in suppliersDictionary {
if let id = aSupplier["id"] as? Int, let name = aSupplier["supplierName"] as? String {
let supplier = Supplier(id: id, name: name)
suppliers.append(supplier.supplierName)
}
}
completion(suppliers) // pass the array to completion block
}
}catch {
print("Could not serialize");
}
}
}.resume()
}
You'll call it like this:
Supplier.arrayOfSupplierNames { (suppliers) -> Void in
// use suppliers as appropriate
}
please note that completion block is called asynchronously (some time in future).

Resources