Populating UITableView from JSON - swift4

I'm trying to use a JSON file to populate a UITableView in my app. Previously I was hard coding an array of sample data, but need to move to using my JSON file. This is a mishmash of various tutorials and answers found on SO, so I apologize if the syntax conventions are a little off.
import UIKit
import os.log
class BonusTableViewController: UITableViewController {
//MARK: Properties
var bonuses = [Bonus]() // Used for old sample data
var jBonuses = [Bonuses]() // Used with JSON based data
override func viewDidLoad() {
super.viewDidLoad()
//MARK: Confirm JSON file was loaded and log the Bonus Codes
let loadedBonuses = loadJson(filename: "BonusData")
for bonus in loadedBonuses! {
print(bonus.bonusCode)
}
}
// Load the JSON file from the bundled file.
func loadJson(filename fileName: String) -> [Bonuses]? {
if let url = Bundle.main.url(forResource: fileName, withExtension: "json") {
do {
let data = try Data(contentsOf: url)
let decoder = JSONDecoder()
let jsonData = try decoder.decode(JSONData.self, from: data)
print("loadJson loaded JSON")
return jsonData.bonuses
} catch {
print("error:\(error)")
}
}
return nil
}
// MARK: Data Structures
// Bonus Data Structs
struct JSONData: Decodable {
let name: String
let version: String
let bonuses: [Bonuses]
}
struct Bonuses: Decodable {
let bonusCode: String
let category: String
let name: String
let value: Int
let city: String
let state: String
let flavor: String
let imageData: String
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return jBonuses.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Table view cells are reused and should be dequeued using a cell identifier.
let cellIdentifier = "BonusTableViewCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? BonusTableViewCell else {
fatalError("The dequeued cell is not an instance of BonusTableViewCell.")
}
// Now using JSON file
let jBonus = jBonuses[indexPath.row]
print("Setting labels using JSON file")
cell.bonusCodeLabel.text = jBonus.bonusCode
cell.categoryLabel.text = jBonus.category
cell.nameLabel.text = jBonus.name
cell.valueLabel.text = "\(jBonus.value)"
cell.cityLabel.text = "\(jBonus.city),"
cell.stateLabel.text = jBonus.state
cell.flavorText.text = jBonus.flavor
cell.primaryImage.image = jBonus.photo
return cell
}
From the console, I can confirm that it is able to see the JSON data and it does spit out the list of bonus codes. I can't pinpoint why this isn't working, but the result is a blank just a tableview with a bunch of empty rows.

Replace viewDidLoad with
override func viewDidLoad() {
super.viewDidLoad()
jBonuses = loadJson(filename: "BonusData")!
tableView.reloadData()
}
You have to assign the loaded data to the data source array and reload the table view.
Or if loadedBonuses could really be nil (it cannot in this case):
override func viewDidLoad() {
super.viewDidLoad()
if let loadedBonuses = loadJson(filename: "BonusData") {
jBonuses = loadedBonuses
tableView.reloadData()
}
}
Notes:
Delete the method numberOfSections, 1 is the default.
Force unwrap the cell, the code must not crash if everything is hooked up properly
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! BonusTableViewCell

You are populating Tableview using jBonuses array of Type Bonuses, but where you are filling the jBonuses array.
It seems that you are not filling jBonuses Array. Fill the jBonuses array once you get API response and call tableview reloadData method.
yourTableView.reloadData()

Related

Is there any way to list user defaults datas with swift?

