UITableView Custom cell data mismatched on fast scrolling - ios

class CalenderCell: UITableViewCell {
#IBOutlet var lblDay: UILabel!
#IBOutlet var lblRest: UILabel!
#IBOutlet var imgCompleted: UIImageView!
override func awakeFromNib()
{
super.awakeFromNib()
}
func updateCell(exerciseobject : Exercise)
{
let resttext = NSLocalizedString("restday", comment: "")
let day = NSLocalizedString("day", comment: "")
let dayexercise = "\(day) \(exerciseobject.exerciseDayID)"
if !exerciseobject.exerciseRestDay
{
lblDay.text = dayexercise
if exerciseobject.exerciseDayStatus
{
imgCompleted.isHidden = false
}
else
{
imgCompleted.isHidden = true
}
lblRest.isHidden = true
}
else
{
lblDay.isHidden = true
imgCompleted.isHidden = true
lblRest.text = resttext
viewWithTag(100)?.backgroundColor = UIColor(red:1.00, green:0.21, blue:0.28, alpha:1.0)
}
}
}
The above is my custom cell class and the below is my tableview class.
When I try to build the app everything is working perfect but whenever I fast scroll the data is mismatching or same data appering in more cells.
I tried:
prepareforreuse()
{
//clearing label and images here
}
in my custom cell class but still getting the fast scrollview data mismatch
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
if let cell = calenderTable.dequeueReusableCell(withIdentifier: "CalenderCell", for: indexPath) as? CalenderCell
{
cell.updateCell(exerciseobject: days[indexPath.row])
return cell
}
else
{
return UITableViewCell()
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return days.count
}

Either use prepareForReuse and reset your controls to default values or make sure to update all the state for every path in the updateCell method.
e.g.
func updateCell(exerciseobject : Exercise)
{
let resttext = NSLocalizedString("restday", comment: "")
let day = NSLocalizedString("day", comment: "")
let dayexercise = "\(day) \(exerciseobject.exerciseDayID)"
if !exerciseobject.exerciseRestDay
{
lblDay.text = dayexercise
lblDay.isHidden = false
if exerciseobject.exerciseDayStatus
{
imgCompleted.isHidden = false
}
else
{
imgCompleted.isHidden = true
}
lblRest.isHidden = true
viewWithTag(100)?.backgroundColor = nil
}
else
{
lblDay.isHidden = true
imgCompleted.isHidden = true
lblRest.text = resttext
lblRest.isHidden = false
viewWithTag(100)?.backgroundColor = UIColor(red:1.00, green:0.21, blue:0.28, alpha:1.0)
}
}

Related

Swift5 Get the "invalid number of rows in section" error when trying to delete a row in tableview (task manager with core data)

