Switch between tableViews using segmented Control - ios

I have a tableView showing multiple tasks and i would like to programmatically switch between 2 dataSources. I have created a segmented control that appear at the top but when i click on the buttons there is no change and i don't know how to link my segmented Control to my dataSources, here's my code:
class MyTasksCollectionCell: UICollectionViewCell, UITableViewDelegate, UITableViewDataSource {
var tasks = [Add]()
var pastTasks = [Add]()
static let identifier = "MyTasksCollectionCell"
private let cellID = "CellID"
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.tasks.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellID, for: indexPath) as! MyTasksTableCell
cell.accessoryType = .disclosureIndicator
cell.categoryLabel.text =
"\(tasks[indexPath.row].category)"
cell.dateLabel.text =
"\(tasks[indexPath.row].date)"
cell.hourLabel.text =
"\(tasks[indexPath.row].hour)"
if cell.categoryLabel.text == "Urgent" {
cell.categoryIcon.image = #imageLiteral(resourceName: "red.png")
}
if cell.categoryLabel.text == "Important" {
cell.categoryIcon.image = #imageLiteral(resourceName: "orange.png")
}
if cell.categoryLabel.text == "Not Important" {
cell.categoryIcon.image = #imageLiteral(resourceName: "green.png")
}
cell.dateIcon.image = UIImage(systemName: "calendar.badge.clock")
return cell
}
func addControl() {
let segmentItems = ["Present Tasks", "Past Tasks"]
let control = UISegmentedControl(items: segmentItems)
control.frame = CGRect(x: 10, y: 0, width: (self.tableView.frame.width - 20), height: 30)
control.addTarget(self, action: #selector(segmentControl(_:)), for: .valueChanged)
control.selectedSegmentIndex = 0
tableView.addSubview(control)
}
#objc func segmentControl(_ segmentedControl: UISegmentedControl) {
switch (segmentedControl.selectedSegmentIndex) {
case 0:
// First segment tapped
print("Present Tasks")
self.tableView.reloadData()
break
case 1:
// Second segment tapped
print("Past Tasks")
self.tableView.reloadData()
break
default:
break
}
}
}

Use an enum to know what to display in your tab depending on segment control value :
enum DispkayedTasks {
case current
case past
}
var displayedTask = DisplayedTasks.current
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch (displayedTask) {
case .current:
// First segment tapped
return self.tasks.count
case .past:
// Second segment tapped
return self.pastTasks.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellID, for: indexPath) as! MyTasksTableCell
cell.accessoryType = .disclosureIndicator
let task = {() -> Add in
switch (displayedTask) {
case .current:
// First segment tapped
return self.tasks[indexPath.row]
case past:
// Second segment tapped
return self.pastTasks[indexPath.row]
}
}()
cell.categoryLabel.text =
"\(task.category)"
cell.dateLabel.text =
"\(task.date)"
cell.hourLabel.text =
"\(task.hour)"
if cell.categoryLabel.text == "Urgent" {
cell.categoryIcon.image = #imageLiteral(resourceName: "red.png")
}
if cell.categoryLabel.text == "Important" {
cell.categoryIcon.image = #imageLiteral(resourceName: "orange.png")
}
if cell.categoryLabel.text == "Not Important" {
cell.categoryIcon.image = #imageLiteral(resourceName: "green.png")
}
cell.dateIcon.image = UIImage(systemName: "calendar.badge.clock")
return cell
}
#objc func segmentControl(_ segmentedControl: UISegmentedControl) {
switch (segmentedControl.selectedSegmentIndex) {
case 0:
// First segment tapped
print("Present Tasks")
displayedTasks = .current
self.tableView.reloadData()
case 1:
// Second segment tapped
print("Past Tasks")
displayedTasks = .past
self.tableView.reloadData()
default:
break
}
}

Related

Cell touch action is not responding on short touches in TableView

