Embedded UITableView not reloading with reloadData() on UIButtonClick in Swift 4 - ios

I am trying to get my table view to update when I add a new cell to my SQLite Table. However, as of now, when I add a new cell to the SQLite Table and call reloadData, nothing updates or happens. My UITableView is embedded into my Main View Controller so that might be part of the issue. I also feel as if this may be an issue with the tableView.dataSource or something. I am not sure. I am very new to swift.
I have tried calling the reloadData from the UITableViewController file and from the UIViewController which contains the button that is supposed to add data and then update the Table. Neither have worked. I also tried to substitute it with tableView?.beginUpdates() and tableView?.endUpdates()
My button code (part of button script):
#IBAction func addTask(_ sender: Any) {
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mmZZZZZ"
let taskDateFormatted = dateFormatter.string(from: taskDate.date)
let newTask = Task(title: taskTitle.text!, description: descriptionTextView.text,
date: taskDateFormatted)
let instSQLiteData = SQLiteData()
let instTaskTableVC = TaskTableVC()
instSQLiteData.uploadData(newTask.title, newTask.description, newTask.date)
instTaskTableVC.taskTable?.reloadData()
dismiss(animated: true, completion: nil)
}
My UITableViewController script/class (part of tableview script):
class TaskTableVC : UITableViewController {
#IBOutlet var taskTable: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.taskTable.dataSource = self
self.taskTable.delegate = self
}
How my UITableViewController loads data (part of tableview script):
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let instSQLiteData = SQLiteData()
return instSQLiteData.getRowCount()
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let instSQLiteData = SQLiteData()
let cell = tableView.dequeueReusableCell(withIdentifier: "taskCell", for: indexPath)
cell.textLabel?.text = instSQLiteData.getTasksArray()[indexPath.row]
return cell
}
Once the button is pressed I just want the table to update but I cannot get it to work even though I have tried to follow a ton of other questions with the same topic. Much thanks!

Related

Call reloadData() to refresh TableView - Reusable Code

I need the tableView to perform reloadData() after a row has been added via a textView. My tableViews all use the reusable code which works fine.
Below is my Reusable TableViewCode.
class ReusableSubtitleTable: NSObject, UITableViewDataSource, UITableViewDelegate{
let cell = "cell"
var dataArray = [String]()
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) {
print("DataArray count from table view = \(dataArray.count)")
return dataArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let selfSizingCell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! SelfSizingCell
let num = indexPath.row
selfSizingCell.titleText.text = (stepText[num])
selfSizingCell.subtitleText.text = dataArray[num]
return selfSizingCell
}
}
The function below uses the reusable code to display the table which works fine.
class DetailViewController: UIViewController {
let step = 13
var tableView: UITableView!
let dataSource = ReusableSubtitleTable()
var selectedEntry: JournalEntry!
var dataModel = [String]()
var didSave = false
var coreDataManager = CoreDataManager()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = dataSource
tableView.dataSource = dataSource
dataSource.dataArray = dataModel
#IBAction func unwindToDetail( _ sender: UIStoryboardSegue) {
dataModel[10] = step11
didSave = coreDataManager.updateEntry(step11: step11, selectedEntry: selectedEntry)
}
}
The problem come in when a user wants to add to the last row. The user taps a button and is taken to the next controller which is a TextView. When user finishes their entry they tap the 'Save' button which returned to the DetailViewController via an unwind. The selectedEntry is saved and the dataModel updated. Now the table view needs to reload to display this added text.
I've tried adding tableView.ReloadData() after didSave. I've tried a Dispatch and tried saving the data before returning from the textView via the unwind but that doesn't work either.
I tried adding the below function to ReusableTableView and called it after the coredata update - there are no errors but it does not reload the table.
func doReload(){
tableView.reloadData()
}
I have verified that the data is saved and it does displays if I return to the summary controller and then go forward the DetailViewController.
Any help is appreciated.
Placing the UITableView reloadData() within either viewWillAppear or viewDidAppear should resolve this issue.
For example:
func viewDidAppear(_ animated: bool) {
super.viewDidAppear(animated)
tableView.reloadData()
}
This is because the view hierarchy isn't yet regarded as "visible" during the segue unwind and why you see it work by going back to reloading the view controller. The reloadData() function, for efficiency, only redisplays visible cells and at the time of the unwind the cells aren't "visible".
Apple Documentation - UITableView.reloadData():
For efficiency, the table view redisplays only those rows that are
visible.