I am trying to do save datas like name, address with user default at main view controller. After saving data, I wanted to list all user default data at second view controller with tableview (PeopleViewController). I managed to save user default data with main view controller. But only last saved user default data can be showed at tableview while I would like to list all data at tableview. What am I doing wrong? Can you give an idea? Thank you in advance..
My second view controller / PeopleViewController
import UIKit
class PeopleListViewController: UIViewController, UITableViewDelegate,UITableViewDataSource {
var names = [String]()
var address = [String]()
let storage = UserDefaults.standard
override func viewDidLoad() {
super.viewDidLoad()
let peopleData = storage.value(forKey: "people") as? Data
if peopleData != nil {
let decoder = JSONDecoder()
do {
let person = try decoder.decode(People.self, from: peopleData!)
names.append(person.name)
address.append(person.address)
}catch {
print(error)
}
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return names.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "LocationCell",for: indexPath)
cell.textLabel?.text = names[indexPath.row]
cell.detailTextLabel?.text = address[indexPath.row]
return cell
}
}
My main view controller
import UIKit
class ViewController: UIViewController {
#IBOutlet var TextFields: [UITextField]!
let storage = UserDefaults.standard
override func viewDidLoad() {
super.viewDidLoad()
let peopleData = storage.value(forKey: "people") as? Data
if peopleData != nil {
let decoder = JSONDecoder()
do {
TextFields[0].text = ""
TextFields[1].text = ""
}catch {
print(error)
}
}
}
#IBAction func createButtonTapped(_ sender: UIButton) {
let name = TextFields[0].text!
let address = TextFields[1].text!
let people = People(
name: name,
address: address
)
let encoder = JSONEncoder()
do {
let peopleData = try encoder.encode(people)
storage.setValue(peopleData, forKey: "people")
TextFields[0].text! = ""
TextFields[1].text! = ""
}catch {
print(error)
}
}
}
My People Model
import Foundation
struct People: Codable{
var name: String
var address: String
}
Right now, you are only storing one person in UserDefaults. Every time you create a new person, you are overwriting the previous value. To store many people, you need an [People]. When you decode and encode, you can encode the whole [People].
// main VC
let encoder = JSONEncoder()
let decoder = JSONDecoder()
do {
// first decode the array from the storage
let array = storage.data(forKey: "people").map { try decoder.decode([People].self, from: $0) } ?? []
array.append(people) // add the newly created person
let newArrayData = try encoder.encode(array)
storage.set(newArrayData, forKey: "people")
TextFields[0].text! = ""
TextFields[1].text! = ""
}catch {
print(error)
}
// table VC
// you don't need two parallel arrays
var people: [People] = []
let storage = UserDefaults.standard
override func viewDidLoad() {
super.viewDidLoad()
do {
people = try storage.data(forKey: "people").map { try decoder.decode([People].self, from: $0) } ?? []
} catch let error {
print(error)
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return people.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "LocationCell",for: indexPath)
cell.textLabel?.text = people[indexPath.row].name
cell.detailTextLabel?.text = people[indexPath.row].address
return cell
}

How do I format my plist array to look nice in a tablecell?

I have some saved plist data that outputs in my tableCell like this:
[findameeting_app.FavoriteLocation(name: "Alcoholics Anon Meeting", address: "2532 Hampton Glen ct, Matthews Nc", latitude: 33.46996, longitude: -111.9845152)]
I'm not sure how to format the decoded array can look normal in a tableCell. Like perhaps just showing the title of the meeting and address under it?
import UIKit
class favoritesListController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var tableData = [String]()
//writing data
override func viewDidLoad() {
super.viewDidLoad()
let realpath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("Favorites.plist")
do {
let data = try Data(contentsOf: realpath)
let decoder = PropertyListDecoder()
do {
let favoriteLocationsArray = try decoder.decode(Array<FavoriteLocation>.self, from: data)
// This is your data ready to use
print(favoriteLocationsArray)
tableData = ["\(favoriteLocationsArray)"] // outputting here.
} catch {
// Handle error
}
} catch {
// Handle error
}
}
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return(tableData.count)
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
// Configure the cell...
cell.textLabel!.text = tableData[indexPath.row]
return(cell)
}
}
1) Create a table view outlet and connect it in Interface Builder
#IBOutlet weak var tableView: UITableView!
2) Connect also dataSource and delegate of the table view in IB
3) Replace
var tableData = [String]()
with
var tableData = [FavoriteLocation]()
4) Replace
let favoriteLocationsArray = try decoder.decode(Array<FavoriteLocation>.self, from: data)
with
self.tableData = try decoder.decode(Array<FavoriteLocation>.self, from: data)
5) Replace
tableData = ["\(favoriteLocationsArray)"] // outputting here.
with
DispatchQueue.main.async {
self.tableView.reloadData()
}
6) Replace
cell.textLabel!.text = tableData[indexPath.row]
with (the table cell style must be set accordingly to enable detailTextLabel)
cell.textLabel!.text = tableData[indexPath.row].name
cell.detailTextLabel?.text = tableData[indexPath.row].address
And remove all parentheses after return e.g. return(cell). return is not a function.

