How to Display two different data from json on same tableview - ios

I am trying to put 2 diff data from same json on same tableview but I am not able to do! In every case, I can put only one. I want to display .name and .email at the same time
Thanks
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var userInfo = [UserData]()
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
JsonDownload
{
self.tableView.reloadData()
}
tableView.delegate = self
tableView.dataSource = self
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return userInfo.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
cell.textLabel?.text = userInfo[indexPath.row].name
return cell
}
func JsonDownload(completed: #escaping () -> ()) {
let source = URL(string: "https://jsonplaceholder.typicode.com/users")
URLSession.shared.dataTask(with: source!) { (data, response, error) in
if let data = data {
do
{
self.userInfo = try JSONDecoder().decode([UserData].self, from: data)
DispatchQueue.main.async
{
completed()
}
}
catch
{
print("Json Error")
}
}
}.resume()
}
}

here is how you can display .name and .email at the same time.. but instead of creating new cell each time .. use deque
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "Cell"){
cell.textLabel?.text = userInfo[indexPath.row].name
cell.detailTextLabel?.text = userInfo[indexPath.row].email
return cell
}
return UITableViewCell()
}

If you want to use the default cell provided by Apple.
You can you textLabel and detailTextLabel. Another better way is creating your custom tableviewcell.

Related

How to parse JSON to the tableView in Swift?

Please give me advise, I can not figure out how to parse data in a table view properly. My goal is to make a tableView with all continents, not just with one "Africa" cell.
Here is my model:
struct ContinentRoot: Codable {
let links: ContinentMiddle
}
struct ContinentMiddle: Codable {
let continentItems: [ContinentsResponse]
}
struct ContinentsResponse: Codable {
let name: String
let href: String
}
In ViewController I add tableView, continentsArray ([ContinentRoot]) and do some regular things for networking.
I guess that the problem may be here, because in the networking method everything seems normal:
private func getContinentsList() {
guard let url = URL(string: "https://api.teleport.org/api/continents/") else { fatalError("URL failed")}
URLSession.shared.dataTask(with: url) { [weak self] (data, response, error) in
if let data = data {
guard let continent = try? JSONDecoder().decode(ContinentRoot.self, from: data) else { fatalError("DecodingError \(error!)") // REMEMBER: the highest struct
}
self?.continentsArray.append(continent)
}
DispatchQueue.main.async {
self?.tableView.reloadData()
}
}.resume()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return continentsArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ContinentsTableViewController", for: indexPath)
let model = continentsArray[indexPath.row].links.continentItems[indexPath.row].name
cell.textLabel?.text = model
return cell
}
In viewDidLoad() I call my methods:
getContinentList()
tableView.delegate = self
tableView.dataSource = self
tableView.register(ContinentsTableCell.self, forCellReuseIdentifier: "ContinentsTableViewController")
setupLayout()
Thank you so much for for attention!
According to your attachment design:
if continentsArray is an array of "ContinentRoot" s.
and you want to show the links in the selected ContinentRoot you must first select it, and use it like below:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return selectedContinent.links.continentItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ContinentsTableViewController", for: indexPath)
let model = selectedContinent.links.continentItems[indexPath.row].name
cell.textLabel?.text = model
return cell
}
if Not you must use your code and change this line:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ContinentsTableViewController", for: indexPath)
let selectedIndex = .zero // or every index you want
let model = continentsArray[indexPath.row].links.continentItems[selectedIndex].name
cell.textLabel?.text = model
return cell
}

Using a completion handler in another class/extension

