Here is my code :
class SchedulerViewController: UIViewController, UITableViewDelegate, UITableViewDataSource,
scheduleCellDelegate
{
var scheduleArray : Array<Array<String>>?
var scheduler : [String : Array<Array<String>>]?
var deviceID : String = ""
let retrievedString = KeychainWrapper.standard.string(forKey: "token")
var day = ""
var dayNum = 0
@IBOutlet weak var spinner: UIActivityIndicatorView!
@IBOutlet var buttons: [UIButton]!
@IBOutlet weak var scheduleView: UITableView!
var header : HTTPHeaders? = nil
var ScheduleURL : Dictionary<String, String>?
override func viewDidLoad() {
super.viewDidLoad()
scheduleView.delegate = self
scheduleView.dataSource = self
spinner.isHidden = true
//scheduleView.allowsSelection = false
scheduleView.register(UINib(nibName: "schedulerCell", bundle: nil), forCellReuseIdentifier: "schedulerCell")
self.getData()
}
func getData(){
AFFunctions.getAFRequest(ofType: ScheduleResponse.self, url: ScheduleURL!["GET"]!) { responseData, statusCode in
print(responseData?.data?.scheduler, statusCode)
self.scheduler = responseData?.data?.scheduler
DispatchQueue.main.async {
self.scheduleView.reloadData()
}
}
}
var buttonNum : Int?
@IBAction func daySelected(_ sender: UIButton) {
self.buttons.forEach { $0.tintColor = ($0 == sender) ? UIColor.orange : UIColor.systemTeal }
self.dayNum = sender.tag
switch dayNum {
case 0 : self.day = "Sunday"
case 1 : self.day = "Monday"
case 2 : self.day = "Tuesday"
case 3 : self.day = "Wednesday"
case 4 : self.day = "Thursday"
case 5 : self.day = "Friday"
case 6 : self.day = "Saturday"
default : self.day = "Sunday"
}
showDetail(day : day,dayNum : dayNum)
}
func showDetail(day : String, dayNum : Int) {
if let dayArray = scheduler?[day]
{
scheduleArray = dayArray
self.scheduleView.reloadData()
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return scheduleArray?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = Bundle.main.loadNibNamed("scheduleCell", owner: self, options: nil)?.first as! scheduleCell
cell.cellDelegate = self
cell.editBtn.tag = indexPath.row
cell.deleteSchedule.tag = indexPath.row
scheduleArray = scheduler![self.day]
/////////////THE BELOW STATEMENT THROWS THE ERROR ///////////////////
if let firstLabel = self.scheduleArray?[indexPath.row][0], let secondLabel = self.scheduleArray?[indexPath.row][1] {
DispatchQueue.main.async {
cell.timeLabel1.text = firstLabel
cell.timeLabel2.text = secondLabel
}
}
return cell
}
func didPressButton(_ tag: Int, btnType: String) {
let deleteURL = K.delURL
if(btnType == "delete") {
AFFunctions.deleteAFRequest(ofType: scheduleResponse.self, url: "\(deleteURL)?day=\(self.day)&place=\(tag)") { [self]
responseData, statusCode in
if(statusCode == 200){
let deleteAlert = UIAlertController(title: "Deleted", message: "Device Schedule Successfully Deleted", preferredStyle: UIAlertController.Style.alert)
deleteAlert.addAction(UIAlertAction(title: "Ok", style: .default, handler: { (action: UIAlertAction!) in
self.scheduler![self.day]?.remove(at: tag)
self.scheduleView.reloadData()
}))
self.present(deleteAlert, animated: true, completion: nil)
}
}
}
}
}
I am doing the changes in the array locally as data is fetched only once in ViewDidLoad() with getData function. It shows a schedule for each day of the week (7 buttons, one for each day, are linked to an Outlet Collection), with 2 buttons embedded in the custom cell nib - an edit button and a delete button. I have implemented the logic for deleting, button tags are equal to IndexPath.row which works perfectly and I am able to delete the values I want but when I can't seem to get the table reload working. Even after deleting the row data, the table doesn't update itself. I am calling reloadData after successful deletion. What am I doing wrong?
CodePudding user response:
there are 2 issues
- when you update something on UI after a request call, you have to push the UI update process back to the main thread (tableview.reloadData() should get triggered on the main thread)
- self.scheduleArray?[indexPath.row][0] and self.scheduleArray?[indexPath.row][1] is the issue index out of range. Because you assume it always contains 2 items without safe check.
I've refactored the code a bit as below
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "scheduleCell") else {
return Bundle.main.loadNibNamed("scheduleCell", owner: self, options: nil)?.first as! scheduleCell
}
cell.cellDelegate = self
cell.editBtn.tag = indexPath.row
cell.deleteSchedule.tag = indexPath.row
scheduleArray = scheduler?[self.day]
guard let item = scheduleArray?[indexPath.row], item.count == 2 else {
return cell
}
if let firstLabel = item.first, let secondLabel = item.last {
cell.timeLabel1.text = firstLabel
cell.timeLabel2.text = secondLabel
}
return cell
}
func didPressButton(_ tag: Int, btnType: String) {
let deleteURL = K.delURL
if(btnType == "delete") {
AFFunctions.deleteAFRequest(ofType: scheduleResponse.self, url: "\(deleteURL)?day=\(self.day)&place=\(tag)") { [self]
responseData, statusCode in
if(statusCode == 200){
// This has to be executed on the main thread to get tableView updated
DispatchQueue.main.async {
let deleteAlert = UIAlertController(title: "Deleted", message: "Device Schedule Successfully Deleted", preferredStyle: UIAlertController.Style.alert)
deleteAlert.addAction(UIAlertAction(title: "Ok", style: .default, handler: { (action: UIAlertAction!) in
self.scheduler![self.day]?.remove(at: tag)
self.scheduleView.reloadData()
}))
self.present(deleteAlert, animated: true, completion: nil)
}
}
}
}
}
CodePudding user response:
The issue here is you are holding multiple sources of truth and fail at synchronysing them. You have:
var scheduleArray : Array<Array<String>>?
var scheduler : [String : Array<Array<String>>]?
Where both seem to hold the same information in different form. I can´t see why you are doing this from the example code you posted.
You get an error at:
self.scheduleArray?[indexPath.row][0]
because when you delete your item you are removing it from scheduler
and reload your tableview. The tableview on the other hand get´s the information how many rows it should render from scheduleArray
:
return scheduleArray?.count ?? 0
and these differ at that time because you didn´t assign scheduler
to scheduleArray
.
So 2 possible solutions here:
assign
scheduleArray
before you reload your tableviewself.scheduler![self.day]?.remove(at: tag) scheduleArray = scheduler![self.day]
and remove the assignment in the cellForItemAt
function
- stop using
scheduleArray
andscheduler
. Use only a single collection to hold the information.
CodePudding user response:
Solved it! Updated Code :
class SchedulerViewController: UIViewController, UITableViewDelegate, UITableViewDataSource,
scheduleCellDelegate
{
var scheduleArray : Array<Array<String>>?
var scheduler : [String : Array<Array<String>>]?
var deviceID : String = ""
let retrievedString = KeychainWrapper.standard.string(forKey: "token")
var day = ""
var dayNum = 0
@IBOutlet weak var spinner: UIActivityIndicatorView!
@IBOutlet var buttons: [UIButton]!
@IBOutlet weak var scheduleView: UITableView!
var header : HTTPHeaders? = nil
var ScheduleURL : Dictionary<String, String>?
override func viewDidLoad() {
super.viewDidLoad()
scheduleView.delegate = self
scheduleView.dataSource = self
spinner.isHidden = true
//scheduleView.allowsSelection = false
scheduleView.register(UINib(nibName: "schedulerCell", bundle: nil), forCellReuseIdentifier: "schedulerCell")
self.getData()
}
func getData(){
self.header =
[
"Content-Type" : "application/json",
"Authorization": retrievedString!
]
//scheduleView.register(scheduleCell.self, forCellReuseIdentifier: "scheduleCell")
print(ScheduleURL!["GET"])
AFFunctions.getAFRequest(ofType: ScheduleResponse.self, url: ScheduleURL!["GET"]!) { responseData, statusCode in
print(responseData?.data?.scheduler, statusCode)
self.scheduler = responseData?.data?.scheduler
self.scheduleView.reloadData()
}
}
var buttonNum : Int?
@IBAction func daySelected(_ sender: UIButton) {
self.buttons.forEach { $0.tintColor = ($0 == sender) ? UIColor.orange : UIColor.systemTeal }
self.dayNum = sender.tag
print(sender.tag)
switch dayNum {
case 0 : self.day = "Sunday"
case 1 : self.day = "Monday"
case 2 : self.day = "Tuesday"
case 3 : self.day = "Wednesday"
case 4 : self.day = "Thursday"
case 5 : self.day = "Friday"
case 6 : self.day = "Saturday"
default : self.day = "Sunday"
}
print(day, dayNum)
showDetail(day : day)
}
func showDetail(day : String) {
print(day)
print(scheduler?[day])
if let dayArray = scheduler?[day]
{ print(dayArray)
scheduleArray = dayArray
self.scheduleView.reloadData()
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("111 CHECK")
return scheduleArray?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = Bundle.main.loadNibNamed("scheduleCell", owner: self, options: nil)?.first as! scheduleCell
cell.cellDelegate = self
cell.editBtn.tag = indexPath.row
cell.deleteSchedule.tag = indexPath.row
if let item = scheduleArray?[indexPath.row],
item.count > 1 {
DispatchQueue.main.async {
cell.timeLabel1.text = item[0]
cell.timeLabel2.text = item[1]
}
}
return cell
}
func didPressButton(_ tag: Int, btnType: String) {
let deleteURL = K.delURL
print("134", self.day)
print("135", self.scheduleArray?[tag])
print("136", scheduler?[self.day])
print("TAG : ", tag)
print("BTN TYPE: ", btnType)
if(btnType == "delete") {
AFFunctions.deleteAFRequest(ofType: scheduleResponse.self, url: "\(deleteURL)?day=\(self.day)&place=\(tag)") { [self]
responseData, statusCode in
print("\(deleteURL)?day=\(self.day)&place=\(tag)")
print(responseData, statusCode)
if(statusCode == 200){
self.scheduler![self.day]!.remove(at: tag)
DispatchQueue.main.async {
let deleteAlert = UIAlertController(title: "Deleted", message: "Device Schedule Successfully Deleted", preferredStyle: UIAlertController.Style.alert)
deleteAlert.addAction(UIAlertAction(title: "Ok", style: .default, handler: { (action: UIAlertAction!) in
self.showDetail(day: self.day)
// self.scheduleView.reloadData()
}))
self.present(deleteAlert, animated: true, completion: nil)
}
}
}
}
}
}