I have a Scene in storyboard that contains HistoryTable with historyCell and own UserHistoryViewCell view. This cell and also table is set to User interaction Enabled in Traits and Interaction sections. Scrolling is disabled and Delay Touch Down is also disabled. This cell is not responding at didSelectRowAt func at short click (only when pressing longer than 5? secs). I have a Tap Gesture Recognizer in my Scene and also another table with same problem. I tried to disable Tap Gesture Recognizer, but it seems that it doesn't do anything special. I'm not sure where is problem, I have my tables delegated and using the code bellow. I also provide screenshots of Attributes inspector for the table and cell. Btw. My second table has sections, so its little bit messy.
Second Btw. I have delay touch down enabled at the screenshot, because it has no effect at all. Third Btw. I have historyButton in my historyTable which is for removing old records. It has no impact at the cell itself (I think) because same stuff happens to my second table without this button. Also it worked before I added sections to my second table.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var counter = 0
if tableView.tag == 1 {
counter = (UserDefaults.standard.stringArray(forKey: "HistoryArray") ?? [String]()).count
let base = 60
self.historyTableHeightConstraint.constant = CGFloat(base) + CGFloat(counter) * 40
} else {
switch section {
case 0:
counter = presenter.storelist?.count ?? 0
case 1:
counter = presenter.storelist?.count ?? 0
default:
counter = 0
notFoundLabel.isHidden = false
}
}
return counter
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView.tag == 1 {
let defaults = UserDefaults.standard
let userSearchHistory = defaults.stringArray(forKey: "HistoryArray") ?? [String]()
let cell = tableView.dequeueReusableCell(withIdentifier: "historyCell", for: indexPath) as! UserHistoryTableViewCell
cell.titleLabel.text = userSearchHistory.reversed()[indexPath.row]
cell.historyButton.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
cell.historyButton.tag = indexPath.row
if self.editable == true {
let newImage = UIImage(named: "RemoveIcon")
cell.historyButton.setImage(newImage, for: .normal)
cell.historyButton.isEnabled = true
} else {
let newImage = UIImage(named: "searchHistoryIcon")
cell.historyButton.setImage(newImage, for: .normal)
cell.historyButton.isEnabled = false
}
return cell
} else {
switch indexPath.section {
case 0:
let cell = tableView.dequeueReusableCell(withIdentifier: "shopSearchCell", for: indexPath) as! ShopSearchTableViewCell
cell.shopLabel.text = presenter.storelist?[indexPath.row].storename
return cell
case 1:
let cell = tableView.dequeueReusableCell(withIdentifier: "SearchResultCell", for: indexPath) as! SearchResultCell
cell.SearchResultLabel.text = presenter.storelist?[indexPath.row].storename
return cell
default:
return UITableViewCell()
}
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print(#function + "\(indexPath.row)")
}
func tableView(_ tableView: UITableView, titleForHeaderInSection
section: Int) -> String? {
if tableView.tag == 2 {
if section == 0 {
if self.tableView(tableView, numberOfRowsInSection: section) > 0 {
return "Search shop"
} else {
return nil
}
} else {
if self.tableView(tableView, numberOfRowsInSection: section) > 0 {
return "Search"
} else {
return nil
}
}
} else {
return nil
}
}
// Sectionæ•°
func numberOfSections(in tableView: UITableView) -> Int {
if tableView.tag == 2 {
return 2
}
return 1
}

How to show a parent view which is outside tableview and is scrollable?

I have a scenario where I need to show a parent view with shadow and corner radius containing a long list of reusable items. I used a tableView to display items. But I stuck at making my tableview expand as much as its contentSize. It works but not accurate. Any solutions?
Edit:
Desired result:
I used the following reference for self sizing tableview.
Self Sizing UITableView
I made a few modifications as below:
final class SelfSizedTableView: UITableView {
var maxHeight = CGFloat.greatestFiniteMagnitude
override func reloadData() {
super.reloadData()
self.invalidateIntrinsicContentSize()
self.layoutIfNeeded()
}
override var intrinsicContentSize: CGSize {
let height = min(contentSize.height, maxHeight)
let size = CGSize(width: contentSize.width, height: height)
return size
}
}
I used a parent tableView with a cell having my containerView and embedding this self sized tableView.
class MyContainerViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
// MARK: - IBOutlets
#IBOutlet weak var parentTableView: UITableView!
// MARK: - Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
}
private func estimateDataHeight() -> CGFloat {
let detailCellHeight: CGFloat = 32
let headingCellHeight: CGFloat = 43
let headings: CGFloat = headingCellHeight*2
let detailsHeight: CGFloat = detailCellHeight*4
let baseHeight = headings + detailsHeight
let membersHeight =
CGFloat(sectionsArray.count) * detailCellHeight
return baseHeight + membersHeight
}
}
// MARK: - UITableViewDataSource
extension MyContainerViewController {
func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let id = String(describing: MyContainerTVCell.self)
guard let cell = tableView
.dequeueReusableCell(withIdentifier: id, for: indexPath)
as? MyContainerTVCell else {
return UITableViewCell()
}
cell.policyDetails = dataSource
// my cheat/trick doesn't work on large data.
DispatchQueue.main.asyncAfter(deadline: .now()+0.4) {
tableView.beginUpdates()
cell.tableView.layoutIfNeeded()
cell.tableView.reloadData() // the overridden one
tableView.endUpdates()
}
return cell
}
}
extension MyContainerViewController {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return estimateDataHeight()
}
}
My cell class which has the self size tableView and containerView:
class MyContainerTVCell: UITableViewCell, UITableViewDataSource, UITableViewDelegate {
// MARK: - IBOutlets
#IBOutlet weak var containerView: UIView!
#IBOutlet weak var shadowView: UIView!
#IBOutlet weak var tableView: SelfSizedTableView!
// MARK: - Properties
let titles = ["Email ID:", "Mobile Number:", "Address:", "ID: "] // first section data array
let moreData: [String] = [] // remaining reusable sections array
// no of subsequent sections for moreData array type
var numberOfSections: Int {
return 4
}
// MARK: -
var dataSource: MyDataSource!
// MARK: - Life Cycle
override func awakeFromNib() {
super.awakeFromNib()
setupView()
}
override func layoutSubviews() {
super.layoutSubviews()
}
// MARK: - Setup
func setupView() {
containerView.rounded(with: 10)
shadowView.layer.applyShadow()
tableView.dataSource = self
tableView.delegate = self
}
}
// MARK: - UITableViewDataSource
extension MyContainerTVCell {
func numberOfSections(in tableView: UITableView) -> Int {
return numberOfSections + 1
}
func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
if section == 0 { return titles.count + 1 }
else if section == 1 { return moreData.count + 1 }
else { return moreData.count }
}
func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let headerID = String(describing: MyHeaderTVCell.self)
let itemID = String(describing: MyItemTVCell.self)
switch indexPath.section {
case 0:
if indexPath.row == 0 {
guard let cell = tableView
.dequeueReusableCell(withIdentifier: headerID, for: indexPath)
as? MyHeaderTVCell else {
return UITableViewCell()
}
cell.titleLabel.text = dataSource.title
return cell
} else {
guard let cell = tableView
.dequeueReusableCell(withIdentifier: itemID, for: indexPath)
as? MyItemTVCell else {
return UITableViewCell()
}
let item = titles[indexPath.row-1]
cell.titleLabel.text = item
cell.separatorView.isHidden = true
let data: String
switch indexPath.row {
case 1:
data = dataSource.emailID
case 2:
data = dataSource.mobileNo
case 3:
data = dataSource.address
case 4:
data = dataSource.name
case 5:
data = dataSource.age
case 6:
data = dataSource.id
case 7:
data = dataSource.office
case 8:
data = dataSource.academic
default: data = String()
}
cell.detailLabel.text = data
return cell
}
case 1:
if indexPath.row == 0 {
guard let cell = tableView
.dequeueReusableCell(withIdentifier: headerID, for: indexPath)
as? MyHeaderTVCell else {
return UITableViewCell()
}
cell.titleLabel.text = "More Data"
return cell
} else {
guard let cell = tableView
.dequeueReusableCell(withIdentifier: itemID, for: indexPath)
as? MyItemTVCell else {
return UITableViewCell()
}
let sectionIndex = indexPath.section-1
guard sectionIndex <= numberOfSections-1,
let section = sectionsArray?[indexPath.section-1] else {
return UITableViewCell()
}
cell.titleLabel.text = moreData[indexPath.row-1]
cell.separatorView.isHidden = true
switch indexPath.row {
case 1:
cell.detailLabel.text = section.a
case 2:
cell.detailLabel.text = section.b
case 3:
cell.detailLabel.text = "\(section.c ?? 0)"
case 4:
cell.detailLabel.text = section.d
case 5:
cell.detailLabel.text = section.e
case 6:
cell.detailLabel.text = section.f
if indexPath.section < numberOfSections {
cell.separatorView.isHidden = false
}
default: break
}
return cell
}
default:
guard let cell = tableView
.dequeueReusableCell(withIdentifier: itemID, for: indexPath)
as? MyItemTVCell else {
return UITableViewCell()
}
let sectionIndex = indexPath.section-1
guard sectionIndex <= numberOfSections-1,
let section = sectionsArray?[indexPath.section-1] else {
return UITableViewCell()
}
cell.titleLabel.text = moreData[indexPath.row]
cell.separatorView.isHidden = true
switch indexPath.row {
case 0:
cell.detailLabel.text = section.a
case 1:
cell.detailLabel.text = section.b
case 2:
cell.detailLabel.text = "\(section.c ?? 0)"
case 3:
cell.detailLabel.text = section.d
case 4:
cell.detailLabel.text = section.e
case 5:
cell.detailLabel.text = section.f
if indexPath.section < numberOfSections {
cell.separatorView.isHidden = false
}
default: break
}
return cell
}
}
}
// MARK: - UITableViewDelegate
extension MyContainerTVCell {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.section == 0 && indexPath.row == 0 { return 43 }
if indexPath.section == 1 && indexPath.row == 0 { return 43 }
return 32
}
}
Why would you want to expand tableView as much as its content size to make it scrollable, when tableView is already scrollable?
However, if you have some other content, aside from table, on the screen and you want them to scroll together, then you need to embed all your content into UIScrollView.
Then, make a height constraint for you tableView in xib/storyboard with any value.
Then you might do something like this:
// in your view controller
private var heightObservation: NSKeyValueObservation?
// called once, for example, in viewDidLoad()
private func setupTableView() {
...
observation = tableView.constraintFrameHeightToContentSizeHeight()
}
extension UITableView {
func constraintFrameHeightToContentSizeHeight() -> NSKeyValueObservation {
return observe(\.contentSize, changeHandler: { (tableView, _) in
tableView.heightConstraint?.constant = tableView.contentSize.height
})
}
}
// find height constraint
extension UIView {
var heightConstraint: NSLayoutConstraint? {
return constraints.first(where: { $0.firstAttribute == .height })
}
}
Don't forget to uncheck "Scrolling Enabled" in xib/storyboard for that table view.

