Index of JSON Array Swift - ios

I am using this library to parse an API endpoint that returns an array: https://github.com/SwiftyJSON/SwiftyJSON
I am grabbing an array fetched from a JSON response and I am trying to feed it into a table.
Right after my class in my view controller is declared, I have
var fetched_data:JSON = []
Inside of my viewDidLoad method:
let endpoint = NSURL(string: "http://example.com/api")
let data = NSData(contentsOfURL: endpoint!)
let json = JSON(data: data!)
fetched_data = json["posts"].arrayValue
To feed the table, I have:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: UITableViewCell = self.tableView.dequeueReusableCellWithIdentifier("cell1")! as UITableViewCell
cell.textLabel?.text = self.fetched_data[indexPath.row]
return cell
}
I am getting this error, when trying to set the cell textLabel:
Cannot subscript a value of a type ‘JSON’ with an index type of ‘Int’
How do I do this properly and get this to work?

You are declaring fetched_data as JSON
var fetched_data:JSON = []
but you are assigning Array to it:
fetched_data = json["posts"].arrayValue
Lets change the type to array of AnyObject:
var fetched_data: Array<AnyObject> = []
and then assigning should be like this (we have [AnyObject] so we need to cast):
if let text = self.fetched_data[indexPath.row] as? String {
cell.textLabel?.text = text
}
Edit: You also need to remember to assign correct Array, by doing arrayObject instead of arrayValue:
fetched_data = json["posts"].arrayObject

Related

Using a dictionary to populate a UITableView data in Swift 4

I am currently trying to populate a UITableView with data from a dictionary. I am getting an error "Cannot subscript a value of type 'Dictionary'". I know what it means but I don't know how to fix it. I know how to make it work with an array. Is it possible to use a dictionary?
// my dictionary
var contactDictionary: Dictionary = [String: String]()
//function that throws error
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "contactcell", for: indexPath) as! UITableViewCell
let object = contactDictionary[indexPath.row]
}
You can use contactDictionary.keys to retrieve the array of keys and use the key to find value, but the order may change every time; Swift does not guarantee the order of dictionary. If order is important, you can search for 3rd party OrderedDictionary or other data structures.
You just need to change your dictionary declaration code from
var contactDictionary: Dictionary = [String: String]()
to
var contactDictionary = [String: String]()
or
var contactDictionary: [String: String] = [:]
As already mentioned in comments Dictionary is an unordered collection. If you need to keep it sorted you can use an array of key value pairs:
var contacts: [(key: String, value: String)] = []

swift table view cells does not let me use indexPath.row