I have a class, LocationViewController, which needs to implement a TableView. I have a function getParsedTestingLocation() which uses a completion handler from another function to get some data.
class LocationViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
func getParsedTestingLocations(completion: #escaping ([TestingLocation]?, Error?) -> (Void)) {
getTestingLocationsJSON(completion: { testLocationsJSON, error in
if let testLocationsJSON = testLocationsJSON {
let testLocationsData = Data(testLocationsJSON.utf8)
let decoder = JSONDecoder()
do {
let testLocations = try decoder.decode([TestingLocation].self, from: testLocationsData)
completion(testLocations, nil)
} catch {
print(error)
}
}
})
}
}
I want to use the value testLocations within getParsedTestingLocations() in an external extension in this file. Here are the extensions I have:
extension LocationViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("you tapped me!")
}
}
extension LocationViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = "empty cell"
return cell
}
}
Within all 3 tableView() functions I want to get the values stored in testLocations in the completion handler in these functions. How could I do this?
Actually you don't need a completion handler. Reload the table view inside the completion closure of the API call
class LocationViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var locationData = [TestingLocation]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
getParsedTestingLocations()
}
func getParsedTestingLocations() {
getTestingLocationsJSON(completion: { testLocationsJSON, error in
if let testLocationsJSON = testLocationsJSON {
let testLocationsData = Data(testLocationsJSON.utf8)
let decoder = JSONDecoder()
do {
self.locationData = try decoder.decode([TestingLocation].self, from: testLocationsData)
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch {
print(error)
}
}
})
}
}
extension LocationViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return locationData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let location = locationData[indexPath.row]
cell.textLabel?.text = location.whatEverStringYouWantToDisplay
return cell
}
}
Replace whatEverStringYouWantToDisplay with the real struct member name.
You don't need a completionHandler in getParsedTestingLocations in this case as the function already calls a function which has completionHandler. Just use a variable
class LocationViewController: UIViewController {
private lazy var locationArr = [TestingLocation]()
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
getParsedTestingLocations()
}
func getParsedTestingLocations() {
getTestingLocationsJSON(completion: { testLocationsJSON, error in
if let testLocationsJSON = testLocationsJSON {
let testLocationsData = Data(testLocationsJSON.utf8)
let decoder = JSONDecoder()
do {
let testLocations = try decoder.decode([TestingLocation].self, from: testLocationsData)
self.locationArr = testLocations
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch let error {
//Show Alert
}
}
})
}
}
extension LocationViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return locationArr.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = locationArr[indexPath.row].variable
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("you tapped me! \(locationArr[indexPath.row])")
}
}

Custom xib cells are not appearing on UITableView swift

my cells are not appearing.
I did:
Checked if datasource and delegate were connected
Checked if my custom cells identifier name and class were correct
Things that I didn't:
I am struggling with auto layout, so I just decided not to do it.
My app is loading with the correct amount of cells, but the cells are not registered.
My code:
import UIKit
class WelcomeViewController: UITableViewController, NetworkManagerDelegate {
private var networkManager = NetworkManager()
private var infoForCells = [Result]()
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.register(UINib(nibName: "ImageViewCell", bundle: nil), forCellReuseIdentifier: "imageCell")
networkManager.delegate = self
networkManager.fetchNews()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return infoForCells.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "imageCell", for: indexPath) as? ImageViewCell else{
return UITableViewCell(style: .default, reuseIdentifier: "cell")
}
let cellIndex = infoForCells[indexPath.row]
cell.titleForImage.text = cellIndex.alt_description
print(cell.titleForImage ?? "lol")
// if let image = cellIndex.urlToImage {
// cell.imageForArticle.load(url: image)
// }
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
func didUpdateNews(root: Root) {
infoForCells = root.results
}
}
Reload the table
func didUpdateNews(root: Root) {
infoForCells = root.results
tableView.reloadData()
}
In addition to Sh_Khan answer you can also listen to updates of infoForCells property
private var infoForCells = [Result]() {
didSet {
DispatchQueue.main.async { [weak self] in
self?.tableView.reloadData()
}
}
}

how to use indexpath.row outside the table function