How to navigate to viewController from tableview cell, which is placed in other tableview Cell?

I have placed a tableview2 in tableview1 Cell, Now when I click on the tableview2 cell I need to navigate to a new viewController. Please help me... I was struggling with one whole day :(
here is the code, the second table view is placed in SegmentedCell...
when i am trying to push, its unable to go next controller..
import UIKit
import XMSegmentedControl
import Alamofire
import SwiftyJSON
class segmentedCell: UITableViewCell, XMSegmentedControlDelegate, UITableViewDelegate, UITableViewDataSource{
let byndrColor : UIColor = UIColor( red: 224/255, green: 0/255, blue: 115/255, alpha: 1.0 )
let fontStyle = UIFont(name: "Lato-bold", size: 12)
#IBOutlet weak var segmentedControl: XMSegmentedControl!
#IBOutlet weak var feedTableView: UITableView!
var getApi = UIApplication.shared.delegate as! AppDelegate
var course_id = String()
var materialListObjects = [MaterialsInSingleCourseGetSet]()
var assignmentExamAndQuizListObjects = [AssignmentAndExamsQuizGetSet]()
override func awakeFromNib() {
super.awakeFromNib()
feedTableView.delegate = self
feedTableView.dataSource = self
segmentedControl.delegate = self
segmentedControl.segmentTitle = ["LATEST", "MATERIALS", "COURSEWORK", "PROGRESS"]
segmentedControl.font = fontStyle!
segmentedControl.selectedItemHighlightStyle = XMSelectedItemHighlightStyle.BottomEdge
segmentedControl.backgroundColor = UIColor.white
segmentedControl.tint = UIColor.black
segmentedControl.highlightTint = byndrColor
segmentedControl.highlightColor = byndrColor
segmentedControl.edgeHighlightHeight = 2
segmentedControl.selectedSegment = 0
let share = UIApplication.shared.delegate as! AppDelegate
materialListObjects = share.materialListInSingleCourse as! [MaterialsInSingleCourseGetSet]
assignmentExamAndQuizListObjects = share.assignmentsExamsAndQuizListInSingleCourse as! [AssignmentAndExamsQuizGetSet]
// Initialization code
}
func xmSegmentedControl(xmSegmentedControl: XMSegmentedControl, selectedSegment: Int) {
if xmSegmentedControl == segmentedControl {
print("SegmentedControl1 Selected Segment: \(selectedSegment)")
switch segmentedControl.selectedSegment
{
case 0:
feedTableView.reloadData()
case 1:
feedTableView.reloadData()
case 2:
feedTableView.reloadData()
case 3:
feedTableView.reloadData()
default :
break
}
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if segmentedControl.selectedSegment == 0
{
return 0
}
else
if segmentedControl.selectedSegment == 1
{
return materialListObjects.count
}
else
if segmentedControl.selectedSegment == 2
{
return assignmentExamAndQuizListObjects.count
}
else
{
return 0
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if segmentedControl.selectedSegment == 0
{
let cell = Bundle.main.loadNibNamed("TypeOneCell", owner: self, options: nil)?.first as! TypeOneCell
return cell
}
else
if segmentedControl.selectedSegment == 1
{
if materialListObjects[indexPath.row].type == "file"
{
let cell = Bundle.main.loadNibNamed("materialCellOne", owner: self, options: nil)?.first as! materialCellOne
cell.materialNameLabel.text = materialListObjects[indexPath.row].title
let image = materialListObjects[indexPath.row].title
cell.contentImage.image = image.documentType(givenType: image)
return cell
}else
{
let cell = Bundle.main.loadNibNamed("materialCellTwo", owner: self, options: nil)?.first as! materialCellTwo
cell.materialNameLabel.text = materialListObjects[indexPath.row].title
cell.contentImage.image = #imageLiteral(resourceName: "material_hyperlink")
return cell
}
}
else
if segmentedControl.selectedSegment == 2
{
let cell = Bundle.main.loadNibNamed("CourseWorkCell", owner: self, options: nil)?.first as! CourseWorkCell
print("assignment title : \(assignmentExamAndQuizListObjects[indexPath.row].title)")
cell.titleLabel.text = assignmentExamAndQuizListObjects[indexPath.row].title
if assignmentExamAndQuizListObjects[indexPath.row].type == ""
{
cell.contentImage.image = #imageLiteral(resourceName: "assignment_large")
}else
{
cell.contentImage.image = #imageLiteral(resourceName: "exam_inline")
}
var time = assignmentExamAndQuizListObjects[indexPath.row].start
time = time.dateRange(dateString: time)
time = time.days(givenDate: time)
cell.timeLabel.text = time
return cell
}
else
if segmentedControl.selectedSegment == 3
{
let cell = Bundle.main.loadNibNamed("TypeOneCell", owner: self, options: nil)?.first as! TypeOneCell
return cell
}
else
{
let cell = Bundle.main.loadNibNamed("TypeOneCell", owner: self, options: nil)?.first as! TypeOneCell
return cell
}
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if segmentedControl.selectedSegment == 2
{
return 70
}
else
{
return 100
}
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let view = UIView(frame: CGRect.zero)
let label = UILabel(frame: CGRect(x: 8, y: 8, width: 150, height: 20))
view.addSubview(label)
label.font = UIFont(name: "Lato-Heavy", size: 17)
if segmentedControl.selectedSegment == 1
{
switch section {
case 0:
label.text = "All Materials"
case 1:
label.text = "From Your Courses"
default:
break
}
}
else
if segmentedControl.selectedSegment == 2
{
switch section {
case 0:
label.text = "All CourseWork"
case 1:
label.text = "From Your Courses"
default:
break
}
}
else
{
}
return view
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 50
}
//How to perform from here
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if segmentedControl.selectedSegment == 1
{
let storyboard = UIStoryboard(name: "Main", bundle : nil)
let nextViewController = storyboard.instantiateViewController(withIdentifier: "QuickLook") as! QuickLook
if materialListObjects[indexPath.row].type == "url"
{
nextViewController.id = materialListObjects[indexPath.row].body
nextViewController.type = "url"
}
else
{
nextViewController.id = materialListObjects[indexPath.row].id
}
nextViewController.course_id = String(describing: materialListObjects[indexPath.row].course_id)
let naviControl = UINavigationController(rootViewController: nextViewController)
naviControl.pushViewController(nextViewController, animated: true)
}
}
}
I have created a similar scenario as yours and this is how you can get it working.
1. View Hierarchy
I have used tag property to uniquely identify both UITableViews, i.e.
Outer tableView tag = 0
Inner tableView tag = 1
2. Now implement UITableViewDataSource, UITableViewDelegate methods for both the tableViews. Set the dataSource and delegate of both the tableViews as the ViewController.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
if tableView.tag == 0
{
return 1
}
else if tableView.tag == 1
{
return 5
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
if tableView.tag == 0
{
return tableView.dequeueReusableCell(withIdentifier: "outercell", for: indexPath)
}
else if tableView.tag == 1
{
return tableView.dequeueReusableCell(withIdentifier: "innercell", for: indexPath)
}
return UITableViewCell()
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
if tableView.tag == 1
{
//TODO: Write your code for navigating to another ViewController here
print("Inner cell tapped")
}
}
Edit:
In the Interface Builder, you can find a tag attribute corresponding to each element in the attributes inspector, i.e.
For outer tableView set it to 0 and for inner tableView set it to 1.
Let me know if you still face any issues. Happy Coding..🙂
Use performSegue(withIdentifier: "ViewController", sender: self); in didSelectrow method of you tableView2.

Reload tableView data from UIAlertController not working

I'm having trouble refreshing tableView data from a UIAlertController.
The code is for a quiz-style app and this page lets the user choose
subjects as well as some other options (see screenshots). There is a
reset button next to "Show only unseen questions" which triggers a
UIAlertController. However, clicking the Reset action in this alert
updates the database but doesn't update the tableView. The database is
definitely updated as if I go back a page and then revisit the
tableView, the unseen question values in the subject cells are updated. I realise there's quite a few of
these type of questions here but I'm afraid none of the usual fixes are
working.
Extra info:
The tableView is customised with a series of custom
UITableViewCells
Data is loaded from a SQLite database through FMDB
The UIAlertController is triggered from a NSNotification when the reset
button is clicked
So far I have:
Checked datasource and delegates set correctly, programmatically and
in IB. Confirmed with print(self.tableView.datasource) etc
Confirmed reloadData() is firing
Using main thread for reloadData()
Extract of TableViewController code and screenshots below.
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.dataSource = self
self.tableView.delegate = self
//For unique question picker changed
NotificationCenter.default.addObserver(self, selector: #selector(SubjectsTableViewController.reloadView(_:)), name:NSNotification.Name(rawValue: "reload"), object: nil)
//For slider value changed
NotificationCenter.default.addObserver(self, selector: #selector(SubjectsTableViewController.updateQuantity(_:)), name:NSNotification.Name(rawValue: "updateQuantity"), object: nil)
//Trigger UIAlertController
NotificationCenter.default.addObserver(self, selector: #selector(SubjectsTableViewController.showAlert(_:)), name:NSNotification.Name(rawValue: "showAlert"), object: nil)
}
// MARK: - Table view data source
///////// Sections and Headers /////////
override func numberOfSections(in tableView: UITableView) -> Int {
return 3
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?
{
let subjectHeaderCell = tableView.dequeueReusableCell(withIdentifier: "sectionHeader")
switch section {
case 0:
subjectHeaderCell?.textLabel?.text = "Select Subjects"
return subjectHeaderCell
case 1:
subjectHeaderCell?.textLabel?.text = "Options"
return subjectHeaderCell
case 2:
subjectHeaderCell?.textLabel?.text = ""
return subjectHeaderCell
default:
subjectHeaderCell?.textLabel?.text = ""
return subjectHeaderCell
}
}
//Header heights
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat
{
return 34.0
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 0:
return SubjectManager.subjectWorker.countSubjects()
case 1:
return 2
case 2:
return 1
default:
return 0
}
}
///////// Rows within sections /////////
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch (indexPath.section) {
case 0:
//Configure subjectCell //
let cellWithSubject = tableView.dequeueReusableCell(withIdentifier: "subjectCell", for: indexPath) as! SubjectTableViewCell
//Curve corners
cellWithSubject.subjectCellContainer.layer.cornerRadius = 2
cellWithSubject.subjectCellContainer.layer.masksToBounds = true
//Set subject title label
cellWithSubject.subjectTitleLabel.text = SubjectManager.subjectWorker.collateSubjectTitles()[indexPath.row]
//Available questions for subject label
questionCountForSubjectArray = QuestionManager.questionWorker.countQuestions()
cellWithSubject.subjectAvailableQuestionsLabel.text = "Total questions available: \(questionCountForSubjectArray[indexPath.row])"
//Get questions in subject variables
seenQuestionsForSubjectArray = QuestionManager.questionWorker.countOfQuestionsAlreadySeen()
//New questions available label
unseenQuestionsForSubjectArray.append(questionCountForSubjectArray[indexPath.row] - seenQuestionsForSubjectArray[indexPath.row])
cellWithSubject.newQuestionsRemainingLabel.text = "New questions remaining: \(unseenQuestionsForSubjectArray[indexPath.row])"
return cellWithSubject
case 1:
switch (indexPath.row) {
case 0:
//Configure uniqueQuestionCell //
let cellWithSwitch = tableView.dequeueReusableCell(withIdentifier: "uniqueQuestionCell", for: indexPath) as! UniqueQuestionTableViewCell
//Curve corners
cellWithSwitch.uniqueQuestionContainer.layer.cornerRadius = 2
cellWithSwitch.uniqueQuestionContainer.layer.masksToBounds = true
return cellWithSwitch
case 1:
//Configure sliderCell //
let cellWithSlider = tableView.dequeueReusableCell(withIdentifier: "questionPickerCell", for: indexPath) as! QuestionPickerTableViewCell
//Curve corners
cellWithSlider.pickerCellContainer.layer.cornerRadius = 2
cellWithSlider.pickerCellContainer.layer.masksToBounds = true
//Set questions available label
cellWithSlider.questionsAvailableLabel.text = "Available: \(sumQuestionsSelected)"
//Configure slider
cellWithSlider.questionPicker.maximumValue = Float(sumQuestionsSelected)
cellWithSlider.questionPicker.isContinuous = true
//Logic for if available questions changes - updates slider stuff
if questionQuantityFromSlider > sumQuestionsSelected {
questionQuantityFromSlider = sumQuestionsSelected
cellWithSlider.questionsToStudy = questionQuantityFromSlider
cellWithSlider.questionsChosenLabel.text = "Questions to study: \(questionQuantityFromSlider)"
} else { questionQuantityFromSlider = cellWithSlider.questionsToStudy
}
//Configure questions chosen label:
if questionsToStudyDict.isEmpty {
cellWithSlider.chooseSubjectsLabel.text = "Choose a subject"
cellWithSlider.questionsChosenLabel.text = "Questions to study: 0"
} else {
cellWithSlider.chooseSubjectsLabel.text = ""
}
return cellWithSlider
default:
return UITableViewCell()
}
case 2:
print("cellForRowAt case 2")
//Configure beginCell //
let cellWithStart = tableView.dequeueReusableCell(withIdentifier: "beginCell", for: indexPath) as! BeginStudyTableViewCell
//Curve corners
cellWithStart.startContainer.layer.cornerRadius = 2
cellWithStart.startContainer.layer.masksToBounds = true
return cellWithStart
default:
return UITableViewCell()
}
}
//Row heights
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
switch (indexPath.section) {
case 0:
return 120.0
case 1:
switch (indexPath.row) {
case 0:
return 60.0
case 1:
return 100.0
default:
return 44.0
}
case 2:
return 100.0
default:
return 44.0
}
}
override func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
if indexPath.section == 2 || indexPath.section == 0 {
return true
} else {
return false
}
}
override func tableView(_ tableView: UITableView, didHighlightRowAt indexPath: IndexPath) {
if indexPath.section == 2 && selectedRowsDict.isEmpty != true && questionQuantityFromSlider > 0 {
let cellToBegin = tableView.cellForRow(at: indexPath) as! BeginStudyTableViewCell
cellToBegin.startContainer.backgroundColor = UIColor.lightGray
}
}
override func tableView(_ tableView: UITableView, didUnhighlightRowAt indexPath: IndexPath) {
if indexPath.section == 2 {
let cellToBegin = tableView.cellForRow(at: indexPath) as! BeginStudyTableViewCell
cellToBegin.startContainer.backgroundColor = UIColor.white
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
switch (indexPath.section) {
case 0:
//Set checkbox to ticked image
let cellWithSubject = tableView.cellForRow(at: indexPath) as! SubjectTableViewCell
cellWithSubject.subjectSelectedImageView.image = UIImage(named: "CheckboxTicked")
//Determine questions available for subject depending on unseen value
if showUnseenQuestions == true {
questionsToStudyDict[indexPath.row] = unseenQuestionsForSubjectArray[indexPath.row]
} else {
questionsToStudyDict[indexPath.row] = questionCountForSubjectArray[indexPath.row]
}
//Sum questions available
sumQuestionsSelected = Array(questionsToStudyDict.values).reduce(0, +)
//Reload table to pass this to questions available label in UISlider cell and reselect selected rows
let key: Int = indexPath.row
selectedRowsDict[key] = indexPath.row
self.tableView.reloadData()
if selectedRowsDict.isEmpty == false {
for (keys,_) in selectedRowsDict {
let index: IndexPath = NSIndexPath(row: selectedRowsDict[keys]!, section: 0) as IndexPath
tableView.selectRow(at: index, animated: false, scrollPosition: .none)
}
}
case 1:
break
case 2:
if selectedRowsDict.isEmpty != true && questionQuantityFromSlider > 0 {
self.performSegue(withIdentifier: "showStudyQuestion", sender: self)
} else {
print("Segue not fired")
}
default:
break
}
}
override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
if indexPath.section == 0 {
//Set checkbox to unticked image
let cellWithSubject = tableView.cellForRow(at: indexPath) as! SubjectTableViewCell
cellWithSubject.subjectSelectedImageView.image = UIImage(named: "Checkbox")
//Remove questions available for unselected subject from questions dictionary
questionsToStudyDict[indexPath.row] = nil
//Update sum of questions selected
sumQuestionsSelected = Array(questionsToStudyDict.values).reduce(0, +)
//Reload table to pass this to questions available label in UISlider cell and reselect selected rows
let key: Int = indexPath.row
selectedRowsDict[key] = nil
self.tableView.reloadData()
if selectedRowsDict.isEmpty == false {
for (keys,_) in selectedRowsDict {
let index: IndexPath = NSIndexPath(row: selectedRowsDict[keys]!, section: 0) as IndexPath
tableView.selectRow(at: index, animated: false, scrollPosition: .none)
}
}
}
}
func reloadView(_ notification: Notification) {
//Change bool value
showUnseenQuestions = !showUnseenQuestions
//For keys in dict, update values according to showUnseenQuestion value
if showUnseenQuestions == true {
for (key,_) in questionsToStudyDict {
questionsToStudyDict[key] = unseenQuestionsForSubjectArray[key]
}
} else {
for (key,_) in questionsToStudyDict {
questionsToStudyDict[key] = questionCountForSubjectArray[key]
}
}
//Re-run sum dict function
sumQuestionsSelected = Array(questionsToStudyDict.values).reduce(0, +)
//Finally reload the view and reselect selected rows
let selectedRowsIndexes = tableView.indexPathsForSelectedRows
self.tableView.reloadData()
if selectedRowsIndexes != nil {
for i in (selectedRowsIndexes)! {
tableView.selectRow(at: i, animated: false, scrollPosition: .none)
}
}
}
func updateQuantity(_ notification: Notification) {
//Reload the view and reselect selected rows
let selectedRowsIndexes = tableView.indexPathsForSelectedRows
self.tableView.reloadData()
if selectedRowsIndexes != nil {
for i in (selectedRowsIndexes)! {
tableView.selectRow(at: i, animated: false, scrollPosition: .none)
}
}
}
func showAlert(_ notification: Notification) {
let alertController = UIAlertController(title: "Reset Seen Questions", message: "Are you sure you want to reset all questions to unseen?", preferredStyle: .alert)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { action in
// ...
}
alertController.addAction(cancelAction)
let OKAction = UIAlertAction(title: "Reset", style: .default, handler:{(action:UIAlertAction) -> Void in
QuestionManager.questionWorker.resetHasSeenValues()
self.reloadData()
print("reloadData fired")
})
alertController.addAction(OKAction)
self.present(alertController, animated: true, completion: nil)
}
func reloadData() {
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}
func countOfQuestionsAlreadySeen() -> [Int] {
var questionSeenYesArray: [Int] = []
if openWriteDatabase() {
let queryYes = "SELECT SUM(hasSeen) FROM UserData GROUP BY subjectID"
let querySeenYes: FMResultSet? = writeDatabase?.executeQuery(queryYes, withArgumentsIn: nil)
while (querySeenYes?.next())! {
if let questionSeenYes = (querySeenYes?.int(forColumnIndex: 0)) {
questionSeenYesArray.append(Int(questionSeenYes))
}
}
}
return questionSeenYesArray
}
func resetHasSeenValues() {
if openWriteDatabase() {
let resetHasSeenValues = "UPDATE UserData Set hasSeen = 0"
_ = writeDatabase?.executeUpdate(resetHasSeenValues, withArgumentsIn: nil)
}
}
Some more debugging revealed that the unseenQuestionsForSubjectArray wasn't being populated correctly in the cellForRowAt method. I fixed this, and this fixed the reloadData() issue. Thanks all for the help.
add self.reloadData() inside main queue. Outlet changes always should be held inside main thread.
let OKAction = UIAlertAction(title: "Reset", style: .default, handler:{(action:UIAlertAction) -> Void in
QuestionManager.questionWorker.resetHasSeenValues()
DispatchQueue.main.async {
self.tableView.reloadData()
}
print("reloadData fired")
})

Prototype cells not displaying correctly

My tableView has two prototype cells which are programmed in the storyboard as below.
The identifiers are all correct another isn't a mistake between the IB and the storyboard as the identifiers link up. But when I build and run the project I get presented with the following screen (Note that on this run, semester.subjects and semester.activities are empty).
Also notice that when I click my first cell, nothing happens but when I click my second cell it changes to what I wanted it to be. My question is How does this happen? Also below is my cellForRowAt indexPath if you need it.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "subjectCell", for: indexPath) as! SubjectTableViewCell
let addCell = tableView.dequeueReusableCell(withIdentifier: "addCell", for: indexPath) as! AddSubjectTableViewCell
var cellToAdd = UITableViewCell()
switch indexPath.section {
case 0:
if indexPath.row < semester.subjects.count{
let subject = semester.subjects[indexPath.row]
cell.subjectColourView.backgroundColor = subject.colour
cell.subjectLabel.text = subject.name
cell.teacherLabel.text = subject.teacher
cellToAdd = cell
}
else if indexPath.row == semester.subjects.count {
cellToAdd = addCell
}
case 1:
if indexPath.row < semester.activities.count{
let activity = semester.activities[indexPath.row]
cell.subjectColourView.backgroundColor = activity.colour
cell.subjectLabel.text = activity.name
cell.teacherLabel.text = activity.teacher
cellToAdd = cell
}
else if indexPath.row == semester.activities.count {
cellToAdd = addCell
}
default:
break
}
print(cellToAdd.reuseIdentifier)
return cellToAdd
}
Here is the entire TVC
import UIKit
class SubjectTableViewController: UITableViewController {
var semester: Semester!
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 0:
return semester.subjects.count + 1
case 1:
return semester.activities.count + 1
default:
return 0
}
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
switch section {
case 0:
return "Subjects"
case 1:
return "Extracurricular Activities"
default:
return nil
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "subjectCell", for: indexPath) as! SubjectTableViewCell
let addCell = tableView.dequeueReusableCell(withIdentifier: "addCell", for: indexPath) as! AddSubjectTableViewCell
var cellToAdd = UITableViewCell()
switch indexPath.section {
case 0:
if indexPath.row < semester.subjects.count{
let subject = semester.subjects[indexPath.row]
cell.subjectColourView.backgroundColor = subject.colour
cell.subjectLabel.text = subject.name
cell.teacherLabel.text = subject.teacher
cellToAdd = cell
}
else if indexPath.row == semester.subjects.count {
cellToAdd = addCell
}
case 1:
if indexPath.row < semester.activities.count{
let activity = semester.activities[indexPath.row]
cell.subjectColourView.backgroundColor = activity.colour
cell.subjectLabel.text = activity.name
cell.teacherLabel.text = activity.teacher
cellToAdd = cell
}
else if indexPath.row == semester.activities.count {
cellToAdd = addCell
}
default:
break
}
print(cellToAdd.reuseIdentifier)
return cellToAdd
}
}
The problem, for who knows what reason, is that the cell was denied outside of the switch.
if indexPath.row < semester.subjects.count{
let cell = tableView.dequeueReusableCell(withIdentifier: "subjectCell", for: indexPath) as! SubjectTableViewCell
let subject = semester.subjects[indexPath.row]
cell.subjectColourView.backgroundColor = subject.colour
cell.subjectLabel.text = subject.name
cell.teacherLabel.text = subject.teacher
cellToAdd = cell
}

Resources