Trying to create the Task manager App with core data. I have an array with all done and current tasks. I divide it for two arrays to load a tableview (it shows either current tasks or tasks are done). When I'm trying to delete a task with swipe on the table, I receive "invalid number of rows in section" error. My code is below. Please advise how to fix it? My thanks in advance.
import UIKit
import Foundation
protocol TaskNotesDelegate: AnyObject {
func refreshTasks()
func deleteTask(with id: UUID)
}
class TasksViewController: UIViewController {
#IBOutlet weak var doneTaskSwitcher: UIBarButtonItem!
#IBOutlet weak var addTaskButton: UIBarButtonItem!
static let identifier = "TasksViewController"
let tableView: UITableView = {
let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
return tableView
}()
let idTasksCell = "idTasksCell"
//константа для реализации функции поиска через searc controllr
let searchController = UISearchController(searchResultsController: nil)
var allTasks: [Task] = []
var doneTasks: [Task] = []
var currentTask: [Task] = []
private var filteredTasks: [Task] = []
var currentSearch = ""
var doneTasksCheck = false
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
tableView.separatorStyle = .none
tableView.register(TaskTableViewCell.self, forCellReuseIdentifier: idTasksCell)
configureSearchBar()
setConstraints()
fetchTasksFromStorage()
for i in 0..<allTasks.count {
if allTasks[i].taskIsDone {
doneTasks.append(allTasks[i])
} else {
currentTask.append(allTasks[i])
}
}
for i in 0..<allTasks.count {
print("all \(allTasks[i].id)")
}
override func viewWillAppear(_ animated: Bool) {
self.navigationController?.navigationBar.backgroundColor = .white
self.tabBarController?.tabBar.backgroundColor = .white
tableView.reloadData()
}
#IBAction func showDoneTasks(_ sender: UIBarButtonItem) {
if doneTasksCheck == false {
doneTasksCheck = true
doneTaskSwitcher.title = "current"
doneTasks = doneTasks.sorted { $0.taskDate > $1.taskDate }
tableView.reloadData()
addTaskButton.isEnabled = false
} else {
doneTasksCheck = false
doneTaskSwitcher.title = "is done"
tableView.reloadData()
addTaskButton.isEnabled = true
}
}
private func indexForTask(id: UUID, in list: [Task]) -> IndexPath {
let row = Int(list.firstIndex(where: { $0.id == id }) ?? 0)
return IndexPath(row: row, section: 0)
}
#IBAction func addTaskButton(_ sender: UIBarButtonItem) {
goToEditTask(createTask())
}
private func goToEditTask(_ task: Task) {
let controller = storyboard?.instantiateViewController(identifier: TaskDetailsViewController.identifier) as! TaskDetailsViewController
controller.task = task
controller.delegate = self
navigationController?.pushViewController(controller, animated: true)
}
// MARK:- Methods to implement
private func createTask() -> Task {
let task = CoreDataManager.shared.createTask()
// Update table
allTasks.insert(task, at: 0)
tableView.insertRows(at: [IndexPath(row: 0, section: 0)], with: .automatic)
return task
}
private func fetchTasksFromStorage() {
allTasks = CoreDataManager.shared.fetchTasks()
}
private func deleteTaskFromStorage(_ task: Task) {
deleteTaskSviped(with: task.id)
CoreDataManager.shared.deleteTask(task)
}
}
//UITableViewDelegate, UITableViewDataSource
extension TasksViewController: UITableViewDelegate,UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if doneTasksCheck == true {
if currentSearch == "" {
filteredTasks = doneTasks.sorted { $0.taskDate > $1.taskDate }
}
} else {
if currentSearch == "" {
filteredTasks = currentTask.sorted { $0.taskDate < $1.taskDate }
}
}
return filteredTasks.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: idTasksCell, for: indexPath) as! TaskTableViewCell
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd.MM.YYYY"
cell.taskLabel.text = filteredTasks[indexPath.row].task
cell.taskDateLabel.text = dateFormatter.string(from: filteredTasks[indexPath.row].taskDate)
if filteredTasks[indexPath.row].taskDate < Date().onlyDate! {
cell.taskDateLabel.textColor = .red
} else {
cell.taskDateLabel.textColor = .black
}
cell.taskTegLabel.text = filteredTasks[indexPath.row].taskTag
if filteredTasks[indexPath.row].taskReminder != nil {
cell.taskRemindTimeLabel.isHidden = false
dateFormatter.dateFormat = "HH:mm"
cell.taskRemindTimeLabel.text = dateFormatter.string(from: filteredTasks[indexPath.row].taskReminder!)
} else {
cell.taskRemindTimeLabel.isHidden = true
}
if filteredTasks[indexPath.row].taskNote == nil || filteredTasks[indexPath.row].taskNote == "" {
cell.taskNoteLabel.isHidden = true
} else {
cell.taskNoteLabel.isHidden = false
}
if filteredTasks[indexPath.row].taskIsDone == false {
cell.taskDoneButton.setBackgroundImage(UIImage(systemName: "circle"), for: .normal)
} else {
cell.taskDoneButton.setBackgroundImage(UIImage(systemName: "chevron.down.circle.fill"), for: .normal)
}
cell.layer.masksToBounds = true
cell.layer.cornerRadius = 15
cell.layer.borderWidth = 2
cell.layer.borderColor = UIColor.white.cgColor
cell.selectionStyle = .none
cell.cellTaskDelegate = self
cell.index = indexPath
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 75
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
goToEditTask(filteredTasks[indexPath.row])
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
deleteTaskFromStorage(filteredTasks[indexPath.row])
}
}
}
extension TasksViewController {
func setConstraints() {
view.addSubview(tableView)
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0),
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0)
])
}
}
// MARK:- TaskNotes Delegate
extension TasksViewController: TaskNotesDelegate {
func refreshTasks() {
allTasks = allTasks.sorted { $0.taskDate < $1.taskDate }
currentTask = currentTask.sorted { $0.taskDate < $1.taskDate }
tableView.reloadData()
}
func deleteTaskSviped(with id: UUID) {
let indexPath = indexForTask(id: id, in: filteredTasks)
filteredTasks.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .automatic)
// just so that it doesn't come back when we search from the array
allTasks.remove(at: indexForTask(id: id, in: allTasks).row)
}
}

TableView cells becomes inactive