Passing Data from one view controller to another (Image)

Error:
Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
Destination Controller
var getImage = UIImage()
var name = String()
var gender = String()
var house = String()
var ancestry = String()
override func viewDidLoad() {
super.viewDidLoad()
imageView.image = (charData.image)as! UIImage
nameLabel.text! = name
houseLabel.text! = house
// Do any additional setup after loading the view.
}
Source Controller
var charactersData = [Character]()
override func viewDidLoad() {
super.viewDidLoad()
loadData()
}
func loadData()
{
DispatchQueue.main.async {
Alamofire.request("http://hp-api.herokuapp.com/api/characters").responseJSON(completionHandler: {
(response) in
switch response.result
{
case.success(let value):
let json = JSON(value)
print(json)
json.array?.forEach({
(character) in
let character = Character(name: character["name"].stringValue, house:character["house"].stringValue,image:character["image"].stringValue, gender: character["gender"].stringValue, ancestry: character["ancestry"].stringValue)
self.charactersData.append(character)
})
self.tableView.reloadData()
case.failure(let error):
print(error.localizedDescription)
}
})
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return charactersData.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CharTableViewCell
cell.nameLabel.text = "Name: " + charactersData[indexPath.row].name
cell.houseLabel.text = "House: " + charactersData[indexPath.row].house
if let imageURL = URL(string: self.charactersData[indexPath.row].image) {
DispatchQueue.global().async {
let data = try? Data(contentsOf: imageURL)
if let data = data {
let image = UIImage(data: data)
DispatchQueue.main.async {
cell.charImageView.image = image
}
}
}
}
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let hpc = storyboard?.instantiateViewController(withIdentifier: "CharDetails") as? CharDetailsViewController
hpc?.getImage = (charactersData[indexPath.row].image) as! UIImage
hpc?.name = charactersData[indexPath.row].name
hpc?.house = charactersData[indexPath.row].house
self.navigationController?.pushViewController(hpc!, animated: true)
}
Im trying to pass an image to another controller but it seems im getting that error, could someone kindly help me. All the other data like name and house is passing properly other than the image. Kindly please let me know where to make changes
This might work:
override func viewDidLoad() {
super.viewDidLoad()
imageView.image = getimage // change this in view did load method
}
You are doing wrong(this is not UIImage, this is URL string so you can use this url string to download the image):
hpc?.getImage = (charactersData[indexPath.row].image) as! UIImage
Please replace UIImage to String because this is not UIImage, This is String URL to download the image
So For this You have to change getImage variable UIImage to String and pass this String to this variable
var getImage = String()
hpc?.getImage = (charactersData[indexPath.row].image) as! String
After that again download this image from URL in another controller but this way is not good so you to follow below way:
Or
You have another option like when you download the image than save it Character Struct and pass it when didselect
Process:
Add a new variable like image in Character Struct/Model
Assign downloaded image when API call in the Struct/Model
And pass this image to another controller when didSelect

Swift - Code not returning array value for outside closure

Im having a problem with my swift code. I am trying to get the values of yearsLinks array outside of the task closure but I am unable to do so as it returns an empty array and I understand this is because the code runs the print(yearsLinks) code before it can complete the closure.
I need help on what I need to do, some people suggested using async but I am unsure where and how I am supposed to implement it.
PLEASE HELP
I want to set the array values, yearsLinks to the cells in a tableview.
Here is a snippet of my code. Appreciate the help. Thank you so much :)
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var url = String()
var yearsLinks = [String]()
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return yearsLinks.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell2", for: indexPath)
cell.textLabel?.text = yearsLinks[indexPath.row]
return cell
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
if let requests = URL(string: url){
let request = NSURLRequest(url: requests)
let task = URLSession.shared.dataTask(with: request as URLRequest){
data, response, error in
if error != nil {
print(error as Any)
} else {
if let unwrappedData = data {
let wrappedData = NSString(data: unwrappedData, encoding: String.Encoding.utf8.rawValue)
...
// THESE ARE JUST CODE TO GRAB THE ARRAY THAT I WANT
// linkyears IS AN ARRAY OF VALUES DECLARED IN THE CLOSURE THAT I WANT TO SET TO THE yearsLinks VARIABLE DECLARED OUTSIDE.
self.yearsLinks = linkyears
...
print(self.yearsLinks) // THIS WORKS FINE AND RETURNS AN ARRAY OF VALUES THAT I WANTED.
}
}
}
}
// print(yearsLinks)
// TRYING TO ACCESS THE ARRAY OF VALUES IN THE yearsLinks ARRAY ABOVE RETURNS AN EMPTY STRING??
task.resume()
}
}
}
You need to reload the table view after you obtain the data inside the completion block.
if let unwrappedData = data {
let wrappedData = NSString(data: unwrappedData, encoding: String.Encoding.utf8.rawValue)
...
DispatchQueue.main.async {
self.yearsLinks = linkyears
print(self.yearsLinks)
self.tableView.reloadData()
}
}
}

