So I'm trying to populate multiple tableviews with the json code below:
{ "company":[
{ "company_id":"0",
"name": "Company Name",
"phone_number":"0123978978",
"website":"http:\/\/www.Company.co.uk\/",
"email":"Company#hotmail.co.uk",
"address":"123 Alm Street...",
"employees":[
{
"company_id": "0",
"name":"Steve",
"age":"25",
"description":"desc"},
{
"company_id": "0",
"name":"Paul",
"age":"35",
"description":"desc"}]
}
]
}
below is the model of fetching the json and formatting it into something I can use
class Company: NSObject {
var name: String
var phoneNumber: String
var website: String
var email: String
var address: String
var employees: [Employee]
override init() {
}
init(name: String, phoneNumber: String, website: String, email: String, address: String, employees: [Employee]) {
self.name = name
self.phoneNumber = phoneNumber
self.website = website
self.email = email
self.address = address
self.employees = employees
}
}
class Employee : NSObject {
var name:String
var age:Int
var information:String
override init() {
}
init(name: String, age: Int, information: String) {
self.name = name
self.age = age
self.information = information
}
}
func getJSON(completion: (array: [Company])->()) {
var companyArray = [Company]()
let requestURL: NSURL = NSURL(string: urlString)!
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("Everything is fine, file downloaded successfully.")
do{
let json = try NSJSONSerialization.JSONObjectWithData(data!, options:.AllowFragments)
if let companies = json["companies"] as? NSArray {
for companyDict in companies {
let company = Company()
let employee = Employee()
var employeeArray = [Employee]()
if let name = companyDict["name"] as? String,
let phoneNumber = companyDict["phone_number"] as? String,
let website = companyDict["website"] as? String,
let email = companyDict["email"] as? String,
let address = companyDict["address"] as? String {
company.name = name
company.phoneNumber = phoneNumber
company.website = website
company.email = email
company.address = address
}
if let employees = companyDict["employees"] as? NSArray {
for employeesDict in employees {
if let name = employeesDict["name"] as? String,
let age = employeesDict["age"] as? Int,
let information = employeesDict["information"] as? String {
var employeeArray = [Employee]()
let employee = Employee()
employee.name = name
employee.information = information
employee.age = age
employeeArray.append(employee)
company.employees = employeeArray
}
}
}
companyArray.append(company)
}
dispatch_async(dispatch_get_main_queue()) {
completion(array: companyArray)
}
}
} catch {
print("Error with Json: \(error)")
}
}
}
task.resume()
}
This is where the problem arrises, I can populate my first tableview fine. Each cell displays the data fine. The problem occurs when I try to populate my second tableview based on selection of a cell in my first tableview.
let selectedCompany:Company?
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.selectedCompany!.employees!.count //ignore force unwrap
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let employee = self.selectedCompany!.employees![indexPath.row] //ignore force unwrap
cell.employeeName.text = employee.name
return cell
}
The line let employee = self.selectedCompany!.employees![indexPath.row] displays a compiler error of type has no subscript members. I think I know why - I need to populate the second table view as an array. At the moment, Employee is not an array. I've tried several different methods to make it an array but I can't seem to do it. What I've tried usually returns nil and the app crashes or i get compiler errors
You have to declare employees in Company as array
var employees = [Employee]()
and also in the initializer
init(name ... , employees: [Employee]) {
and – very very important – you have to create the Employee instance in the repeat loop, otherwise you have employees.count times the same object (due to reference semantics of classes)
...
for employeesDict in employees {
if let name = employeesDict["name"] as? String,
let age = employeesDict["age"] as? Int,
let information = employeesDict["information"] as? String {
let employee = Employee(name:name, age:age, information:information)
employeeArray.append(employee)
}
}
}
company.employees = employeeArray
...
You have to use the initializer anyway. And you have to assign the array after the repeat loop.
Related
I've been using JSONParsing to display my data when you search for a term. Now I want to list out all of those terms in an alphabetized list. But am having trouble getting the code to work correctly. I've replicated some code from someone else that was having the same problem and got that to work but I'm having trouble implementing my own code.
I currently am parsing my JSON with this code:
func parseJSONSignDictionary() {
if let url = Bundle.main.url(forResource: "csvjson", withExtension: "json") {
do {
let date = Date()
let data = try Data(contentsOf: url)
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String:Any] {
(json["results"] as? [[String:Any]])?.forEach { j in
if let name = j["identifier"] as? String, let id = j["id"] as? Int {
let sign = Signs(name: name, number: id)
signsArray.append(sign)
}
}
}
print("Took", Date().timeIntervalSince(date))
} catch {
print(error.localizedDescription)
}
}
}
Edit to add some more code, this is my Signs class, which would replace the Restaurant Array/Class:
class Signs: NSObject, Decodable, NSCoding {
private var _signName: String!
private var _signNumber: Int!
var signName: String {
return _signName
}
var signNumber: Int {
return _signNumber
}
func encode(with aCoder: NSCoder) {
aCoder.encode(signName, forKey: "signNameKey")
}
required init?(coder aDecoder: NSCoder) {
print("Trying to turn Data into Sign")
self._signName = aDecoder.decodeObject(forKey: "signNameKey") as? String
}
init(name: String, number: Int) {
self._signName = name
self._signNumber = number
}
}
The code from another StackOverflow that I'm trying to use is from here. question:Display data from JSON in alphabetical sections in Table View in Swift
func makeDataSource(names:[String:[AnyObject]]) {
var dict = [String:[Restaurant]]()
let letters = NSCharacterSet.letters
for (_,value) in names {
//Iterating Restaurants
for resObj in value {
if let restaurantName = resObj["name"] as? String {
let restaurant = Restaurant(name: restaurantName)
var key = String(describing: restaurant.name.first!)
//To check whether key is alphabet or not
key = isKeyCharacter(key: key, letters: letters) ? key : "#"
if let keyValue = dict[key] {
//Already value exists for that key
var filtered = keyValue
filtered.append(restaurant)
//Sorting of restaurant names alphabetically
//filtered = filtered.sorted(by: {$0.0.name < $0.1.name})
dict[key] = filtered
} else {
let filtered = [restaurant]
dict[key] = filtered
}
}
}
}
//To sort the key header values
self.dataArray = Array(dict).sorted(by: { $0.0 < $1.0 })
//Logic to just shift the # category to bottom
let temp = self.dataArray[0]
self.dataArray.removeFirst()
self.dataArray.append(temp)
self.indexTitles = Array(dict.keys.sorted(by: <))
let tempIndex = self.indexTitles[0]
self.indexTitles.removeFirst()
self.indexTitles.append(tempIndex)
}
I have my own array that would replace Restaurant, called Signs.
if let restaurantName = resObj["name"] as? String {
I'm also wondering where this "name" is being pulled from? Is it the array/model which has the var name?
I'm not sure since I have a way to access the JSON data with my own function if I even need to try to use the getdata() function.
I just wanna understand what I'm missing, and how to do it on my own to get the code to work properly.
I passed a JSON data to this table view controller. How to get the JSON data and show it on a table view cell?
When I print passedData I receive the following output:
["jobs": <__NSArrayM 0x17005d9d0>
({
jobDate = "2017-08-31";
jobEndTime = 1504144800;
jobID = 87;
jobTime = 1504137600;
},
{
jobDate = "2017-08-31";
jobEndTime = 1504173600;
jobID = 89;
jobTime = 1504170000;
}),
"result": success,
"message": Retrieve Sucessfully]
This is the code I'm using:
var passedData: [String: Any]!
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let jobs = passedData["jobs"] as? [[String:Any]] else {return 0}
return jobs.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "jobCell", for: indexPath)
// jobs[indexPath.row] display jobTime
return cell
}
Instead of giving you a reply on the subject I would try to help understanding the domain you are working with.
The JSON response you are retrieving contains an array (jobs) where each element is an object. In fact, the JSON syntax states that:
In JSON, values must be one of the following data types:
a string
a number
an object (JSON object)
an array
a boolean
null
If you are using JSONSerialization class, then you will have a dictionary that will contain an array of dictionaries.
A simple snippet like this will give you that array
if let jsonArray = jsonDict["jobs"] as? [[String: Any]] {
print(jsonArray)
}
Now, in order to access elements of that array you can do like the following:
let jsonArrayDict = jsonArray[0]
print(jsonArrayDict["jobTime"] ?? 0)
Obviously the code here is not production ready since you need to pay attention to possible crashes of your app.
What I really suggest is to work with a model that can be passed to your table view cell. This approach has these benefits:
avoid using optionals
document your code
unit test your code
etc.
Here an example on how to convert your JSON object into a specific model. Run it in a playground and practice.
struct Job {
let jobDate: String
let jobEndTime: Int
let jobID: Int
let jobTime: Int
}
extension Job {
init?(dict: [String: Any]) {
guard let jobDate = dict["jobDate"] as? String,
let jobEndTime = dict["jobEndTime"] as? Int,
let jobID = dict["jobID"] as? Int,
let jobTime = dict["jobTime"] as? Int else {
return nil
}
self.jobDate = jobDate
self.jobEndTime = jobEndTime
self.jobID = jobID
self.jobTime = jobTime
}
}
extension Job: CustomStringConvertible {
var description: String {
return "Job: \(jobDate) \(jobEndTime) \(jobID) \(jobTime)"
}
}
let jsonString = """
{
"jobs": [
{
"jobDate": "2017-08-31",
"jobEndTime": 1504144800,
"jobID": 87,
"jobTime": 1504137600
},
{
"jobDate2": "2017-08-31",
"jobEndTime": 1504144800,
"jobID": 87,
"jobTime": 1504137600
}
],
"result": "success",
"message": "Retrieve Sucessfully"
}
"""
if let jsonData = jsonString.data(using: .utf8), let jsonObject = try? JSONSerialization.jsonObject(with: jsonData, options: []),
let jsonDict = jsonObject as? [String: Any],
let jsonArray = jsonDict["jobs"] as? [[String: Any]] {
let jobs = jsonArray.flatMap { Job(dict: $0) }
print(jobs)
} else {
print("No Results")
}
This should help you understand the steps needed to parse and use the JSON data that you have. You will likely need to create a custom UITableViewCell which is beyond the scope of this answer. There are plenty of resources online which will explain this part of the process.
You will also need to convert your timestamps to dates, there are alot of answers on StackOverflow that can help with this. Like this one
Mapping JSON Data
let passedJsonStr = "{\"jobs\":[{\"jobDate\":\"2017-08-31\",\"jobEndTime\":1504144800,\"jobID\":87,\"jobTime\":1504137600},{\"jobDate\":\"2017-08-31\",\"jobEndTime\":1504173600,\"jobID\":89,\"jobTime\":1504170000}],\"result\":\"success\",\"message\":\"Retrieve Sucessfully\"}"
struct Job {
var jobDate: String
var jobEndTime: Int
var jobID: Int
var jobTime: Int
init(dict: [String:AnyObject]) {
// unwrap these safely, I'm just giving an example
self.jobDate = dict["jobDate"] as! String
self.jobEndTime = dict["jobEndTime"] as! Int
self.jobID = dict["jobID"] as! Int
self.jobTime = dict["jobTime"] as! Int
}
}
var jobs = [Job]()
if let data = passedJsonStr.data(using: String.Encoding.utf8) {
if let jsonObject = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String:AnyObject] {
if let jsonData = jsonObject["jobs"] as? [[String:AnyObject]] {
jobs = jsonData.map { Job(dict: $0) }
}
print(jobs)
}
}
Displaying in UITableView
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "jobCell", for: indexPath)
let job = self.jobs[indexPath.row]
cell.titleLabel.text = job.jobDate
return cell
}
I have an API that loads into my app:
https://wger.de/api/v2/exercise/?language=2
I have no errors on the API call and no errors in the table view where I run the get data function. Its worked previously perfectly until now. I havent changed any code.
I assumed the servers would be down for the call, but they arent as you can see.
I am running a print 'request was successful' which is displayed in the debug when running, so its getting the data, however the print(self.exercises) returns an empty array hence no table data...any ideas? Here is the API call
open class ApiService: NSObject {
open func getData(completionHandler: #escaping (NSDictionary?, NSError?) -> Void) -> Self {
//loads api filtering by english only
let requestUrl = "https://wger.de/api/v2/exercise/?format=json&language=2"
Alamofire.request(requestUrl, method: .get, encoding: URLEncoding.default)
.responseJSON { response in
switch response.result {
case .success( let data):
print("Request was sucessful")
completionHandler(data as? NSDictionary, nil)
case .failure(let error):
print("Request failed with error: \(error)")
completionHandler(nil, error as NSError?)
}
}
return self
}
Table Function
func getApiData() {
let _ = apiService.getData() {
(data, error) in
if let data = data {
if let results = data["results"] as? [[String:Any]] {
for result in results {
if let exercise = Exercise(dictionary: result) {
self.exercises.append(exercise)
}
}
self.exercisesTableView.reloadData()
print(self.exercises)
}
}
}
}
I am also using a serialization model if that could interfere?
final public class Exercise {
var id: Int
var descrip: String
var name: String
var language: [Int]
var muscles: [Int]
var musclesSecondary: [Int]
var equipment: [Int]
public init?(dictionary: [String: Any]) {
guard
let id = dictionary["id"] as? Int,
let descrip = dictionary["description"] as? String,
let name = dictionary["name"] as? String,
let language = dictionary["language"] as? [Int],
let muscles = dictionary["muscles"] as? [Int],
let musclesSecondary = dictionary["muscles_secondary"] as? [Int],
let equipment = dictionary["equipment"] as? [Int]
else { return nil }
self.id = id
self.descrip = descrip
self.name = name
self.language = language
self.muscles = muscles
self.musclesSecondary = musclesSecondary
self.equipment = equipment
}
Sorry,
I havent changed any code
is wrong.
You added the properties musclesSecondary and language and exactly there is the error: language is single Int rather than an array.
var language: Int
...
let language = dictionary["language"] as? Int,
Im having a really hard time wrapping my head around this process, I have made an API call and received back JSON, I then use a model to serialise the JSON so it can be used easily in my View Controller Table, but im having issues with how to call the API in the View Controller to have the result fit into my serialisation model. I hope I explained it correctly?
Here is the code of my API Request:
open class ApiService: NSObject {
open func getData(completionHandler: #escaping (NSDictionary?, NSError?) -> Void) -> Self {
let requestUrl = "https://wger.de/api/v2/exercise/?format=json"
Alamofire.request(requestUrl, method: .get, encoding: URLEncoding.default)
.responseJSON { response in
switch response.result {
case .success( let data):
completionHandler(data as? NSDictionary, nil)
case .failure(let error):
print("Request failed with error: \(error)")
completionHandler(nil, error as NSError?)
}
}
return self
}
}
and here is the code of my serialisation
final public class Exercise: ResponseObjectSerializable {
var id: Int!
var description: String!
var name: String!
var muscles: String!
var equipment: String!
public init?(response: HTTPURLResponse, representation: Any) {
guard
let representation = representation as? [String: Any],
let id = representation["id"] as? Int,
let description = representation["description"] as? String,
let name = representation["name"] as? String,
let muscles = representation["muscles"] as? String,
let equipment = representation["equipment"] as? String
else { return nil }
self.id = id
self.description = description
self.name = name
self.muscles = muscles
self.equipment = equipment
}
}
But I cant work out how to fit this into my view controller function call which is currently this
let apiService = ApiService()
let searchController = UISearchController(searchResultsController: nil)
var arrRes: [String] = []
var filtered: [String] = []
var searchActive: Bool = false
var id: Int?
var description: String?
var name: String?
var muscles: String?
var equipment: String?
override func viewDidLoad() {
super.viewDidLoad()
exercisesTableView.delegate = self
exercisesTableView.dataSource = self
exerciseSearchBar.delegate = self
getApiData()
}
func getApiData() {
let _ = apiService.getData() {
(data, error) in
if let data = data {
if let arr = data["results"] as? [String] {
self.arrRes = arr
self.exercisesTableView.reloadData()
}
} else if let error = error {
print(error)
}
}
}
First of all the HTTP response does not affect the custom class at all so I left it out.
Second of all the values for keys muscles and equipment are arrays rather than strings.
Third of all since the JSON data seems to be immutable declare the properties in the class as constant (let)
With a few slightly changes this is the custom class
final public class Exercise {
let id : Int
let description: String
let name: String
let muscles : [Int]
let equipment : [Int]
public init?(dictionary: [String: Any]) {
guard
let id = dictionary["id"] as? Int,
let description = dictionary["description"] as? String,
let name = dictionary["name"] as? String,
let muscles = dictionary["muscles"] as? [Int],
let equipment = dictionary["equipment"] as? [Int]
else { return nil }
self.id = id
self.description = description
self.name = name
self.muscles = muscles
self.equipment = equipment
}
}
Then you have to declare the data source array
var exercises = [Exercise]()
And in the method getApiData() populate the array
...
if let results = data["results"] as? [[String:Any]] {
for result in results {
if let exercise = Exercise(dictionary: result) {
self.exercises.append(exercise)
}
}
self.exercisesTableView.reloadData() // might be dispatched on the main thread.
}
Note: Any is used in Swift 3, in Swift 2 replace all occurrences of Any with AnyObject
I am trying to parse some weather data, and I have ran into an issue. I created a default value in an array that gives the user a dummy location and value in a table view. This value is unwrapped using the same code below that I am trying to use for real data. It works fine for the dummy value and can find the locationStore easily, but when I am loading from the view that has the parsed data, the locationStore is showing as nil in the debugger. I am not sure how to solve this, so any help would be greatly appreciated.
class LocationWeatherViewController: UITableViewController{
var locationStore: LocationStore!
var imageStore: ImageStore!
var newLocation: Location!
var locationCreated : NSMutableArray = NSMutableArray( array: ["Test ", 0, 0.0, 0.0 , 0.0])
override func viewDidLoad() {
super.viewDidLoad()
print(locationCreated.count)
if let locationName = locationCreated[0] as? String {
print(locationName)
if let currentTemp = locationCreated[2] as? Double{
print(currentTemp)
if let zip32 = (locationCreated[1] as? Int){
let zip = Int64(zip32)
print(zip)
if let xCrd = locationCreated[3] as? Double{
print(xCrd)
if let yCrd = locationCreated[4] as? Double{
print(yCrd)
newLocation = locationStore.createLocation(false, location: locationName, currentTemp: currentTemp, zipCode: zip, xCord: xCrd, yCord: yCrd)
if let index = locationStore.allLocations.indexOf(newLocation){
let indexPath = NSIndexPath(forRow: index, inSection: 0)
tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
}
}
}
}
}
}
}
func createLocation(random: Bool, location: String, currentTemp: Double, zipCode: Int64, xCord: Double, yCord: Double ) -> Location{
if !random{
let newLocation = Location(random: false, locationNme: location, currentTmperature: currentTemp, locationZpCode: zipCode, xCrd: xCord, yCrd: yCord)
allLocations.append(newLocation)
return newLocation
}
LocationStore:
let newLocation = Location(random: true, locationNme: location, currentTmperature: currentTemp, locationZpCode: zipCode, xCrd: xCord, yCrd: yCord)
allLocations.append(newLocation)
return newLocation
}
Location:
init( locationName: String, currentTemperature: Double, locationZipCode: Int64, xCord: Double, yCord: Double){
self.locationName = locationName
self.currentTemperature = currentTemperature
self.locationZipCode = Int64(locationZipCode)
self.xCord = xCord
self.yCord = yCord
self.itemKey = NSUUID().UUIDString
super.init()
}
convenience init( random:Bool = false, locationNme: String, currentTmperature: Double, locationZpCode: Int64, xCrd: Double, yCrd: Double ){
if random {
let locations = ["Louisville", "Gainesville", "Austin", "San Francisco"]
//38.2527, 85.7585
//29.6516, 82.3248
// 30.2672, -97.7431
// 37.7749, -122.4194
let xCords = [38.2527, 29.6516, 30.2672, 37.7749 ]
let yCords = [-85.7585, -82.3248, -97.7431, -122.4194 ]
let zips = [40217, 32608, 77878, 46454]
let temps = [76.0, 101.3, 95.4, 68.5]
var idx = arc4random_uniform(UInt32(locations.count))
let randomLocation = locations[Int(idx)]
let randomLocationxCord = xCords[Int(idx)]
let randomLocationyCord = yCords[Int(idx)]
let randomZip = zips[Int(idx)]
idx = arc4random_uniform(UInt32(temps.count))
let randomTemp = temps[Int(idx)]
self.init ( locationName: randomLocation, currentTemperature: randomTemp, locationZipCode: Int64(randomZip), xCord: randomLocationxCord, yCord: randomLocationyCord)
}
else{
self.init( locationName: locationNme, currentTemperature: currentTmperature, locationZipCode: locationZpCode, xCord: xCrd, yCord: yCrd)
}
}
APIFinder:
func useAPI(zipCode: Int) -> NSArray{
let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
let locationURL = NSURL( string: "http://api.openweathermap.org/data/2.5/forecast/city?zip=\(zipCode),us&APPID=74b78f4effe729b2a841cb35e3862d85")
let request = NSURLRequest(URL: locationURL!)
let task = session.dataTaskWithRequest(request) {
(data,response, error) -> Void in
if let locationData = data {
if let jsonString = NSString( data:locationData,encoding: NSUTF8StringEncoding){
let data = jsonString.dataUsingEncoding(NSUTF8StringEncoding)
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as! [String: AnyObject]
if let city = json["city"]{
if let name = city["name"]{
self.locationArray[0] = name!
self.locationArray[1] = zipCode
}
if let coord = city["coord"]{
if let xCord = coord!["lat"]{
self.locationArray[3] = xCord!
}
if let yCord = coord!["lon"]{
self.locationArray[4] = yCord!
}
}
}
if let list = json["list"]{
if let main = list[0]["main"]{
if let temp = main!["temp"]{
self.locationArray[2] = temp!
}
}
}
} catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
}
}
}
else if let requestError = error {
print("Error fetching weather data: \(requestError)")
}
else {
print("Unexpected error with request")
}
}
task.resume()
return locationArray
}
Output:
5
Test
0.0
0
0.0
0.0
5
Hartford
295.54
42328
37.45116
-86.909157
fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)
Solved:
The problem was that I needed to pass the LocationStore between the views in the segue. Such a dumb problem once the answer was shown.
Thanks for all of the help and suggestions.