i am trying to populate data into cells inside of a table view. I created a chosenPlanData var which is initialized to an object with the data inside of it.. The object has properties such as "name" and "event location". An issue occurs when inside of 'cellForRowAt'. It does not let me add [indexPath.row] to the cell i am creating, which in turn does not populate the cells correctly.
For instance - i removed indexPath.row from the first cell.nameLbl.text call - and in turn every single name label in the table view was the same. here is piece of the code
var chosenPlanData = ChosenPlan()
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "individualPlansCell") as? PlanitsHomeViewCell else { return UITableViewCell() }
cell.nameLbl.text = chosenPlanData.nameOfEvent[indexPath.row] // error Cannot assign value of type 'Character' to type 'String?'
cell.dateAndTimeLbl.text = chosenPlanData.eventStartsAt[indexPath.row] as? String // error 'subscript' is unavailable: cannot subscript String with an Int, see the documentation comment for discussion
cell.nameLbl.text = chosenPlanData.nameOfEvent // This works - but every single cell has the same nameLbl though
return cell
}
// Here is the call back where i initialize the value for chosenPlanData
let EventbriteTVC = segue.destination as! EventbriteTableView
EventbriteTVC.callbackChosePlan = { result in
DispatchQueue.main.async {
self.individualPlanitsTableView.reloadData()
}
self.chosenPlanData = result
}
import Foundation
import UIKit
class ChosenPlan {
var nameOfEvent : String = ""
var eventStartsAt : String = ""
var eventLocationIs : String = ""
var eventURL : String = ""
var imageForPlan : String?
convenience init( eventName: String, eventTime: String, eventLocation: String, eventImage: String){
self.init()
self.nameOfEvent = eventName
self.eventStartsAt = eventTime
self.eventLocationIs = eventLocation
//self.eventURL = eventLink
self.imageForPlan = eventImage
//eventLink: String,
}
}
Your chosenPlanData variable is a single instance of ChosenPlan - You cannot subscript a single instance.
It needs to be an array of ChosenPlan:
var chosenPlanData = [ChosenPlan]()
Then you can index into this array:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "individualPlansCell") as? PlanitsHomeViewCell else { return UITableViewCell() }
cell.nameLbl.text = chosenPlanData[indexPath.row].nameOfEvent
cell.dateAndTimeLbl.text = chosenPlanData[indexPath.row].eventStartsAt
extending my comment
var chosenPlanData = ChosenPlan()
chosenPlanData is object of ChosenPlan
Now in cellForRow you writing chosenPlanData.nameOfEvent[indexPath.row] but nameOfEvent is String as per your ChosenPlan that you mentioned in question.
For more info,
chosenPlanData.nameOfEvent[indexPath.row] this line represents you using the n th (indexPath.row) object of nameOfEvent which is object of chosenPlanData
Hope now will be more cleared.
Solution
var chosenPlanData = [ChosenPlan]() <- create array
In cellForRow chosenPlanData[indexPath.row].nameOfEvent that means you'r using nameOfEvent of nth object of chosenPlanData.
Type handling is very important in Swift. Try this.
cell.nameLbl.text = String(chosenPlanData.nameOfEvent[indexPath.row])
let index = chosenPlanData.eventStartsAt.index(chosenPlanData.eventStartsAt.startIndex, offsetBy: 3)
cell.dateAndTimeLbl.text = String(chosenPlanData.eventStartsAt[index])
if
var chosenPlanData = [ChosenPlan]()
Try this:-
cell.nameLbl.text = "\(chosenPlanData[indexPath.row].nameOfEvent)"
or
cell.nameLbl.text = "\(chosenPlanData[indexPath.row].nameOfEvent ?? "")"

Using Swift 3, how can I parse a JSON file so that I can access the data globally?