I am using a tableView to take some surveys.
Header I use for a question. Footer for «back» and «next» buttons. And tableView cells for answer options.
Now I started to have a problem, with some user interaction: when you simultaneously click on the “next” button and select an answer, the answer options cease to be active, nothing can be selected. Although the buttons remain active.
Tell me in what direction to look for the problem and how you can debug this problem in order to understand what's wrong.
It all started after fixing bugs, when the application crashed when simultaneously (or almost) pressing the "next" button and choosing an answer. Because the didSelectRowAt method worked after I changed the current array of answer options, and the selected index in the previous question turned out to be larger than the size of the array with the answers to the new question.
class AssessmentVC: UIViewController {
#IBOutlet weak var tableView: UITableView!
var footer: FooterTableView?
var header: UIView?
var arrayAssessmnet = [AssessmentDM]()
var assessment: AssessmentDM!
var question: QuestionDM!
var viewSeperationHeader = UIView()
var arrayOptions: [Option]?
var countAssessment = 0
var numberAssessment = 0
var numberQuestion = 0
var countQuestion = 0
var numberQusttionForLabel = 1
var arrayQuestion = [QuestionDM]()
var arrayAnswers = [AnswerDM]()
var arrayEvents = [EventDM]()
override func viewDidLoad() {
super.viewDidLoad()
settingAssessment()
}
//MARK: - settingAssessment()
private func settingAssessment() {
let id = self.assessment.serverId
arrayQuestion = QuestionDM.getQuestions(id: id)
assessmentName.text = assessment.name
countQuestion = arrayQuestion.count
let day = self.assessment.day
arrayAnswers = AnswerDM.getAnswers(idAssessment: id, day: day)
settingQuestion(eventType: .start)
}
//MARK: - settingQuestion()
private func settingQuestion(eventType: EventType? = nil) {
let prevQuestion = question
question = arrayQuestion[numberQuestion]
timeQuestion = 0
footer!.grayNextButton()
//first question
if numberQuestion == 0 && numberAssessment == 0 {
footer!.previousButton.isHidden = true
} else {
footer!.previousButton.isHidden = false
}
arrayOptions = [Option]()
let sortOption = question.options!.sorted {$0.numberOption < $1.numberOption}
for option in sortOption {
arrayOptions?.append(Option(label: option.label, value: option.value))
}
tableView.rowHeight = UITableView.automaticDimension
tableView.reloadData()
heightTableView()
tableView.setContentOffset(.zero, animated: false)
}
//MARK: - heightTableView()
func heightTableView() {
}
//MARK: - UITableViewDataSource
extension AssessmentVC: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
viewSeperationHeader.isHidden = false
footer?.viewSeperationFooter.isHidden = false
tableView.separatorStyle = .singleLine
return question.options?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(forIndexPath: indexPath as IndexPath) as AnswerAssessmentCell
cell.initCell(text: arrayOptions![indexPath.row].label, value: arrayOptions![indexPath.row].value, arrayValue: arrayAnswers[numberQuestion].response, isCheckbox: true)
return cell
}
}
//MARK: - UITableViewDelegate
extension AssessmentVC: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
isChangAnswerInAssessment = true
if question.answerType == "Radio" || question.answerType == "Checkbox"{
selectRadioOrChekbox(indexPath: indexPath)
}
}
}
//MARK: - selectRadioOrChekbox
extension AssessmentVC {
private func selectRadioOrChekbox(indexPath: IndexPath) {
if question.answerType == "Radio" {
let cells = tableView.visibleCells as! Array<AnswerAssessmentCell>
for cell in cells {
cell.select = false
cell.isSelected = false
}
let cell = tableView.cellForRow(at: indexPath) as! AnswerAssessmentCell
cell.select = true
cell.isSelected = true
if arrayOptions?.count ?? 0 > indexPath.row {
arrayAnswers[numberQuestion].response = arrayOptions![indexPath.row].value
footer?.greenNextButton()
}
}
if question.answerType == "Checkbox" {
if arrayOptions?.count ?? 0 > indexPath.row {
//если нажато что-то, что должно сбросить "None"
// question.options![0].isSelect = false
let cells = tableView.visibleCells as! Array<AnswerAssessmentCell>
if cells[0].answerLabel.text == "None" {
cells[0].select = false
cells[0].isSelected = false
}
var array = arrayAnswers[numberQuestion].response?.components(separatedBy: ";")
array?.removeAll { $0 == "0"}
if array?.count == 0 {
arrayAnswers[numberQuestion].response = nil
} else {
arrayAnswers[numberQuestion].response = array?.joined(separator: ";")
}
let cell = tableView.cellForRow(at: indexPath) as! AnswerAssessmentCell
cell.select = !cell.select
cell.isSelected = cell.select
arrayAnswers[numberQuestion].response = array.joined(separator: ";")
if array.count == 0 {
arrayAnswers[numberQuestion].response = nil
footer?.grayNextButton()
} else {
footer?.greenNextButton()
}
}
}
}
}
//MARK: - Navigation between questions
extension AssessmentVC {
func nextQuestion() {
footer!.grayNextButton()
numberQuestion += 1
numberQusttionForLabel += 1
settingQuestion(eventType: .next)
} else {
}
func previousQuestion() {
numberQusttionForLabel -= 1
settingQuestion(eventType: .previous)
}
}
Some snippets that can help you :
// Answer type : use enum . Here the Strong/Codable is if you want to
// save using JSON encoding/decoding
enum AnswerType: String, Codable {
case checkBox = "CheckBox"
case radio = "Radio"
}
Setup of your cell :
class AnswerAssessmentCell: UITableViewCell {
...
// work with Option type
func initCell(option: Option, response: String?, answerType: AnswerType) {
// setup cell contents (labels)
// check for selected status
switch answerType {
case .checkBox:
// check if option is in response
// set isSelected according
break
case .radio:
// check if option is response
// set isSelected according
break
}
}
}
In table view data source :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! AnswerAssessmentCell
// Use the option to init the cell
// this will also set the selected state
let optionNumber = indexPath.row
cell.initCell(option: arrayOptions![optionNumber], response: arrayAnswers[numberQuestion].response, answerType: question.answerType)
return cell
}
In Table view delegate :
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
isChangAnswerInAssessment = true
let optionNumber = indexPath.row
switch question.answerType {
case .radio:
selectRadio(optionNumber: optionNumber)
case .checkBox:
selectCheckBox(optionNumber: optionNumber)
}
// Reload tableview to show changes
tableView.reloadData()
}
// Separate in 2 function for smaller functions
// in this function work only with model data, the reload data will do
// cell update
// only the footer view button cooler may need to be changed
private func selectRadio(optionNumber: Int) {
// Reset current response
// set response to optionNumber
// update footer button cooler if necessary
}
private func selectCheckBox(optionNumber: Int) {
// if option is in response
// remove option from response
// else
// add response to option
// update footer button cooler if necessary
}
Hope this can help you

Why 'edit actions' are not appear ,but (_:editActionsForRowAt) is called