my tableview is
struct country : Decodable {
let name : String
let capital : String
let region : String
}
class ViewController: UIViewController {
var countries = [country]()
let color = UIColor()
#IBOutlet weak var tableview: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableview.delegate = self
tableview.dataSource = self
let jsonurl = "https://restcountries.eu/rest/v2/all"
let url = URL(string: jsonurl)
URLSession.shared.dataTask(with: url!) { (data, response, error) in
do {
self.countries = try JSONDecoder().decode([country].self, from: data!)
} catch {
print("Error")
}
DispatchQueue.main.async {
self.tableview.reloadData()
}
}.resume()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func redButtonAction(_ sender: UIButton) {
let index = IndexPath.init(row: 0, section: 0)
let cell = tableview.cellForRow(at: index)
}
}
extension ViewController:UITableViewDelegate,UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection
section: Int) -> Int {
return countries.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath:
IndexPath) -> CGFloat {
return 40
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableview.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = countries[indexPath.row].name.capitalized
let cellnumber = indexPath.row
return cell
}
now I want to use cellnumber or indexpath.row in my button action. I do this but i can not get indexpath.row
I want to do when I pressed the button at that time the odd number cell of the tableview 's background color change in red and even number cell 's background color change blue. But problem is out side tableview function I get only one value not whole number of array. In above program if I print cellnumber we get whole number of cell.
table view cell is reusable and you can get only visible cell's indexPath so you should reload table view on clicked of button and need to put condition if you want to change color or not in cellForRowAt
Code for change row color on button clicked
struct country : Decodable {
let name : String
let capital : String
let region : String
}
class ViewController : UIViewController {
var countries = [country]()
let color = UIColor()
var needToChangeColor : Bool = false
#IBOutlet weak var tableview: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableview.delegate = self
tableview.dataSource = self
let jsonurl = "https://restcountries.eu/rest/v2/all"
let url = URL(string: jsonurl)
URLSession.shared.dataTask(with: url!) { (data, response, error) in
do {
self.countries = try JSONDecoder().decode([country].self, from: data!)
} catch {
print("Error")
}
DispatchQueue.main.async {
self.tableview.reloadData()
}
}.resume()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func redButtonAction(_ sender: UIButton) {
self.needToChangeColor = true
self.tableview.reloadData()
}
}
extension ViewController :UITableViewDelegate,UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection
section: Int) -> Int {
return countries.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath:
IndexPath) -> CGFloat {
return 40
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableview.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = countries[indexPath.row].name.capitalized
if needToChangeColor == true {
if indexPath.row % 2 == 0 {
cell.backgroundColor = UIColor.blue
} else {
cell.backgroundColor = UIColor.red
}
} else {
cell.backgroundColor = UIColor.white
}
return cell
}
}
The very basic solution would be adding a tag for your button in cellForRowAt. It will work in case you have only one section.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableview.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = countries[indexPath.row].name.capitalized
cell.button.tag = indexPath.row
return cell
}
Now in your method
#IBAction func redButtonAction(_ sender: UIButton) {
let index = IndexPath.init(row: sender.tag, section: 0)
let cell = tableview.cellForRow(at: index)
}
Unfortunately this approach will not be so efficient if you add any complexity to your tableView layout.

Swift3, CloudKit and UITableView returns blank table

I am having a small mental block here, I am pretty comfortable with Core Data and decided to have a delve into CloudKit for some of my Apps, however whilst the upload side was fairly basic, I am having trouble populating a simple table view.
The CKRecord is Activity and the field I would like to display is name. The print function print(actName) returns 7 times showing all the records have been counted but the table is blank and no errors.
I am sure this is something simple and i can't see the wood for the trees, so I am happy for a point in the right direction please.
Cheers
import UIKit
import CloudKit
class TableViewController: UITableViewController {
var activities = [Activity]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("counted records")
return activities.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let active = activities[indexPath.row]
if let actName = active.name {
cell.textLabel?.text = actName
print(actName)
}
return cell
}
I seem to have sorted it out, user3069232, I don't think there was/is a great latency problem as the updating code was instantaneous with CloudKit Desktop. Nirav I think you were right it boiled down to not storing and then reloading. I have modified my original code as I think 'Activity' was causing problems too, the script below works well, thanks for the point in the right direction guys.
import UIKit
import CloudKit
class CoursesVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var coursesTable: UITableView!
var coursesArray: Array<CKRecord> = []
var selectedCourseIndex: Int!
override func viewDidLoad() {
super.viewDidLoad()
coursesTable.delegate = self
coursesTable.dataSource = self
fetchCourses()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return coursesArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "courseCell", for: indexPath)
let courseRecord: CKRecord = coursesArray[indexPath.row]
cell.textLabel?.text = courseRecord.value(forKey: "courseVenue") as? String
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd MMMM yyyy"
let CSDate = dateFormatter.string(from: courseRecord.value(forKey: "courseDate") as! Date)
// let CSDetail = courseRecord.value(forKey: "courseDetail") as? String
cell.detailTextLabel?.text = CSDate
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 40.0
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedCourseIndex = indexPath.row
performSegue(withIdentifier: "showMapDetail", sender: self)
}
func fetchCourses() {
let container = CKContainer.default()
let publicDatabase = container.publicCloudDatabase
let predicate = NSPredicate(format: "TRUEPREDICATE")
let query = CKQuery(recordType: "Courses", predicate: predicate)
query.sortDescriptors = [NSSortDescriptor(key: "courseDate", ascending: true)]
publicDatabase.perform(query, inZoneWith: nil) { (results, error) -> Void in
if error != nil {
print("error fetch notes \(error)")
} else {
print("Success")
for result in results! {
self.coursesArray.append(result )
}
OperationQueue.main.addOperation({ () -> Void in
self.coursesTable.reloadData()
self.coursesTable.isHidden = false
})
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}

Resources