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.
Related
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
}
I have an ios app. I need to store two fields in my database realm(Title and image(or path to image). Now my data is storing in struct video but after reloading the app all changes missed. So how can I implement realm in my app?
My code looks :
struct Video {
var image: UIImage
var title : String }
class VideoCell: UITableViewCell {
var videoImageView=UIImageView()
var videoTitleLabel=UILabel()
}
required init?(coder:NSCoder){
fatalError("init(coder :) has not been implemented")
}
func set(video : Video) {
videoTitleLabel.text = video.title
videoImageView.image = video.image
}
class VideoListVC: UIViewController, UISearchResultsUpdating {
var tableView=UITableView()
var videos : [Video] = []
var videoImageView=UIImageView()
var videoTitleLabel=UILabel()
struct Cells {
static let videoCell = "VideoCell"
}
override func viewDidLoad(){
super.viewDidLoad()
title = ""
videos = fetchData()
configureTableView()
}
func configureTableView(){
view.addSubview(tableView)
setTableViewDelegates()
tableView.rowHeight = 290
tableView.register(VideoCell.self, forCellReuseIdentifier: Cells.videoCell)
tableView.pin(to:view)
} func setTableViewDelegates(){
tableView.delegate=self
tableView.dataSource=self
}
extension VideoListVC: UITableViewDelegate,UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return videos.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: Cells.videoCell) as! VideoCell
let video = videos[indexPath.row]
cell.set(video: video)
return cell
}
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
return .delete
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
tableView.beginUpdates()
videos.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
tableView.endUpdates()
}
}
import realm-swift from github here https://github.com/realm/realm-swift
your data should looks somewhat similar to this
class Video: Object {
#Persisted(primaryKey: true) var _id: String
#Persisted var image: Data
#Persisted var title: String
}
I am assuming you want to persist your data after you fetch.
do {
let realm = try Realm()
try realm.write {
realm.add(video)
}
} catch {
assertionFailure("\(error)")
}
Not sure if there is a way to persist UIImage. I usually convert it to Data when I persist and then initialize the image using the Data when I retrieve.
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 have an array of dictionary saved in User Defaults. I am showing these value in UITableview. When the user right swipes the table cell and remove it, the cell is successfully deleted, but it is not actually deleted from User Defaults.Here what I tried :
var notificationArray: [[String: AnyObject]] = []
var title = [String]()
var detail = [String]()
override func viewDidLoad() {
super.viewDidLoad()
if let tempArray = UserDefaults().array(forKey: "notificationArray") as? [[String: AnyObject]] {
notificationArray = tempArray
self.title = tempArray.flatMap { $0["title"] as? String }
self.detail = tempArray.flatMap { $0["detail"] as? String }
}
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
print("Deleted")
self.title.remove(at: indexPath.row)
self.detail.remove(at: indexPath.row)
self.tableView.deleteRows(at: [indexPath], with: .automatic)
notificationArray.append(["title": title as AnyObject, "detail": detail as AnyObject])
UserDefaults.standard.set(notificationArray, forKey: "notificationArray")
print("title, detail", title, detail)
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return title.count
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
{
return 80
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell : SubCategoryTableViewCell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! SubCategoryTableViewCell
cell.notificationTittleLabel.text = title[indexPath.row]
cell.notificationDetailLabel.text = detail[indexPath.row]
cell.backgroundColor = UIColor.clear
return cell
}
This is worked for me. Thanks, #Paulw11 :)
var myNotificationArray = UserDefaults.standard.object(forKey: "notificationArray") as? [AnyHashable]
myNotificationArray?.remove(at: indexPath.row)
UserDefaults.standard.set(myNotificationArray, forKey: "notificationArray")
UserDefaults.standard.synchronize()
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
}