When I am swiping left, edit actions do not appear. But! All delegete's functions called (canEditRowAt and editActionsForRowAt) Why I can not swipe?
import UIKit
class HikingCustomLocationViewController: UIViewController {
//MARK: - Constants
private let DEFAULT_HEADER_FOOTER_VIEW_ID = "DefaultHeaderFooterViewId"
private let CUSTOM_LOCATION_CELL_ID = "CustomLocationCellId"
// MARK: - UI properties
private lazy var customLocationTableView: UITableView = {
let tableView = UITableView()
tableView.backgroundColor = ColorHelper.bckgDefaultBlue
tableView.separatorStyle = .singleLine
tableView.separatorColor = ColorHelper.bckgDefaultBlue
tableView.separatorInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
tableView.indicatorStyle = .white
tableView.backgroundColor = ColorHelper.bckgDefaultBlue
tableView.isHidden = true
// Delagates
tableView.delegate = self
tableView.dataSource = self
// Registration
let defaultHeaderFooterViewNib = UINib(nibName: "DefaultTableViewHeaderFooterView", bundle: nil)
tableView.register(defaultHeaderFooterViewNib, forHeaderFooterViewReuseIdentifier: DEFAULT_HEADER_FOOTER_VIEW_ID)
let customLocationViewCellNib = UINib(nibName: "PlacesTableViewCell", bundle: nil)
tableView.register(customLocationViewCellNib, forCellReuseIdentifier: CUSTOM_LOCATION_CELL_ID)
return tableView
}()
private lazy var userMessageLabel: UILabel = {
let label = UILabel()
label.font = FontHelper.body
label.textAlignment = .center
label.numberOfLines = 0
label.textColor = ColorHelper.bckgTextTextWhite
label.text = NSLocalizedString("PLACESNOTHINGADDED", comment: "")
label.isHidden = true
return label
}()
// MARK: - Dependencies
private lazy var messageBus = MessageBus.sharedInstance
private lazy var globalState = GlobalState.sharedInstance
// MARK: - Props
enum HikingCustomLocationProps {
case loading
case loaded(customLocationGroups: [(tourId: String, customLocations: [CustomLocation])])
}
private var props: HikingCustomLocationProps = .loading {
didSet {
view.setNeedsLayout()
}
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
switch props {
case .loading:
customLocationTableView.isHidden = true
userMessageLabel.isHidden = true
case .loaded(let customLocationGroups):
if customLocationGroups.isEmpty {
customLocationTableView.isHidden = true
userMessageLabel.isHidden = false
} else {
customLocationTableView.isHidden = false
userMessageLabel.isHidden = true
}
customLocationTableView.reloadData()
}
}
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
title = NSLocalizedString("PLACESTITLE", comment: "")
view.backgroundColor = ColorHelper.bckgDefaultBlue
setupMessageBusDelegates()
subscribeMessages()
view.addSubview(customLocationTableView)
view.addSubview(userMessageLabel)
setupConstraints()
if case .product(let appStoreId) = globalState.state {
messageBus.send(msg: GetCustomLocationsAction(appStoreId: appStoreId))
}
}
deinit {
unsubscribeMessages()
}
// MARK: - MessageBus
private var customLocationsReadyMessage: MessageBusDelegate<CustomLocationsReadyMessage>?
private func setupMessageBusDelegates() {
customLocationsReadyMessage = MessageBusDelegate<CustomLocationsReadyMessage>(closure: { [weak self] msg in
if case .product(let appStoreId) = self?.globalState.state {
if msg.appStoreId != appStoreId { return }
self?.props = .loaded(customLocationGroups: msg.customLocationsGroup)
}
if case .tour(let appStoreId, _, _) = self?.globalState.state {
if msg.appStoreId != appStoreId { return }
self?.props = .loaded(customLocationGroups: msg.customLocationsGroup)
}
})
}
private func subscribeMessages() {
messageBus.subscribe(closure: customLocationsReadyMessage)
}
private func unsubscribeMessages() {
messageBus.unsubscribe(closure: customLocationsReadyMessage)
}
// MARK: - Constraints
private func setupConstraints() {
customLocationTableView.translatesAutoresizingMaskIntoConstraints = false
customLocationTableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
customLocationTableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
customLocationTableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
customLocationTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
userMessageLabel.translatesAutoresizingMaskIntoConstraints = false
userMessageLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
userMessageLabel.widthAnchor.constraint(equalToConstant: 288).isActive = true
userMessageLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 16).isActive = true
}
}
// MARK: - UITableViewDelegate
extension HikingCustomLocationViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if case .loaded(let customLocationGroups) = props {
guard
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: DEFAULT_HEADER_FOOTER_VIEW_ID) as? DefaultTableViewHeaderFooterView,
let group = customLocationGroups[safe: section]
else { return nil }
headerView.configure(title: ("\(NSLocalizedString("TOUR", comment: "")) \(group.tourId)"), description: nil)
headerView.contentView.backgroundColor = ColorHelper.bckgTableHeaderDarkBlue
return headerView
}
return nil
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
if case .product(let appStoreId) = globalState.state {
if case .loaded(let customLocationGroups) = props {
guard
let group = customLocationGroups[safe: indexPath.section],
let cLocation = group.customLocations[safe: indexPath.row],
let tourId = Int(group.tourId)
else { return }
globalState.state = .tour(
appStoreId: appStoreId,
tourId: tourId,
mapSelectionState: .customLocation(id: cLocation.id))
let hikingTourContainerVC = HikingTourContainerViewController(
exitButtonTitile: NSLocalizedString("PLACESTITLE", comment: ""))
self.navigationController?.pushViewController(hikingTourContainerVC, animated: true)
}
}
}
func numberOfSections(in tableView: UITableView) -> Int {
if case .loaded(let customLocationGroups) = props {
return customLocationGroups.count
}
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if case .loaded(let customLocationGroups) = props {
guard let customLocations = customLocationGroups[safe: section] else { return 0 }
return customLocations.customLocations.count
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if case .loaded(let customLocationGroups) = props {
guard
let customLocations = customLocationGroups[safe: indexPath.section]?.customLocations,
let cell = tableView.dequeueReusableCell(withIdentifier: CUSTOM_LOCATION_CELL_ID) as? PlacesTableViewCell,
let cLocation = customLocations[safe: indexPath.row]
else { return UITableViewCell() }
cell.configure(noteText: cLocation.note, date: cLocation.date)
setSelectedBackgroundViewFor(cell)
return cell
}
return UITableViewCell()
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let deleteAction = UITableViewRowAction(style: .destructive, title: NSLocalizedString("DELETE", comment: ""), handler: { (rowAction, indexPath) in
print("Delete action")
})
return [deleteAction]
}
private func setSelectedBackgroundViewFor(_ cell: UITableViewCell) {
let selectedBackgroundView = UIView()
selectedBackgroundView.backgroundColor = ColorHelper.bckg3DefalutBlue
cell.selectedBackgroundView = selectedBackgroundView
}
}
The problem was in calling customLocationTableView.reloadData() inside viewWillLayoutSubviews(). After moving it out, (In my case, I move it to props' didSet) edit actions start to appear.

display different custom table view cells based on if a int is > 0

I have a table in my sql database that shows artists, song ,and albums. Each of these has a id. I also have 3 custom cells.
If the id is over 0, i'd like for that song, artist or albums to show in the table view. I am getting this data with arrays.
Whenever this code runs I get a Thread 1: Fatal error: Index out of range crash. It might have something to do with the logic of my if statement. Any suggestions would be appreciated.
var searchActive: Bool = false
var search = [Search]()
var songs = [Songs]()
var artists = [Artist]()
var album = [Album]()
var cleanSong = ""
var artistName = ""
var albumName = ""
var songCover = UIImage()
var artistPic = UIImage()
var albumCover = UIImage()
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(SongTableViewCell.nib(), forCellReuseIdentifier: SongTableViewCell.songCell)
tableView.register(ArtistTableViewCell.nib(), forCellReuseIdentifier: ArtistTableViewCell.artistCell)
tableView.register(AlbumTableViewCell.nib(), forCellReuseIdentifier: AlbumTableViewCell.AlbumCell)
tableView.delegate = self
tableView.dataSource = self
searchesBar.delegate = self
print(search)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if (search[indexPath.row].songid > 0) { //CRASH: Thread 1: Fatal error: Index out of range
let cell = tableView.dequeueReusableCell(withIdentifier: "SongTableViewCell", for: indexPath) as! SongTableViewCell
cell.mainLabel!.text = songs[indexPath.row].cleanName
cell.secondLabel!.text = songs[indexPath.row].artistName
cell.cellImage!.image = UIImage(named: songs[indexPath.row].cover)
return cell
} else if (search[indexPath.row].artistid > 0) {
let cell = tableView.dequeueReusableCell(withIdentifier: "ArtistTableViewCell", for: indexPath) as! ArtistTableViewCell
cell.artistLabel.text = artists[indexPath.row].artistName
cell.artiistImage.image = UIImage(named: artists[indexPath.row].picture)
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "AlbumTableViewCell", for: indexPath) as! AlbumTableViewCell
cell.albumLabel!.text = search[indexPath.row].cleanSong
cell.albumCover.image = UIImage(named: album[indexPath.row].cover)
return cell
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (searchActive) {
return search.count
} else {
return 1
}
}
it because you have condition in numberOfRowsInSection, you need to check the same condition in cellForRowAt
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if searchActive {
if (search[indexPath.row].songid > 0) {
let cell = tableView.dequeueReusableCell(withIdentifier: "SongTableViewCell", for: indexPath) as! SongTableViewCell
cell.mainLabel!.text = songs[indexPath.row].cleanName
cell.secondLabel!.text = songs[indexPath.row].artistName
cell.cellImage!.image = UIImage(named: songs[indexPath.row].cover)
return cell
} else if (search[indexPath.row].artistid > 0) {
let cell = tableView.dequeueReusableCell(withIdentifier: "ArtistTableViewCell", for: indexPath) as! ArtistTableViewCell
cell.artistLabel.text = artists[indexPath.row].artistName
cell.artiistImage.image = UIImage(named: artists[indexPath.row].picture)
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "AlbumTableViewCell", for: indexPath) as! AlbumTableViewCell
cell.albumLabel!.text = search[indexPath.row].cleanSong
cell.albumCover.image = UIImage(named: album[indexPath.row].cover)
return cell
}
} else {
// return cell for searchActive == false, I don't know what you want to show, so I return empty UITableViewCell
return UITableViewCell()
}
}
I personally don't like the way you are dealing with the current scenario. We can rather build this through a better design pattern instead of using Int and having three different UITableViewcell class. I would prefer to do that through Bride Pattern. I tried to do this with below code:
//Resource protocol is used to display for cell used in table view cell
//we can use model also in place of different functions to return cell data
protocol ResourceProtocol {
func snippet()
func title()
func image()
func url()
}
class ArtistResource: ResourceProtocol {
func snippet() {
//return snippet
}
func title() {
//return title
}
func image() {
//return image
}
func url() {
//return url
}
}
class SongResource: ResourceProtocol {
func snippet() {
//return snippet
}
func title() {
//return title
}
func image() {
//return image
}
func url() {
//return url
}
}
class AlbumResource: ResourceProtocol {
func snippet() {
//return snippet
}
func title() {
//return title
}
func image() {
//return image
}
func url() {
//return url
}
}
protocol IViewProtocol {
var resource: ResourceProtocol { get set }
func show()
}
class TableViewCellView: IViewProtocol {
var resource: ResourceProtocol
init(source: ResourceProtocol) {
resource = source
}
func show() {
//show snippet
//show title
//show url
//show image
}
}
class ListView {
var dataSource: [ResourceProtocol]?
func fetchData() {
//return data source
}
//show data in cell from UITableViewDataSource functions
}
class DetailView: IViewProtocol {
var resource: ResourceProtocol
init(source: ResourceProtocol) {
resource = source
}
func show() {
//show detail
//show title
//show url
//show image
}
}
You can customize this code, i have just given a prototype, how it should be implemented.

