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
}
Related
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.
My certain two rows of data of UITableViewCell exchange, and this is how it looks.
The row data after exchanged are the right, and they're wrong before exchanged.
And I'm sorry for not have enough reputation to post .gif image.
Is there any way to avoid the data exchange to the wrong row?
my-data-changes-in-uitableviewcell-when-i-scroll-down-and-get-back
ios-uitableview-mixes-up-data-when-scrolling-too-fast
swift-user-input-mixed-up-when-uitableview-reuses-cells
swift-2-tableview-scrolling-changes-data
The links of problems above seem to be related to mine, but I think my problem is not caused by scrolling.
I setup UITableViewCell with DispatchQueue.global(qos: .background).async, and this function is inside the cell itself.
--SensorRecordTableViewCell (UITableViewCell)--
func getNewLiveInfo(shedId: String, sensorCategory: String) {
DispatchQueue.global(qos: .background).async {
let id = self.shedId.cString(using: .utf8)
let shed_id = UnsafeMutablePointer(mutating: id)
let category = self.sensorCategory.cString(using: .utf8)
let sensor_category = UnsafeMutablePointer(mutating: category)
if let data = getliveInfo(shed_id, sensor_category) {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
// record_id, sensor_id, sensor_category, sensor_value, read_time
self.sensorRecord.recordId = String(cString: data.pointee!)
self.sensorRecord.sensorId = String(cString: (data+1).pointee!)
self.sensorRecord.sensorCategory = String(cString: (data+2).pointee!)
self.sensorRecord.value = Double(String(cString: (data+3).pointee!))
self.sensorRecord.time = formatter.date(from: String(cString: (data+4).pointee!))
DispatchQueue.main.async {
self.setValueAndTime()
}
data.deallocate()
}
}
}
And call the function above from func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath)
--UITableViewDelegate, UITableViewDataSource--
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
var cell = cell as! SensorRecordTableViewCell
cell.getNewLiveInfo(shedId: shed.id!, sensorCategory: config.sensorRecordOrder[indexPath.row])
}
Finally, I setup the cell from func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: sensorCellId, for: indexPath) as! SensorRecordTableViewCell
cell.setUpView(shedId: shed.id!, sensorCategory: config.sensorRecordOrder[indexPath.row])
return cell
}
Is there any way to avoid the data exchange to the wrong row?
Here is a simple example to show what it should looks like,
hope it helps.
class SensorRecord {
var recordID: Int = 0
var sensorID: Int = 0
}
class ViewController: UIViewController {
private var dataSource = [SensorRecord]()
override func viewDidLoad() {
super.viewDidLoad()
// request infos from your server
getNewLiveInfo(completion: { (sensorRecords)
// after the request success, reload data
self.dataSource = sensorRecords
self.tableView.reloadData()
})
}
}
// MARK: UITableViewDataSource
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataSource.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ID", for: indexPath)
// let your cell to show datas from your server
let sensorRecord = dataSource[indexPath.row]
cell.update(with: sensorRecord)
return cell
}
}
I can't seem to understand how a JSON array be edited when presented in a UITableView?
The JSON array looks like the following example:
[
{
"customer": "John",
"status": "Yes",
},
{
"customer": "James",
"status": "No",
},
{
"customer": "Jamie",
"status": "No",
}
]
The way the app currently presents the data in the UITableView:
private func fetchJSON() {
guard let url = URL(string: "https://example/example/example"),
let value = driver.addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed)
else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = "driver=\(value)".data(using: .utf8)
URLSession.shared.dataTask(with: request) { data, _, error in
guard let data = data else { return }
do {
self.structure = try JSONDecoder().decode([Structure].self,from:data)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
catch {
print(error)
}
}.resume()
}
TableView Delegate code so far:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if isFiltering() {
return pickup.count
}
print(structure.count)
return structure.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "testingCell", for: indexPath)
let portfolio: Structure
portfolio = structure[indexPath.row]
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.cellForRow(at: indexPath)?.accessoryType = .checkmark
}
override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
tableView.cellForRow(at: indexPath)?.accessoryType = .none
}
I would like to be able to select the customers names James and Jamie and press a button that will change the status from No to Yes.
How can the selection and update mechanism be created from the apps perspective?
UPDATE:
structure is defined as: var structure = [Structure]()
import UIKit
struct Structure: Codable {
let customer: String
let status: String
}
UPDATE 2:
I am working with the following but I cannot seem to update the API:
func saveToDB(_ structure: Structure) {
do{
let data = try JSONEncoder().encode(structure)
if let str = String(data: data, encoding: .utf8) {
print(str)
guard let url = URL(string: "https://example/example/example"),
else { return }
}
} catch {
print(error)
}
}
You must change the status in structure array w.r.t the selected indexPath and not in the JSON directly.
In tableView(_: didSelectRowAt:) and tableView(_: didDeselectRowAt:), you can simply change the status value of structure at that particular indexPath like so,
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
structure[indexPath.row].status = "Yes"
tableView.cellForRow(at: indexPath)?.accessoryType = .checkmark
}
override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
structure[indexPath.row].status = "No"
tableView.cellForRow(at: indexPath)?.accessoryType = .none
}
In case the issue is not resolved, do add the definition of Structure along with more details, so I can help you further.
Edit:
First of all, the definition of Structure goes like,
class Structure: Codable {
var customer: String
var status: String
}
The code to update the status of structure an indexPath is:
class VC: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
var structures = [Structure]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.allowsMultipleSelection = true
fetchJSON()
}
private func fetchJSON() {
//code to fetch data from JSON...
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return structures.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "testingCell", for: indexPath)
cell.textLabel?.text = structures[indexPath.row].customer
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let structure = structures[indexPath.row]
structure.status = "Yes"
saveToDB(structure)
tableView.cellForRow(at: indexPath)?.accessoryType = .checkmark
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
let structure = structures[indexPath.row]
structure.status = "No"
saveToDB(structure)
tableView.cellForRow(at: indexPath)?.accessoryType = .none
}
func saveToDB(_ structure: Structure) {
//add the code to save structure in DB...
}
}
Now, when saving to the DB, there is no need for the JSON response. You can directly save the structure instance in the DB.
Edit-2:
Update the saveToDB(_:) method to:
func saveToDB(_ structure: Structure) {
do{
let data = try JSONEncoder().encode(structure)
if let str = String(data: data, encoding: .utf8) {
print(str)
//str is the updated Structure that you can send in your API...
}
} catch {
print(error)
}
}
In the above method, you'll get the JSON format of the updated structure instance that you can use to call your API.
Hope this will resolve your problem
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "testingCell", for: indexPath)
let portfolio: Structure = structure[indexPath.row]
cell.accessoryType = portfolio.status == "YES" ? .checkmark : .none
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let status = structure[indexPath.row].status // get current status
structure[indexPath.row].status = status == "YES" ? "NO" : "YES" /// toggle status
tableView.reloadData()
}
No need didDeselectRowAt method.
I am trying to download image from server and want to load images inside my cell but as i am downloading inside cellForRowAt method it wont get height for the first time. If i scroll up and scroll down again the image will have proper height.
Using Kingfisher to download images from server
var homeList = [NSDictionary]()
var rowHeights : [Int:CGFloat] = [:]
func numberOfSections(in tableView: UITableView) -> Int
{
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return homeList.count
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if let height = self.rowHeights[indexPath.row]{
print(" Height \(height)")
return height
}
else{
return 160
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let homeObject = homeList[safe: indexPath.row] {
if let dynamicURL = homeObject["dynamic_card_url"] as? String, dynamicURL != "" {
tableView.register(UINib(nibName: "DynamicCell", bundle: nil), forCellReuseIdentifier: "\(indexPath.row)")
let cell = tableView.dequeueReusableCell(withIdentifier: "\(indexPath.row)", for: indexPath) as! DynamicCell
KingfisherManager.shared.downloader.downloadImage(with: URL(string: dynamicURL)!, options: .none, progressBlock: nil, completionHandler: { (image, error, url, data) in
DispatchQueue.main.async {
if (image != nil || url != nil){
let aspectRatio = (image! as UIImage).size.height/(image! as UIImage).size.width
cell.dynamicImageView.image = image
let imageHeight = self.view.frame.width*aspectRatio
self.rowHeights[indexPath.row] = imageHeight
}else{
print("Image or URL is nil")
}
}
})
cell.selectionStyle = .none
cell.backgroundColor = UIColor.clear
return cell
}
}
}
When you downloaded your image you should reload your cell to change it size to appropriate one. You get right sizes as you scrolling because tableView calls heightForRowAt when it needs to display new cell. So inside in DispatchQueue.main.async { reload the cell after setting all necessary properties UITableView().reloadRows(at: [IndexPath], with: UITableViewRowAnimation.automatic)
I used this guys suggestion: https://stackoverflow.com/a/33499766/8903213
code:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.imageView?.kf.setImage(with: URL(string: urlOfPhoto)!, placeholder: PlaceholderImages.user, completionHandler: {
(image, error, cacheType, imageUrl) in
cell.layoutSubviews()
})
...
and this seems to be working for me.
I'm trying to populate a table view with a large data. However, my tableview can only display 20 string objects. How do I simply display the rest of the data and update the table view each time the user scrolls to the end?
var people = [People]()
let configureSession = URLSessionConfiguration.default
let session = URLSession(configuration: configure)
//Setup the Api Key...
let apiKey = "https://congress.api.sunlightfoundation.com/legislators?apikey=(//Api Key here...)"
if error != nil{
print("ERROR: \(error?.localizedDescription)")
return
} else if let jsonData = data {
do{
let parsedJSON = try JSONSerialization.jsonObject(with: jsonData, options: []) as! [String: AnyObject]
guard let results = parsedJSON["results"] as? [[String: AnyObject]] else {return}
for result in results{
let name = myClass()
name.firstName = result["first_name"] as! String
self.people.append(name)
}
DispatchQueue.main.async {
//Reload the data
self.table.reloadData()
}
} catch let error as NSError{
print(error)
}
}
}).resume()
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return people.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")
cell.textLabel?.text = people[indexPath.row] //Only displays 20... Need to display more!
return cell!
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
//Code to display the rest of the data goes here?
}
Make sure you return the correct value in numberOfRowsInSection method:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myLargeData.count
}
With provided myLargeData array there should be 30 rows in your tableView
You can use number of rows in section delegate of table view delegate to handle the more data in array.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return yourarray.count
}