UITableView.reloadData() is not refreshing tableView. No error message

I've already looked at the post UITableView.reloadData() is not working. I'm not sure that it applies to my situation, but let me know if I'm wrong.
My app has a tableView. From the main viewController I am opening another viewController, creating a new object, and then passing that object back to the original viewController, where it is added to an array called timers. All of that is working fine. However, when I call tableView.reloadData() in didUnwindFromNewTimerVC() to display the updated contents of the timers array, nothing happens.
NOTE: I have verified that the timers array is updated with the new object. Its count increments, and I can access its members. Everything else in didUnwindFromNewTimerVC() executes normally. The tableView just isn't updating to reflect it.
Here is my code:
import UIKit
class TimerListScreen: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tabelView: UITableView!
var timers = [Timer]()
let tableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
tabelView.delegate = self
tabelView.dataSource = self
let tempTimer = Timer(timerLabel: "temp timer")
timers.append(tempTimer)
}
#IBAction func didUnwindFromNewTimerVC(_sender:UIStoryboardSegue){
guard let newTimerVC = _sender.source as? newTimerVC else{return}
newTimerVC.timer.setTimerLabel(timerLabel: newTimerVC.timerLabel.text!)
timers.append(newTimerVC.timer)
tableView.reloadData()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tabelView.dequeueReusableCell(withIdentifier: "TimerCell", for: indexPath) as? TimerCell{
let timer = timers[indexPath.row]
cell.updateUI(Timer: timer)
return cell
}else{
return UITableViewCell()
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return timers.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 78
}
}
Thank you
Please note the spelling. There are two table view instances: the outlet tabelView and a (pointless) instance tableView.
Reload the data of the outlet
tabelView.reloadData()
and delete the declaration line of the second instance let tableView ....
However I'd recommend to rename the outlet to correctly spelled tableView (you might need to reconnect the outlet in Interface Builder).
And force unwrap the cell
let cell = tabelView.dequeueReusableCell(withIdentifier: "TimerCell", for: indexPath) as! TimerCell
and remove the if - else part. The code must not crash if everything is hooked up correctly in IB.

Table View data appears on tableview with row selection code