check / uncheck the check box by tapping the cell in table view and how to know which cell has checked or unchecked

i am new to ios swift 2.2 . I tried to create one table view with custom cell xib. and i am populating some data in my table view. And also i added one custom check box button. And i am creating separate class for that check box button. Now when i click only on my button in my customcell.xib .But when i tap on my cell, my check box are not changing. i need to have both. When i click on my button it should change to check and uncheck image. When i tap on my cell also i need to change my button to check or uncheck image
And when i scroll down and again come back to top, my checked images are automatically chnaged to normalcheck box.
i need to do some action , so for that. When i tap on any cell my check box should check and uncheck.And alos i need to know which row cell has checked image . So that i can perform some action for my checked image row cell alone.
here is my custom check box class:
import UIKit
class CheckBoxButton: UIButton {
// Images
let checkedImage = UIImage(named: "CheckBoxChecked")! as UIImage
let uncheckedImage = UIImage(named: "CheckBoxUnChecked")! as UIImage
// Bool property
var isChecked: Bool = false {
didSet{
if isChecked == true {
self.setImage(checkedImage, forState: .Normal)
} else {
self.setImage(uncheckedImage, forState: .Normal)
}
}
}
override func awakeFromNib() {
self.addTarget(self, action: #selector(CheckBoxButton.buttonClicked(_:)), forControlEvents: UIControlEvents.TouchUpInside)
self.isChecked = false
}
func buttonClicked(sender: UIButton) {
if sender == self {
if isChecked == true {
isChecked = false
} else {
isChecked = true
}
}
}
}
Cutom cell.xib class:
import UIKit
class FavCell: UITableViewCell {
#IBOutlet weak var FLabel1: UILabel!
#IBOutlet weak var FLabel2: UILabel!
#IBOutlet weak var checkbox: CheckBoxButton!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
#IBAction func checkboxpress(sender: AnyObject) {
}
}
my viewcontroller.swift
import UIKit
class FavVC: UIViewController {
#IBOutlet weak var FavTableView: UITableView!
//var FData = [FavouritesData]()
var arrDict :NSMutableArray=[]
let cellSpacingHeight: CGFloat = 5 // cell spacing from each cell in table view
override func viewDidLoad() {
super.viewDidLoad()
self.jsonParsingFromURL()
let nib = UINib(nibName:"FavCell", bundle: nil)
FavTableView.registerNib(nib, forCellReuseIdentifier: "FCell")
}
// web services method
func jsonParsingFromURL ()
{
// let token = NSUserDefaults.standardUserDefaults().valueForKey("access_token") as? String
let url = NSURL(string: "som url")
let session = NSURLSession.sharedSession()
let request = NSURLRequest(URL: url!)
let dataTask = session.dataTaskWithRequest(request) { (data:NSData?, response:NSURLResponse?, error:NSError?) -> Void in
// print("done, error: \(error)")
if error == nil
{
dispatch_async(dispatch_get_main_queue())
{
self.arrDict=(try! NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)) as! NSMutableArray
if (self.arrDict.count>0)
{
self.FavTableView.reloadData()
}
}
}
}
dataTask.resume()
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int
{
// return self.FData.count
return self.arrDict.count
}
// number of rows
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return 1
}
// height for each cell
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat
{
return cellSpacingHeight
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell:FavCell = self.FavTableView.dequeueReusableCellWithIdentifier("FCell") as! FavCell
cell.FLabel1.text=arrDict[indexPath.section] .valueForKey("favourite_name") as? String
cell.FLabel2.text=arrDict[indexPath.section] .valueForKey("favourite_address") as? String
return cell
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
please help me out.
Thanks in advance
In order to solve your issue, as #El Capitan mentioned, you will need to use the didSelectRowAtIndexPath method to change its states. Your codes should look something along the lines of this:
// Declare a variable which stores checked rows. UITableViewCell gets dequeued and restored as you scroll up and down, so it is best to store a reference of rows which has been checked
var rowsWhichAreChecked = [NSIndexPath]()
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell:FavCell = tableView.cellForRowAtIndexPath(indexPath) as! FavCell
// cross checking for checked rows
if(rowsWhichAreChecked.contains(indexPath) == false){
cell.checkBox.isChecked = true
rowsWhichAreChecked.append(indexPath)
}else{
cell.checkBox.isChecked = false
// remove the indexPath from rowsWhichAreCheckedArray
if let checkedItemIndex = rowsWhichAreChecked.indexOf(indexPath){
rowsWhichAreChecked.removeAtIndex(checkedItemIndex)
}
}
}
To redisplay cells which have been checked before after scrolling the rows out of view, at your cellForRowAtIndexPath, perform the same checking against rowsWhichAreChecked array and set its states accordingly.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:FavCell = self.FavTableView.dequeueReusableCellWithIdentifier("FCell") as! FavCell
cell.FLabel1.text=arrDict[indexPath.section] .valueForKey("favourite_name") as? String
cell.FLabel2.text=arrDict[indexPath.section] .valueForKey("favourite_address") as? String
if(rowsWhichAreChecked.contains(indexPath) == false){
cell.checkBox.isChecked = true
}else{
cell.checkBox.isChecked = false
}
}
return cell
}
EDITED ANSWER
I have got your code to work but I had to make some modifications to your Checkbox class and ViewController
Checkbox.swift
class CheckBoxButton: UIButton {
// Images
let checkedImage = UIImage(named: "CheckBoxChecked")! as UIImage
let uncheckedImage = UIImage(named: "CheckBoxUnChecked")! as UIImage
// Bool property
var isChecked: Bool = false {
didSet{
if isChecked == true {
self.setImage(uncheckedImage, forState: .Normal)
} else {
self.setImage(checkedImage, forState: .Normal)
}
}
}
override func awakeFromNib() {
self.userInteractionEnabled = false
// self.addTarget(self, action: #selector(CheckBoxButton.buttonClicked(_:)), forControlEvents: UIControlEvents.TouchUpInside)
// self.isChecked = false
}
func buttonClicked(sender: UIButton) {
if sender == self {
if isChecked == true {
isChecked = false
} else {
isChecked = true
}
}
}
}
ViewController.swift
class FavVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var FavTableView: UITableView!
var rowsWhichAreChecked = [NSIndexPath]()
//var FData = [FavouritesData]()
var arrDict :NSMutableArray=[]
let cellSpacingHeight: CGFloat = 5 // cell spacing from each cell in table view
override func viewDidLoad() {
self.FavTableView.delegate = self
self.FavTableView.dataSource = self
super.viewDidLoad()
self.jsonParsingFromURL()
let nib = UINib(nibName:"FavCell", bundle: nil)
FavTableView.registerNib(nib, forCellReuseIdentifier: "FCell")
}
// web services method
func jsonParsingFromURL ()
{
// let token = NSUserDefaults.standardUserDefaults().valueForKey("access_token") as? String
let url = NSURL(string: "some url")
let session = NSURLSession.sharedSession()
let request = NSURLRequest(URL: url!)
let dataTask = session.dataTaskWithRequest(request) { (data:NSData?, response:NSURLResponse?, error:NSError?) -> Void in
// print("done, error: \(error)")
if error == nil
{
dispatch_async(dispatch_get_main_queue())
{
self.arrDict=(try! NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)) as! NSMutableArray
if (self.arrDict.count>0)
{
self.FavTableView.reloadData()
}
}
}
}
dataTask.resume()
//
// let StringUrl = "http"+token!
// let url:NSURL = NSURL(string: StringUrl)!
// if let JSONData = NSData(contentsOfURL: url)
// {
// if let json = (try? NSJSONSerialization.JSONObjectWithData(JSONData, options: [])) as? NSDictionary
// {
// for values in json
// {
// self.FData.append()
// }
// if let reposArray = json["data"] as? [NSDictionary]
// {
//
// for item in reposArray
// {
// let itemObj = item as? Dictionary<String,AnyObject>
//
// let b_type = itemObj!["business_type"]?.valueForKey("type")
//
// //self.Resultcount.text = "\(b_type?.count) Results"
//
// if (b_type as? String == "Taxis")
// {
//
// self.FData.append(FavouritesData(json:item))
//
// }
// }
// }
// }
// }
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int
{
// return self.FData.count
return self.arrDict.count
}
// number of rows
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return 1
}
// height for each cell
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat
{
return cellSpacingHeight
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell:FavCell = self.FavTableView.dequeueReusableCellWithIdentifier("FCell") as! FavCell
cell.FLabel1.text=arrDict[indexPath.section] .valueForKey("favourite_name") as? String
cell.FLabel2.text=arrDict[indexPath.section] .valueForKey("favourite_address") as? String
let isRowChecked = rowsWhichAreChecked.contains(indexPath)
if(isRowChecked == true)
{
cell.checkbox.isChecked = true
cell.checkbox.buttonClicked(cell.checkbox)
}else{
cell.checkbox.isChecked = false
cell.checkbox.buttonClicked(cell.checkbox)
}
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell:FavCell = tableView.cellForRowAtIndexPath(indexPath) as! FavCell
// cross checking for checked rows
if(rowsWhichAreChecked.contains(indexPath) == false){
cell.checkbox.isChecked = true
cell.checkbox.buttonClicked(cell.checkbox)
rowsWhichAreChecked.append(indexPath)
}else{
cell.checkbox.isChecked = false
cell.checkbox.buttonClicked(cell.checkbox)
// remove the indexPath from rowsWhichAreCheckedArray
if let checkedItemIndex = rowsWhichAreChecked.indexOf(indexPath){
rowsWhichAreChecked.removeAtIndex(checkedItemIndex)
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
{
let cell:FilterTableViewCell = tableView.dequeueReusableCell(withIdentifier: "filtercell", for: indexPath) as! FilterTableViewCell
// Configure the cell...
cell.lblCategory?.attributedText = FontAttributes.sharedInstance.AttributesString(message: self.filterArray[indexPath.row], color: Textcolor)
cell.BtnIndex?.addTarget(self, action: #selector(checkMarkTapped(_ :)), for: .touchUpInside)
cell.BtnIndex?.tag = indexPath.row
let rowid = indexPath.row
let found = rowsWhichAreChecked.filter{$0.rowId == rowid}.count > 0
if found
{
cell.BtnIndex?.setImage(checkedImage, for: .normal)
}
else
{
cell.BtnIndex?.setImage(uncheckedImage, for: .normal)
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell:FilterTableViewCell = tableView.cellForRow(at: indexPath) as! FilterTableViewCell
let rowid = indexPath.row
let found = rowsWhichAreChecked.filter{$0.rowId == rowid}.count > 0
if found
{
tempArrayFordelete = rowsWhichAreChecked
for obj in tempArrayFordelete
{
if let index = rowsWhichAreChecked.index(where: { $0.rowId == obj.rowId }) {
// removing item
rowsWhichAreChecked.remove(at: index)
cell.BtnIndex?.setImage(uncheckedImage, for: .normal)
}
}
}
else
{
cell.BtnIndex?.setImage(checkedImage, for: .normal)
let objrowId = selectedIndex(rowId: indexPath.row)
rowsWhichAreChecked.append(objrowId)
}
}
It gives me a great pleasure to inform you all that solve above issue
Resolving Issue Is
CheckBox Functionality
RadioButton Functionality
ReuseCell(tableView.dequeueReusableCell)//Also solve selected cell position issue.
Tested Code
Swift 5
iOS 12.2
Here is my code
import UIKit
class countrySelection:UITableViewCell{
#IBOutlet weak var imgFlag: UIImageView!
#IBOutlet weak var lblCountryName: UILabel!
#IBOutlet weak var btnSelection: UIButton!
}
class ViewController: UIViewController {
var listingDict=[[String:String]]()
var radioOption:Int?// Only used :: if u are 2. RadioButton Functionality implement
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource=self
tableView.delegate=self
fillCountryData()
// Do any additional setup after loading the view.
}
func fillCountryData(){
self.fillJsonData(imgName: "india_flag", countryName: "India")
self.fillJsonData(imgName: "pakistan_flag", countryName: "Pakistan")
self.fillJsonData(imgName: "israel_flag", countryName: "Israel")
self.fillJsonData(imgName: "albania_flag", countryName: "Albania")
self.fillJsonData(imgName: "america_flag", countryName: "America")
self.fillJsonData(imgName: "belize_flag", countryName: "Belize")
self.fillJsonData(imgName: "brunei_flag", countryName: "Brunei")
self.fillJsonData(imgName: "comoros_flag", countryName: "Comoros")
self.fillJsonData(imgName: "congo_flag", countryName: "Congo")
self.fillJsonData(imgName: "ecuador_flag", countryName: "Ecuador")
self.fillJsonData(imgName: "haiti_flag", countryName: "Haiti")
self.fillJsonData(imgName: "jamaica_flag", countryName: "Jamaica")
self.fillJsonData(imgName: "kenya_flag", countryName: "Kenya")
self.fillJsonData(imgName: "mali_flag", countryName: "Mali")
self.tableView.reloadData()
}
func fillJsonData(imgName:String,countryName:String){
var dictData=[String:String]()
dictData["img"]=imgName
dictData["country"]=countryName
dictData["check"]="false"
listingDict.append(dictData)
}
}
extension ViewController:UITableViewDataSource,UITableViewDelegate{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return listingDict.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell=tableView.dequeueReusableCell(withIdentifier: "countrySelection") as! countrySelection
let dictVal=listingDict[indexPath.row]
cell.lblCountryName.text=dictVal["country"]
cell.imgFlag.image=UIImage(named:dictVal["img"]!)
/*//Check Box Functionality
if dictVal["check"] == "false"{
cell.btnSelection.setImage(UIImage(named: "checkbox_UnSelect"), for: .normal)
} else{
cell.btnSelection.setImage(UIImage(named: "checkbox_Select"), for: .normal)
}*/
//RadioButton Functionality
if radioOption==indexPath.row{
listingDict[indexPath.row]["check"]="true"
cell.btnSelection.setImage(UIImage(named: "radioButton_Select"), for: .normal)
} else{
listingDict[indexPath.row]["check"]="false"
cell.btnSelection.setImage(UIImage(named: "radioButton_UnSelect"), for: .normal)
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
/*//CheckBox Functionality
if listingDict[indexPath.row]["check"]=="true"{
listingDict[indexPath.row]["check"]="false"
} else{
listingDict[indexPath.row]["check"]="true"
}*/
//RadioButton Functionality
print("RadioButton",listingDict)
if listingDict[indexPath.row]["check"]=="true"{
radioOption=nil
} else{
radioOption=indexPath.row
}
self.tableView.reloadData()
}
}

Resources