I'm a newbie to iOS development, so apologies in advance if I use incorrect terminology. I am working on a basic app for a school project and would appreciate any help!
I have a JSON file with 20 different objects (please see an example of the first object below), which I would like to parse and access in three different View Controllers for different purposes - the same data, just ordered or presented in different ways.
The JSON file is stored locally within the xCode project. How can I parse it so that I may access the data globally within different View Controllers?
My understanding is that it would be best to parse the JSON file in the AppDelegate controller and then call the data within each individual ViewController but I am unsure of how to do this programatically.
The name of the JSON file is "locations.json"
{
locations: [
{
"id": 0001,
"name": "Helensburgh Tunnels",
"type": ["Tunnels", "Beach", "Views"],
"location": "Helensburgh, South Coast",
"image": "Helensburgh-Tunnels.jpg",
"activity": "Walking, photography, tunnelling",
"isVisited": false,
"latitude": -34.178985,
"longitude": 150.992867
}
}
Then I would appreciate if you could expand upon how I may read this data in a TableView. My TableView cells are currently configured as below (reading out of an array which is hard coded into the same ViewController) - how can I update this to read the parsed JSON data?
Name of ViewController; LocationTableViewController.swift
var locations:[Location] = [
Location(name: "Helensburgh Tunnels", type: "Old, abandoned train tunnels.", location: "Helensburgh, South Coast", image: "Helensburgh-Tunnels.jpg", activity: "Walking, photography, tunnelling", isVisited: false, latitude: "-34.178985", longitude: "150.992867")
]
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "cell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! LocationTableViewCell
// Configure the cell
cell.nameLabel.text = locations[indexPath.row].name
cell.thumbnailImageView.image = UIImage(named: locations[indexPath.row].image)
cell.locationLabel.text = locations[indexPath.row].location
cell.typeLabel.text = locations[indexPath.row].type
cell.accessoryType = locations[indexPath.row].isVisited ? .checkmark : .none
return cell
}
The location model is as follows.
Name of ViewController; Location.swift
class Location: NSObject, MKAnnotation {
var name: String = ""
var type: String = ""
var location: String = ""
var image: String = ""
var activity: String = ""
var isVisited: Bool = false
var rating: String = ""
var latitude: Double = 0.0
var longitude: Double = 0.0
init(name: String, type: String, location: String, image: String, activity: String, isVisited: Bool, latitude: String, longitude: String) {
self.name = name
self.type = type
self.location = location
self.image = image
self.activity = activity
self.isVisited = isVisited
self.latitude = Double(latitude)!
self.longitude = Double(longitude)!
}
public var coordinate: CLLocationCoordinate2D { get {
let coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
return coordinate
}
}
}
Create a new Swift File like GlobalClass.Swift
import Foundation and UIKit in GlobalClass
make function to read location like this:-
func readLocation() ->[String:Any]?{
do {
if let file = Bundle.main.url(forResource: "locations", withExtension: "json") {
let data = try Data(contentsOf: file)
let json = try JSONSerialization.jsonObject(with: data, options: [.mutableContainers])
if let object = json as? [String: Any] {
return object
}
return nil
}
} catch {
print(error.localizedDescription)
}
return nil
}
and call this function from TableView like this:-
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "cell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! LocationTableViewCell
// Configure the cell
if let locationJson = readLocation(){
if let locations = locationJson["location"] as? [[String:Any]]{
cell.nameLabel.text = locations[indexPath.row]["name"] as! String
cell.thumbnailImageView.image = UIImage(named: (locations[indexPath.row]["image"] as! String))
cell.locationLabel.text = locations[indexPath.row]["location"] as! String
cell.typeLabel.text = locations[indexPath.row]["type"] as! String
cell.accessoryType = (locations[indexPath.row]["isVisited"] as! Bool) ? .checkmark : .none
}
}
return cell
}
and number of rows in section should be
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let locationJson = readLocation(){
if let locations = locationJson["location"] as? [[String:Any]]{
return locations.count
}
}
return 0
}
Make sure your location file should be in correct json format otherwise this function throw an error
I wouldn't normally look at the AppDelegate for this type of thing. You should do as little there as possible,
You may want to look at an MVVM-like approach. You could create a viewmodel whose job is to vend out the various permutations of the data. You could create this as a singleton, but if your app isn't too complex you could simply instantiate the viewmodel in each of your view controllers.
I generally write a data manager which is responsible for parsing the data and provides a method to retrieve the data as an array of models. The viewmodel can call the datamanager method when it is instantiated or as needed. Again, for simple projects you could do the data parsing directly in the viewmodel. The viewmodel would have various methods that the view controllers would use to retrieve the data in the necessary format.
The above is heavily simplified, but should work for your needs.
Basic flow:
View controller(s) instantiates the viewmodel (or gets a shared instance) and calls a method on the viewmodel
Viewmodel calls method on datamanager if it doesn't already have data.
Datamanager parses JSON and returns array of models
Viewmodel method manipulates data from models (e.g. sorting) and returns them to the view controller.
View controller presents the returned data.
Read content from file & create a dictionary
let urlString = Bundle.main.path(forResource: "filename", ofType: "txt")
let url = URL(string: urlString!)
if let url = url {
do {
let data = try Data(contentsOf: url)
let jsonDictionary = try JSONSerialization.jsonObject(with: data, options:[])
// Model creation code here.
} catch {
print("Error : ")
}
}
From jsonDictionary object you construct your model classes. Please refer the link for, How to parse the json objects and store it in model classes.
Model :
struct LocationDetails {
var id: Int = 0
var name: String = ""
// Other variables are here
}
Manager Class :
// Here you can store common properties.
class AppUtils {
class var parsedResponse: [LocationDetails]
}
After constructed the models list. For example [LocationDetails]. You have to store it in the AppUtils class parsedResponse variable.
var locationDetailsList = [LocationDetails]()
//Your model construction code here.
//Store the array of objects into AppUtils class
AppUtils.parsedResponse = locationDetailsList
Access the models on different UIViewController like this.
AppUtils.parsedResponse
NOTE : If your app is not accessing the data then don't keep the values always on memory. Try to clean up the memory whenever possible.