I guess this could be one of my rookie mistakes I couldn't figure out.
I have an app which has a table view. It has text label and detail text label.
When I select a row, I takes me to another story board using segue...all of this works fine except the table view display on my simulator.
detail text label shows up on the simulator shown in this picture circled.
Here is the code I am using to detect cell/row selected. When I comment it out this issue goes away...
What you see in the red circle is gradeselected which is also in the detail text label in the tableview.
func sectionIndexTitles(for tableView: UITableView) -> [String]? {
let gradeselected = String(describing: sgrade)
return [gradeselected]
}
Screenshot of simulator with the issue
Please help in resolving this issue. Let me know if you need any more info.
Xcode 9.1
Swift 4
#Caleb here is my code.
import UIKit
class StudentsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var cellButton: UIButton!
#IBOutlet weak var studentDetailTable: UITableView!
var sname:[String]?
var sgrade:[Int]?
var gradetext = "Grade:"
var sstudentname = ""
override func viewDidLoad() {
super.viewDidLoad()
studentDetailTable.delegate = self
studentDetailTable.dataSource = self
// Do any additional setup after loading the view.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sname!.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = studentDetailTable.dequeueReusableCell(withIdentifier: "cell")
cell?.textLabel?.text = sname[indexPath.row] + gradetext + String(sgrade[indexPath.row])
sstudentname = sname![indexPath.row]
cell?.detailTextLabel?.text = String(sgrade![indexPath.row])
cell?.layer.cornerRadius = (cell?.frame.height)!/2
cell?.backgroundColor = UIColor.blue
cell?.textLabel?.textColor = UIColor.white
cell?.layer.borderWidth = 6.0
cell?.layer.cornerRadius = 15
cell?.layer.borderColor = UIColor.white.cgColor
cell?.textLabel?.textColor = UIColor.white
return cell!
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedIndex = tableView.dataSource?.sectionIndexTitles!(for: studentDetailTable)
let indexPath = tableView.indexPathForSelectedRow
let currentCell = tableView.cellForRow(at: indexPath!)!
let scell = currentCell.detailTextLabel!.text!
sstudentname = (currentCell.textLabel?.text)!
}
// - If I comment this section of the code issue goes away.
func sectionIndexTitles(for tableView: UITableView) -> [String]? {
let gradeselected = String(describing: sgrade)
return [gradeselected]
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let myKLVC = segue.destination as! KindergartenLevelViewController
myKLVC.klvstudentname = sstudentname
}
The text in the red circle says [1, 2], which looks like the array that probably holds all the grades, not just the one for a specific cell that we see in the string gradeselected. If you have such an array in your code, look for places where you might be converting it to a string and drawing it. Maybe you did that in an earlier iteration of your code to make sure that the array contained what you thought, or something?
Arrays don't just mysteriously draw themselves on the screen — somewhere, there's some code that causes that to happen. We can't really help you find it because you haven't shown very much of your code, but just knowing what to look for may help you find it yourself.
You can query the selected row via table view's property indexPathForSelectedRow.
The method you have implemented does exactly what you see in the simulator.
Just have a look at the documentation:
property indexPathForSelectedRow: https://developer.apple.com/documentation/uikit/uitableview/1615000-indexpathforselectedrow
func sectionIndexTitles: https://developer.apple.com/documentation/uikit/uitableviewdatasource/1614857-sectionindextitles

CollectionView in TableView displays false Data swift

