I am fetching all user id's from my Firebase database. When I execute the program I can see snapshot of all user id's on console via code in line 26. But the code is not writing to table cells. I done this with tutorial. Everything is same with video. But it does not work for me Where is the problem ?
class ChatInfo: UITableViewController {
let cellId = "cellId"
var users = [User] ()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Geri", style: .plain, target:self, action: #selector(handleCancel))
fetchUser()
}
func handleCancel() {
dismiss(animated: true, completion: nil)
}
func fetchUser() {
Database.database().reference().child("locations").observe(.childAdded, with: {(snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let user = User()
user.userId = dictionary["userId"] as! String
print(user.userId) // IT PRINTS ALL USERS TO CONSOLE
self.users.append(user)
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}
} , withCancel: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func numberOfSections(in tableView: UITableView) -> Int {
return users.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: cellId)
let user = users[indexPath.row]
cell.detailTextLabel?.text = user.userId
return cell
}
}
You are overriding the wrong method
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return users.count
}
And design the cell style in Interface Builder and use this method in cellForRowAt
let cell = tableView.dequeueReusableCell(withCellIdentifier: cellId, for: indexPath)
As per your requirement actual way for fetching records of tableview cell is this:
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return users.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withCellIdentifier: cellId, for: indexPath)
let user = users[indexPath.row]
cell.detailTextLabel?.text = user.userId
return cell
}
Related
I have two buttons in my user's profile page, one for the saved shop items and one for his reviews.
I want when the user clicks the saved button it would load his saved shop's items in the table view and when he clicks the reviews button it would load his reviews.
I'm struggling on how to figure out how to do this
Any help, please?
here is my code:
#IBOutlet weak var reviewsBtn: UIButton!
#IBOutlet weak var saveBtntab: UIButton!
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if(reviewsBtn.isSelected == true){
print("review selected")
return reviews.count
}
if(saveBtntab.isSelected == true){
print("saved selected")
return shops.count
}
return shops.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellFave", for: indexPath) as! FaveTableViewCell
let shops = self.shops[indexPath.row]
let reviews = self.reviews[indexPath.row]
// i want to do the same idea for the number of rows here.
}
#IBAction func reviewsTapped(_ sender: Any) {
reviewsBtn.isSelected = true
reviewsBtn.isEnabled = true
faveBtntab.isEnabled = false
faveBtntab.isSelected = false
}
#IBAction func savedTapped(_ sender: Any) {
faveBtntab.isSelected = true
faveBtntab.isEnabled = true
reviewsBtn.isEnabled = false
reviewsBtn.isSelected = false
}
First of all if there are only two states you can simplify numberOfRows
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return reviewsBtn.isSelected ? reviews.count : shops.count
}
In cellForRow do the same thing, display the items depending on reviewsBtn.isSelected
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellFave", for: indexPath) as! FaveTableViewCell
if reviewsBtn.isSelected {
let reviews = self.reviews[indexPath.row]
// assign review values to the UI
} else {
let shops = self.shops[indexPath.row]
// assign shop values to the UI
}
}
And don't forget to call reloadData when the state has changed.
You can create two different dataSource instances for clarity and separation like following -
class ShopsDataSource: NSObject, UITableViewDataSource, UITableViewDelegate {
var shops: [Shop] = []
var onShopSelected: ((_ shop: Shop) -> Void)?
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return shops.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ShopTableViewCell", for: indexPath) as! ShopTableViewCell
let shop = self.shops[indexPath.row]
cell.populateDetails(shop: shop)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.onShopSelected?(shops[indexPath.row])
}
}
class ReviewsDataSource: NSObject, UITableViewDataSource, UITableViewDelegate {
var reviews: [Review] = []
var onReviewSelected: ((_ review: Review) -> Void)?
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return reviews.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ReviewTableViewCell", for: indexPath) as! ReviewTableViewCell
let review = self.reviews[indexPath.row]
cell.populateDetails(review: review)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.onReviewSelected?(reviews[indexPath.row])
}
}
class ViewController: UIViewController {
let shopsDataSource = ShopsDataSource()
let reviewsDataSource = ReviewsDataSource()
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(ShopTableViewCell.self, forCellReuseIdentifier: "ShopTableViewCell")
tableView.register(ReviewTableViewCell.self, forCellReuseIdentifier: "ReviewTableViewCell")
shopsDataSource.onShopSelected = { [weak self] (shop) in
self?.showDetailsScreen(shop: shop)
}
reviewsDataSource.onReviewSelected = { [weak self] (review) in
self?.showDetailsScreen(review: review)
}
}
#IBAction func shopsTapped(_ sender: Any) {
tableView.dataSource = shopsDataSource
tableView.delegate = shopsDataSource
tableView.reloadData()
}
#IBAction func addNewShop(_ sender: Any) {
/// ask user about shop details and add them here
shopsDataSource.shops.append(Shop())
tableView.reloadData()
}
func showDetailsScreen(shop: Shop) {
/// Go to shop details screen
}
#IBAction func reviewsTapped(_ sender: Any) {
tableView.dataSource = reviewsDataSource
tableView.delegate = reviewsDataSource
tableView.reloadData()
}
#IBAction func addNewReview(_ sender: Any) {
/// ask user about review details and add them here
reviewsDataSource.reviews.append(Review())
tableView.reloadData()
}
func showDetailsScreen(review: Review) {
/// Go to review details screen
}
}
my cells are not appearing.
I did:
Checked if datasource and delegate were connected
Checked if my custom cells identifier name and class were correct
Things that I didn't:
I am struggling with auto layout, so I just decided not to do it.
My app is loading with the correct amount of cells, but the cells are not registered.
My code:
import UIKit
class WelcomeViewController: UITableViewController, NetworkManagerDelegate {
private var networkManager = NetworkManager()
private var infoForCells = [Result]()
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.register(UINib(nibName: "ImageViewCell", bundle: nil), forCellReuseIdentifier: "imageCell")
networkManager.delegate = self
networkManager.fetchNews()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return infoForCells.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "imageCell", for: indexPath) as? ImageViewCell else{
return UITableViewCell(style: .default, reuseIdentifier: "cell")
}
let cellIndex = infoForCells[indexPath.row]
cell.titleForImage.text = cellIndex.alt_description
print(cell.titleForImage ?? "lol")
// if let image = cellIndex.urlToImage {
// cell.imageForArticle.load(url: image)
// }
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
func didUpdateNews(root: Root) {
infoForCells = root.results
}
}
Reload the table
func didUpdateNews(root: Root) {
infoForCells = root.results
tableView.reloadData()
}
In addition to Sh_Khan answer you can also listen to updates of infoForCells property
private var infoForCells = [Result]() {
didSet {
DispatchQueue.main.async { [weak self] in
self?.tableView.reloadData()
}
}
}
I am having trouble finding out how to group my sections by a property in my Core Data database. This is what my DB looks like here. I am trying to group my tableView by the dueDate property. I have loaded up my Attributes in an array and that is how they are displayed. I plan on customizing the headers as well, so I would like to use the standard tableView methods. Here is the code from my ViewController.
import UIKit
import CoreData
class MainTableViewController: UITableViewController {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var taskArray = [Task]()
override func viewDidAppear(_ animated: Bool) {
loadData()
}
// MARK: - Table view functions
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return taskArray.count
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "Date"
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 65.00
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "taskCell", for: indexPath) as! TaskCell
cell.nameLabel.text = taskArray[indexPath.row].name ?? "Add Items"
if taskArray[indexPath.row].dueTime == nil {
cell.timeLabel.text = ""
} else {
let timeFormatter = DateFormatter()
timeFormatter.timeStyle = .short
cell.timeLabel.text = timeFormatter.string(from: taskArray[indexPath.row].dueTime!)
}
return cell
}
// MARK: Add New Task
#IBAction func addButtonPressed(_ sender: Any) {
performSegue(withIdentifier: "newTaskSegue", sender: self)
}
// MARK: Save & Load Data
func saveData() {
do {
try context.save()
} catch {
print("Error saving context \(error)")
}
tableView.reloadData()
}
func loadData() {
let request : NSFetchRequest<Task> = Task.fetchRequest()
let sort = NSSortDescriptor(key: "dueDate", ascending: false)
let sort2 = NSSortDescriptor(key: "dueTime", ascending: false)
request.sortDescriptors = [sort, sort2]
do {
taskArray = try context.fetch(request)
} catch {
print("Error loading data \(error)")
}
tableView.reloadData()
}
}
Any help would be much appreciated. Thanks!
You can easily group your data using NSFetchedResultsController. One parameter in the instantiation of NSFetchedResultsController specifically allows you to group your results into sections by passing the keyPath of an attribute that constitutes the predicate for section grouping.
Apple's documentation explains this pretty clearly, with example code:
override func numberOfSections(in tableView: UITableView) -> Int {
if let frc = <#Fetched results controller#> {
return frc.sections!.count
}
return 0
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let sections = self.<#Fetched results controller#>?.sections else {
fatalError("No sections in fetchedResultsController")
}
let sectionInfo = sections[section]
return sectionInfo.numberOfObjects
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = <#Get the cell#>
guard let object = self.<#Fetched results controller#>?.object(at: indexPath) else {
fatalError("Attempt to configure cell without a managed object")
}
// Configure the cell with data from the managed object.
return cell
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
guard let sectionInfo = <#Fetched results controller#>?.sections?[section] else {
return nil
}
return sectionInfo.name
}
override func sectionIndexTitles(for tableView: UITableView) -> [String]? {
return <#Fetched results controller#>?.sectionIndexTitles
}
override func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
guard let result = <#Fetched results controller#>?.section(forSectionIndexTitle: title, at: index) else {
fatalError("Unable to locate section for \(title) at index: \(index)")
}
return result
}
It is generally a Good Idea(tm) to use an NSFetchedResultsController when dealing with CoreData and UITableView or UICollectionView as you get handy notifications (through a NSFetchedResultsControllerDelegate) when your data changes that allow you to insert or remove cells from your displayed view.
I am having a small mental block here, I am pretty comfortable with Core Data and decided to have a delve into CloudKit for some of my Apps, however whilst the upload side was fairly basic, I am having trouble populating a simple table view.
The CKRecord is Activity and the field I would like to display is name. The print function print(actName) returns 7 times showing all the records have been counted but the table is blank and no errors.
I am sure this is something simple and i can't see the wood for the trees, so I am happy for a point in the right direction please.
Cheers
import UIKit
import CloudKit
class TableViewController: UITableViewController {
var activities = [Activity]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("counted records")
return activities.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let active = activities[indexPath.row]
if let actName = active.name {
cell.textLabel?.text = actName
print(actName)
}
return cell
}
I seem to have sorted it out, user3069232, I don't think there was/is a great latency problem as the updating code was instantaneous with CloudKit Desktop. Nirav I think you were right it boiled down to not storing and then reloading. I have modified my original code as I think 'Activity' was causing problems too, the script below works well, thanks for the point in the right direction guys.
import UIKit
import CloudKit
class CoursesVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var coursesTable: UITableView!
var coursesArray: Array<CKRecord> = []
var selectedCourseIndex: Int!
override func viewDidLoad() {
super.viewDidLoad()
coursesTable.delegate = self
coursesTable.dataSource = self
fetchCourses()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return coursesArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "courseCell", for: indexPath)
let courseRecord: CKRecord = coursesArray[indexPath.row]
cell.textLabel?.text = courseRecord.value(forKey: "courseVenue") as? String
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd MMMM yyyy"
let CSDate = dateFormatter.string(from: courseRecord.value(forKey: "courseDate") as! Date)
// let CSDetail = courseRecord.value(forKey: "courseDetail") as? String
cell.detailTextLabel?.text = CSDate
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 40.0
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedCourseIndex = indexPath.row
performSegue(withIdentifier: "showMapDetail", sender: self)
}
func fetchCourses() {
let container = CKContainer.default()
let publicDatabase = container.publicCloudDatabase
let predicate = NSPredicate(format: "TRUEPREDICATE")
let query = CKQuery(recordType: "Courses", predicate: predicate)
query.sortDescriptors = [NSSortDescriptor(key: "courseDate", ascending: true)]
publicDatabase.perform(query, inZoneWith: nil) { (results, error) -> Void in
if error != nil {
print("error fetch notes \(error)")
} else {
print("Success")
for result in results! {
self.coursesArray.append(result )
}
OperationQueue.main.addOperation({ () -> Void in
self.coursesTable.reloadData()
self.coursesTable.isHidden = false
})
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Userdefaults-saved data is passed from my TextViewCotroller to TextViewTableController successfully, but not perfectly successful. This is because when my TextView, which has some data already, is re-saved, it causes a duplicate.
For example, if the firstly saved data is like "hello, I like bagels" and if I edit it to like "hello, I like bagels and chololate cookies" and re-save it,
At the 0 index of my TableView is "hello, I like bagels and chololate cookies"
At the 1 index of my TableView is "hello, I like bagels"
When this is repeatedly done, there are multiple duplicates of the same text in my TableView. This is so annoying that I really want to detect the cause of this issue. However, I have no idea of fixing this bug.
TextTableViewController:
class TextTableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
self.tableView.reloadData()
}
func saveTextData() -> [String] {
if let textData = userTextDataSave.array(forKey: "txtData") as? [String] {
return textData
}
return []
}
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 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return saveTextData().count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellForText", for: indexPath)
cell.backgroundColor = UIColor.clear
cell.preservesSuperviewLayoutMargins = false
cell.textLabel?.text = saveTextData()[indexPath.row]
cell.textLabel?.font = UIFont.systemFont(ofSize: 20)
tableView.reloadData()
return cell
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 50
}
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
// Override to support editing the table view.
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
// Delete the row from the data source
removeHistory(index: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
} else if editingStyle == .insert {
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "text",sender: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any!) {
if (segue.identifier == "text") {
let subVC: TextViewController = (segue.destination as? TextViewController)!
let indexPath = tableView.indexPathForSelectedRow;
subVC.textFromCell = saveTextData()[(indexPath?.row)!]
}
}
}
TextViewController and functions for saving text data:
func save(){
let alert = UIAlertController(title: "titile", message: "save?", preferredStyle: .alert)
let noAction = UIAlertAction(title: "Cancel", style: .default, handler: { Void in
})
let okAction = UIAlertAction(title: "Save", style: .default, handler: { Void in
self.addTextData(text: self.myTextView.text)
})
alert.addAction(noAction)
alert.addAction(okAction)
present(alert, animated: false, completion: nil)
}
func saveTextData() -> [String] {
if let textData = userTextDataSave.array(forKey: "txtData") as? [String] {
return textData
}
return []
}
func addTextData(text: String) {
var data = saveTextData()
for d in data {
if d == "" {
return
}
}
data.insert(text, at: 0)
userTextDataSave.set(text, forKey: "txtData")
userTextDataSave.synchronize()
}
Try this :
func addTextData(text: String) {
var data = saveTextData()
for d in data {
if d == "" {
return
}
if d == text {
return
}
}
userTextDataSave.set(text, forKey: text)
userTextDataSave.synchronize()
}