How to Unwrap a Modal class object in Swift 3

Model Class:
class CountryModel: NSObject {
var name:NSString!
var countryId:NSString!
init(name: NSString, countryId: NSString) {
self.name = name
self.countryId = countryId
}
}
ViewController:
var nameArr = [CountryModel]() // Model Class object
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = UITableViewCell()
cell = tableView.dequeueReusableCell(withIdentifier: "cell")!
let arr = nameArr[indexPath.row] // How to do if let here
let str:String? = "\(arr.countryId) \(arr.name)"
if let sstr = str{
cell.textLabel?.text = sstr
}
return cell
}
How should one unwrap this because output is an optional, if I try to unwrap nameArr[indexPath.row] gives an error initializer for conditional binding must have optional type, not "country modal"
It works fine I am not concatenating arr.countryId with arr.name
This works fine
var nameArr = [CountryModal]()
nameArr.append(CountryModal.init(name: "harshal", countryId: "india"))
nameArr.append(CountryModal.init(name: "james", countryId: "australia"))
let arr = nameArr[0]
let str:String? = "\(arr.countryId!) \(arr.name!)"
if let sstr = str{
print(sstr)
}
let arr2 = nameArr[1]
let str2:String? = "\(arr2.countryId!) \(arr2.name!)"
if let sstr2 = str2{
print(sstr2)
}
You can try this library https://github.com/T-Pham/NoOptionalInterpolation. It does exactly that
import NoOptionalInterpolation
let n: Int? = 1
let t: String? = nil
let s: String? = "string1"
let o: String?? = "string2"
let i = "\(n) \(t) \(s) \(o)"
print(i) // 1 string1 string2
NoOptionalInterpolation gets rid of "Optional(...)" and "nil" in Swift's string interpolation. This library ensures that the text you set never ever includes that annoying additional "Optional(...)". You can also revert to the default behaviour when needed.
Also, please note that this does not affect the print function. Hence, print(o) (as opposed to print("(o)"), o as in the example above) would still print out Optional(Optional("string2"))
Avoid forced unwrapping as much as possible. i.e. using '!'. Whenever you see !, try and think of it as code smell.
For json parsing you would not know for sure if the values for countryId and name exists or not. So, it makes sense to keep them as optionals and gracefully try to unwrap them later.
class CountryModel {
var name: String?
var countryId: String?
init(name: String?, countryId: String?) {
self.name = name
self.countryId = countryId
}
}
var nameArr = [CountryModel]()
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell") else {
fatalError("cell should always be initialised")
}
var str: String?
let arr = nameArr[indexPath.row] // How to do if let here
if let countryId = arr.countryId, let name = arr.name {
str = "\(countryId) \(name)"
}
if let unwrappedString = str {
cell.textLabel?.text = unwrappedString
}
return cell
}
Bonus:
You could simplify your json parsing a lot using the Object Mapper library. It simplifies a lot of your parsing logic.

Access values in multidimentional Swift array?

I'd like to populate a tableView with results from a Firebase query. I successfully get the results and store them into an array but I'm struggling with how to put those values into the table.
I'm getting back a "userID" and a "title". I'm storing those in an array [[String]]. An example of an array after getting back a value would be [["OYa7U5skUfTqaGBeOOplRLMvvFp1", "manager"],["JQ44skOblqaGBe98ll5FvXzZad", "employee"]]
How would I access an individual value, such as the userID? This is what I have so far in my cellForRowAtIndexPath function:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
let invite = inviteArray[indexPath.row]
print(inviteArray)
print(invite)
return cell
}
Thanks!
EDIT: added function to store snapshot in an array
var dictArray: [Dictionary<String, String>] = []
func getAlerts(){
let invitesRef = self.rootRef.child("invites")
let query = invitesRef.queryOrderedByChild("invitee").queryEqualToValue(currentUser?.uid)
query.observeEventType(.Value, withBlock: { snapshot in
for child in snapshot.children {
guard let invitee = child.value["invitee"] as? String else{
return
}
guard let role = child.value["role"] as? String else{
return
}
self.dictArray.append(child as! Dictionary<String, String>)
print(self.dictArray)
}
})
}

Resources