UITableView in a normal View Controller is not updating with my custom cells

I have a TableView placed within a normal View Controller that displays some simple data I have pulled from a JSON. The cell has only labels. In my ViewController Class, I have the following code, but my table view is not displaying the data still:
import UIKit
class EmployeePortal: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var dateLabel: UILabel!
var appData:Array<Dictionary<String,String>>? = nil
var numberOfApps:Int = 0
override func viewDidLoad() {
self.tableView.dataSource = self
self.tableView.delegate = self
let currentDate = Date()
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .full
let date = dateFormatter.string(from: currentDate)
dateLabel.text = date
/////Methods to get data....////
do{
let JSON = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! Array<Dictionary<String,String>>
self.appData = JSON
print(self.appData) ///test just to make sure data is coming in
self.numberOfApps = JSON.count
//JSON file contains everything needed to make appointments
}catch{
print("ERROR DOWNLOADING JSON")
}
}
task.resume()
///I think my problem might be here, not sure how to make the table
//actually load the data
self.tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSections(in tableView: UITableView) -> Int {
// There is only one section
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//The number of rows is equal to the amount of appointments the data returned:
return self.numberOfApps
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "appCell", for: indexPath) as! appCell //made sure to use my custom cell
let row = indexPath.row
cell.patientName.text = self.appData?[row]["pName"]
cell.reasonLabel.text = self.appData?[row]["reason"]
cell.dateLabel.text = self.appData?[row]["date"]
return cell //do all the stuff to update the labels and return it
}
}
Not sure what else I am missing. I have the table view's delegates and data sources set up appropriately. I guess its something small I am missing but I would appreciate any help!
DispatchQueue.main.async - for update the UI why because network call is background queue .
try this way
do{
let JSON = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! Array<Dictionary<String,String>>
self.appData = JSON
print(self.appData) ///test just to make sure data is coming in
self.numberOfApps = JSON.count
DispatchQueue.main.async(execute: { () -> Void in
self.tableView.reloadData()
})
//JSON file contains everything needed to make appointments
}
catch{
print("ERROR DOWNLOADING JSON")
}
Update : -
if let pName = (self.appData[row]["pName"]) as? String {
cell.patientName.text = pName
}