I'm trying to combine a CollectionViewwith a TableView, so fare everything works except one problem, which I cant fix myself.
I have to load some data in the CollectionViews which are sorted with the header of the TableViewCell where the CollectionView is inside. For some reason, every time I start the app, the first three TableViewCells are identical. If I scroll a little bit vertically, they change to the right Data.
But it can also happen that while using it sometimes displays the same Data as in on TableViewCell another TableViewCell, here again the problem is solved if I scroll a little.
I think the problem are the reusableCells but I cant find the mistake myself. I tried to insert a colletionView.reloadData() and to set the cells to nil before reusing, sadly this didn`t work.
My TableViewController
import UIKit
import RealmSwift
import Alamofire
import SwiftyJSON
let myGroupLive = DispatchGroup()
let myGroupCommunity = DispatchGroup()
var channelTitle=""
class HomeVTwoTableViewController: UITableViewController {
var headers = ["LIVE","Channel1", "Channel2", "Channel3", "Channel4", "Channel5", "Channel6"]
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
self.navigationController?.navigationBar.isTranslucent = false
DataController().fetchDataLive(mode: "get")
DataController().fetchDataCommunity(mode: "get")
}
//MARK: Custom Tableview Headers
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return headers[section]
}
//MARK: DataSource Methods
override func numberOfSections(in tableView: UITableView) -> Int {
return headers.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
//Choosing the responsible PrototypCell for the Sections
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellBig", for: indexPath) as! HomeVTwoTableViewCell
print("TableViewreloadMain")
cell.collectionView.reloadData()
return cell
}
else if indexPath.section >= 1 {
// getting header Titel for reuse in cell
channelTitle = self.tableView(tableView, titleForHeaderInSection: indexPath.section)!
let cell = tableView.dequeueReusableCell(withIdentifier: "cellSmall", for: indexPath) as! HomeVTwoTableViewCellSmall
// anti Duplicate protection
cell.collectionView.reloadData()
return cell
}
else {
channelTitle = self.tableView(tableView, titleForHeaderInSection: indexPath.section)!
let cell = tableView.dequeueReusableCell(withIdentifier: "cellSmall", for: indexPath) as! HomeVTwoTableViewCellSmall
// anti Duplicate protection
cell.collectionView.reloadData()
return cell
}
}
}
}
My TableViewCell with `CollectionView
import UIKit
import RealmSwift
var communities: Results<Community>?
class HomeVTwoTableViewCellSmall: UITableViewCell{
//serves as a translator from ChannelName to the ChannelId
var channelOverview: [String:String] = ["Channel1": "399", "Channel2": "401", "Channel3": "360", "Channel4": "322", "Channel5": "385", "Channel6": "4"]
//Initiaize the CellChannel Container
var cellChannel: Results<Community>!
//Initialize the translated ChannelId
var channelId: String = ""
#IBOutlet weak var collectionView: UICollectionView!
}
extension HomeVTwoTableViewCellSmall: UICollectionViewDataSource,UICollectionViewDelegate {
//MARK: Datasource Methods
func numberOfSections(in collectionView: UICollectionView) -> Int
{
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return (cellChannel.count)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionCellSmall", for: indexPath) as? HomeVTwoCollectionViewCellSmall else
{
fatalError("Cell has wrong type")
}
//removes the old image and Titel
cell.imageView.image = nil
cell.titleLbl.text = nil
//inserting the channel specific data
let url : String = (cellChannel[indexPath.row].pictureId)
let name :String = (cellChannel[indexPath.row].communityName)
cell.titleLbl.text = name
cell.imageView.downloadedFrom(link :"link")
return cell
}
//MARK: Delegate Methods
override func layoutSubviews() {
myGroupCommunity.notify(queue: DispatchQueue.main, execute: {
let realm = try! Realm()
//Getting the ChannelId from Dictionary
self.channelId = self.channelOverview[channelTitle]!
//load data from Realm into variables
self.cellChannel = realm.objects(Community.self).filter("channelId = \(String(describing: self.channelId)) ")
self.collectionView.dataSource = self
self.collectionView.delegate = self
print("collectionView layout Subviews")
self.collectionView.reloadData()
})
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectedCommunity = (cellChannel[indexPath.row].communityId)
let home = HomeViewController()
home.showCommunityDetail()
}
}
Thanks in advance.
tl;dr make channelTitle a variable on your cell and not a global variable. Also, clear it, and your other cell variables, on prepareForReuse
I may be mistaken here, but are you setting the channelTitle on the cells once you create them? As I see it, in your viewController you create cells based on your headers, and for each cell you set TableViewController's channelTitle to be the title at the given section.
If this is the case, then the TableViewCell actually isn't receiving any information about what it should be loading before you call reloadData().
In general, I would also recommend implementing prepareForReuse in your HomeVTwoTableViewCellSmall, since it will give you a chance to clean up any stale data. Likely you would want to do something like set cellChannel and channelId to empty strings or nil in that method, so when the cell is reused that old data is sticking around.
ALSO, I just reread the cell code you have, and it looks like you're doing some critical initial cell setup in layoutSubviews. That method is going to be potentially called a lot, but you really only need it to be called once (for the majority of what it does). Try this out:
override the init with reuse identifier on the cell
in that init, add self.collectionView.dataSource = self and self.collectionView.delegate = self
add a didSet on channelTitle
set channelTitle in the viewController
So the code would look like:
var channelTitle: String = "" {
didSet {
self.channelId = self.channelOverview[channelTitle]!
self.cellChannel = realm.objects(Community.self).filter("channelId = \(String(describing: self.channelId)) ")
self.collectionView.reloadData()
}
}
This way you're only reloading your data when the cell is updated with a new channel, rather than every layout of the cell's views.
Sorry... one more addition. I wasn't aware of how your channelTitle was actually being passed. As I see it, you're using channelTitle as a global variable rather than a local one. Don't do that! remove channelTitle from where it is currently before implementing the code above. You'll see some errors, because you're setting it in the ViewController and accessing it in the cell. What you want is to set the channelTitle on the cell from the ViewController (as I outlined above). That also explains why you were seeing the same data across all three cells. Basically you had set only ONE channelTitle and all three cells were looking to that global value to fetch their data.
Hope that helps a little!
(also, you should be able to remove your else if block in the cellForRowAtIndexPath method, since the else block that follows it covers the same code. You can also delete your viewDidLoad, since it isn't doing anything, and you should, as a rule, see if you can get rid of any !'s because they're unsafe. Use ? or guard or if let instead)

Is this the right way to populate UICollectionView (inside a table view cell) with Firebase?

Problem: TableView is very slow when scrolling. Looks like my code is not efficient at all.
So I have a UICollectionView embedded inside a tableViewCell like so (I used this tutorial to accomplish it.)
I am using Firebase to populate data into the UICollectionViewCells. I have 3 class folders:
TableViewCtrl: Responsible for downloading section titles and then passing some logic to tableViewCell. Here is partial code of the main TableViewCtrl:
// 1. DOWNLOAD SECTION TITLES AND THEN CALL RELOADTABLE
// 2. TableView:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return featuredCollection.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "browseCell", for: indexPath) as! BrowseCell
cell.configureCell(of: featuredCollection[indexPath.row].typeOfItem, title: featuredCollection[indexPath.row].categoryTitle, bookReference: featuredCollection[indexPath.row].bookReference, spotlightTests: featuredCollection[indexPath.row].spotlightTests, bookUniqueIDsToDownload: featuredCollection[indexPath.row].bookUniqueIDsToDownload)
return cell
}
TableViewCell:
func configureCell(of type: FeaturedItem, title: String, bookReference: BookReferenceTest?, spotlightTests: [BookReferenceTest]?, bookUniqueIDsToDownload: [String]?) {
setCollectionViewDataSourceDelegate(delegate: self, dataSource: self)
// DOWNLOAD THE BOOK ITEMS (eg. IMAGES, TITLES, ETC) then call self.collectionView.reloadData()
}
internal func setCollectionViewDataSourceDelegate <D: UICollectionViewDelegate, S: UICollectionViewDataSource>(delegate: D, dataSource: S) {
collectionView.delegate = delegate
collectionView.dataSource = dataSource
let collectionViewFlowLayout = UICollectionViewFlowLayout()
let myCollectionView = UICollectionView(frame: self.collectionView.bounds, collectionViewLayout: collectionViewFlowLayout)
myCollectionView.delegate = self
myCollectionView.dataSource = self
collectionView.reloadData()
}
// Snipit of code that's responsible for downloading book assets:
func downloadBrowsingBooks(bookUniqueKeys: [String]) {
let databaseReference = FIRDatabase.database().reference()
databaseReference.child("Kutub/Books/").observeSingleEvent(of: .value, with: {
(snapshot) in
var books = [BrowsingBook]()
for book in (snapshot.children.allObjects as! [FIRDataSnapshot]) {
if bookUniqueKeys.contains((book.key)) {
let browsingBookValues = book.value as! [String : AnyObject]
let browsingBook = self.createBrowsingBookObject(data: browsingBookValues, uniqueKey: book.key)
books.append(browsingBook)
}
}
self.storedBooks = books
self.collectionView.reloadData()
})
}
internal func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "booksCollectionCell", for: indexPath) as! BooksCollectionCell
let bookTitle = storedBooks[indexPath.item].title
let authors = storedBooks[indexPath.item].authors
cell.configureCell(title: bookTitle, authorNames: authors)
return cell
}
UICollectionViewCell:
func configureCell(title: String, authorNames: [String]? = nil, imageCover: UIImage? = nil) {
var authorName = ""
if let authors = authorNames {
authorName = authors[0]
for index in 1..<authors.count {
authorName += ", \(authors[index])"
}
}
// ....
}
From my understanding, here's step-by-step of what's happening:
Section titles are downloaded
TableView.reload() configures the tableViewCells
Inside tableViewCells, firebase downloads images and other book assets (eg. titles, authors, publishers names in text from Firebase database) and calls on collectionView
CollectionView configures it's cells.
Again, my main problems is that scrolling is very slow and laggy with the way that I'm doing this. When I tried different methods (eg. downloading the data and passing it on to tableviewCell) it works but when I add items to Firebase database only section titles show up and not the content inside the collectionViewCells.

Resources