My TableView Cells aren't loading, when i go to another screen and come back my data shows?
any way to fix this
my Code:
import UIKit
import FirebaseDatabase
var mynamesArray = [String]()
class Services: UIViewController, UITableViewDelegate, UITableViewDataSource {
var refHandle: UInt!
#IBOutlet var tableView: UITableView!
var videos: [String] = []
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = mynamesArray[indexPath.row]
tableView.reloadData()
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
refHandle = ref.child("Services").child("ServiceA").observe(.value, with : { (snapshot) in
mynamesArray = []
for child in snapshot.children {
mynamesArray.append((child as AnyObject).key)
}
print (mynamesArray)
})
return mynamesArray.count
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == UITableViewCell.EditingStyle.delete {
mynamesArray.remove(at: indexPath.row)
tableView.reloadData()
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
myIndex = indexPath.row
performSegue(withIdentifier: "seque", sender: self)
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.reloadData()
tableView.delegate = self
}
I am new so i don't really know how stack overflow works, do let me know if i should update anything or add anything :)
EDIT: I FORGOT TO MENTION THIS BUT I HAVE A SECOND VIEW CONTROLLER IN WHICH THE TEXT APPEARS:
import UIKit
import FirebaseDatabase
class ServicesDisplayViewController: UIViewController {
let ref = Database.database().reference()
#IBOutlet var descLabel: UILabel!
#IBOutlet var titlelabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
ref.child("ServiceC").child("Title").observeSingleEvent(of: .value, with: { DataSnapshot in
print(DataSnapshot) // replace this with textLabel or other item
})
titlelabel?.text = mynamesArray[myIndex]
descLabel?.text = descriptionList[myIndex]
// Do any additional setup after loading the view.
}
You are not using your numberOfRowInSection properly and are confusing sync and async programming.
numberOfRowsInSection is a synchronous method that needs to return "immediately" the number of rows for that section.
This means that you need to know how many cells are in that section in advance.
Inside of it, instead, you are using a firebase method to get that number, and that method is asynchronous. This means that when you return the number of rows it will always be 0 since the block of code you pass to the observe method will execute only after some time (the time of the http request to be made).
Another problem in your code is also the fact that you don't know how many times numberOfRowsInSection is called by the tableView, so you could end up re-making the same http request over and over (maybe not now, but in some future implementation of yours).
You should find a single place, that is maybe called only once and before the table view is shown, to call that method and fill your array before reloading the tableview.
In order to do that, you can add, in the viewDidLoad the code that handles the fetching of the array of items, than (once it's completed) you can reload the table (after you dispatch on main thread).
It would look something like this:
func viewDidLoad() {
super.viewDidLoad()
refHandle = ref.child("Services").child("ServiceA").observe(.value, with : { (snapshot) in
mynamesArray = []
for child in snapshot.children {
mynamesArray.append((child as AnyObject).key)
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
})
}
And in numberOfRowsInSection you just return the count:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) ->
return mynamesArray.count
}
Related
I have been at this for a few days now, what I am trying to do is to be able to select 5 cells (previous populated by a realm database). Any help on this would be really appreciated.
Thanks Mark
The following is my current code as is relates to the Segue to pass the data. I think I have edited the code down to the key elements. Let me know if you need any other information.
Edits Made - based on the suggestion made below. I took a step back and approached the problem differently, and developed a solution. I have updated the code below to reflect my working solution. Hope this helps someone else in the future. Cheers Mark
Primary VC
import UIKit
import RealmSwift
class ExercisesSelectionTimed: UITableViewController{
var realm = try! Realm()
var exercises : Results<ExercisesModel>?
#IBOutlet weak var tableview: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
//populates the Realm Data with the default list
exercises = realm.objects(ExercisesModel.self)
tableView.allowsMultipleSelection = true
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return exercises?.count ?? 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ExercisesAddedTableViewCell", for: indexPath) as! ExercisesAddedTableViewCell
// get the specific exercise in the array
let exercise = exercises?[indexPath.row]
cell.exercisenameLabel.text = exercise?.name
cell.equipmentnameLabel.text = exercise?.equipment
return cell
}
private func registerTableViewCells() {
let textFieldCell = UINib(nibName: "ExercisesAddedTableViewCell",bundle: nil)
self.tableview.register(textFieldCell,forCellReuseIdentifier: "ExercisesAddedTableViewCell")
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
switch (timerSelected) {
case "FGB":
//User needs to select 5 exercises to populate the FGB timer on the next VC
if let selectedRows = tableView.indexPathsForSelectedRows {
//lets the user select 5 cells
if selectedRows.count == 5 {
performSegue(withIdentifier: K.FGB, sender: self)
}
}
default : print ("error")
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
switch (timerSelected) {
case "FGB":
if let selectedRows = tableView.indexPathsForSelectedRows {
let selected = selectedRows.map{exercises?[$0.row]}
let destinationVC = segue.destination as! FBGTimerVCTable
destinationVC.selectedExercise = selected
}
default : print ("error")
}
}
}//last bracket in Class
The target for the data
import UIKit
import RealmSwift
class FBGTimerVCTable: HeartRateMonitorBrain{
var realm = try! Realm()
var selectedExercise = [ExercisesModel?]()
#IBOutlet weak var tableview: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
}
}//last bracket in Class
extension FBGTimerVCTable: UITableViewDataSource{
//setup tableview
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ExercisesAddedTableViewCell", for: indexPath) as! ExercisesAddedTableViewCell
//get the specific 5 exercises selected in the ExercisesSelectionTimed VC
cell.exercisenameLabel.text = selectedExercise[indexPath.row]?.name
cell.equipmentnameLabel.text = selectedExercise[indexPath.row]?.equipment
return cell
}
private func registerTableViewCells() {
let textFieldCell = UINib(nibName: "ExercisesAddedTableViewCell",bundle: nil)
self.tableview.register(textFieldCell,forCellReuseIdentifier: "ExercisesAddedTableViewCell")
}
}//last bracket in Class Extension
Realm
import RealmSwift
class ExercisesModel : Object {
#objc dynamic var name : String = ""
#objc dynamic var equipment : String = ""
}
How can I access the value of the cell of the table view that the user selects? I know how to access the value if the user hasn't searched because I can just access the IndexPath's value of my array, but I can't when the user has searched something because the townArray won't line up with the cells that are shown.
To give you a better understanding- just say I have an array of fruits which has [apples, bananas, oranges]. If the user searches for bananas, then the only cell with text showing (results) will say bananas. If I then try to access the IndexPath element of fruits, I will get apples since it is the first element of fruits and bananas is the first element showing. What I want is to get the access the value bananas when the user selects bananas and searches "bananas" instead of apples. I know this may be confusing but please let me know if you have any thoughts on how I can solve this issue.
var searchedResult: [Fruit] = yourSearchingFunction()
tableview.reloadData()
IndexPaths would refresh correctly after TableView reloaded with a new collection of fruits
Explanation is showed with the following code:
class ViewController: UIViewController {
#IBOutlet private weak var tableView: UITableView!
private var originalFruits: [Fruit] = [] // This data is used to cache
private var fruits: [Fruit] = [] // This data is used to display on TableView
// MARK: - View Cycle
override func viewDidLoad() {
super.viewDidLoad()
setupTableView()
setupData()
}
private func setupData() {
// Loading all fruits at the first time [Apple, Banana, Orange ]
originalFruits = yourSetupDataFunctionToFetchAllFruitsAtTheFirstTime()
fruits = originalFruits
tableView.reloadData()
}
#IBAction private func actionTapToSearchButton(_ sender: Any) {
let searchingKey = searchTextField.text
if searchingKey.isEmpty {
fruits = originalFruits
} else {
fruits = yourSeacrchingFruitFunction(searchingKey)
}
tableView.reloadData()
}
}
// MARK: - UITableViewDataSource
extension ViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return fruits.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: FruitCell.identifier) as? FruitCell {
return cell
}
return UITableViewCell()
}
}
// MARK: - UITableViewDelegate
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
}
I'm creating an app, in which one of the functions is, that the user should be able to write a person's name and an answer to a question - and then when pressing the save-button he/she should be redirected to the previous controller again, which not have created a tableViewCell with this data as title. (Later on you can ofcourse click this cell and see the data in third viewcontroller.)
My way of tackling this was to let the "save" button save the name and the answer by using NSUserDefault. Then connecting a segue to the button at the same time to make it redirect the user to the previous controller - and finally to have the tableView in the previous controller refer to the newly created NSUserDefault-key in the cell.textfield.
I have two questions.
Why does this not work? My code from both viewControllers are underneeth. I don't get why it doesn't work.
If I do get this to work: How do I implement the effect, that every time you enter the "Creating viewController", in which you can write the name and the answer - the user gets the option of saving a NEW person and adding a NEW cell, instead of overriding the old one, which I'm afraid will happen if I get the current approach to work...
Code in the "Creating viewController", where you can write the name and the answer:
class CreateNewPerson: UIViewController {
let defaults = UserDefaults.standard
#IBOutlet weak var Question: UILabel!
#IBOutlet weak var ExtraIdentifier: UILabel!
#IBOutlet weak var PersonName: UITextField!
#IBOutlet weak var PersonAnswer: UITextField!
#IBOutlet weak var PersonExtraIdentifier: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
showDiaryIdentifiers () }
func showDiaryIdentifiers () {
let DiaryQuestion = self.defaults.string(forKey: "DiaryQuestionKey")
let ExtraIdentifer = self.defaults.string(forKey: "RandomIdentifierKey")
self.Question.text = DiaryQuestion
self.ExtraIdentifier.text = ExtraIdentifer
}
#IBAction func SavePerson () {
self.defaults.setValue(self.PersonName.text, forKey: "PersonNameKey")
self.defaults.setValue(self.PersonAnswer.text, forKey: "PersonAnswerKey")
self.defaults.setValue(self.PersonExtraIdentifier.text, forKey: "PersonExtraIdentiferKey")
} }
Code in the other viewController:
class AllPersonsInYourDiary: UIViewController, UITableViewDelegate, UITableViewDataSource {
let defaults = UserDefaults.standard
#IBOutlet weak var ShowingDiaryName: UILabel!
#IBOutlet weak var ShowingDiaryQuestion: UILabel!
#IBOutlet weak var ShowingExtraIdentifer: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
showDiaryIdentifiers()
self.navigationItem.rightBarButtonItem = self.editButtonItem
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func showDiaryIdentifiers () {
let DiaryName = self.defaults.string(forKey: "DiaryNameKey")
let DiaryQuestion = self.defaults.string(forKey: "DiaryQuestionKey")
let ExtraIdentifer = self.defaults.string(forKey: "RandomIdentifierKey")
self.ShowingDiaryName.text = DiaryName
self.ShowingDiaryQuestion.text = DiaryQuestion
self.ShowingExtraIdentifer.text = ExtraIdentifer
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Person1", for: indexPath)
cell.textLabel?.text = self.defaults.string(forKey: "PersonNameKey")
cell.textLabel?.numberOfLines = 0
cell.textLabel?.font = UIFont.preferredFont(forTextStyle: UIFontTextStyle.headline)
return cell
}
In this code, I guess what is not working is the cellForRowAt method. What am I getting wrong? Right now it's not creating any cells at all.
Also, I know I should notr1 return 1 row and 1 section. It's just for now. I know I should in the end return Something.count - but I haven't yet figured out what this something is...
Thanks!
You already created a table view with only one row.
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
When returning to previous controller just reload tableview like(Make sure before reloading datasource have contain new data.)
tableView.reloadData()
If I understand correctly that you need the user to enter a set of values and then use these values to populate a table view in another view controller, then what you wanna do is:
1- create 2 dictionaries, an optional dictionary in AllPersonsInYourDiary that would carry the new values and one in your CreateNewPerson something like this let dic = [[String: String]]().
2- Instantiate the view controller:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "#yourSegueIdentifier" {
let vc = segue.destination as! AllPersonsInYourDiary
vc.dic = self.dic
}
}
3- in your AllPersonsInYourDiary view controller, override the functions like this:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dic.count
}
and populate the cell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Person1", for: indexPath)
cell.textLabel?.text = dic[indexPath.row]["#whateverKeyForValue"]
cell.textLabel?.numberOfLines = 0
cell.textLabel?.font = UIFont.preferredFont(forTextStyle: UIFontTextStyle.headline)
return cell
}
I have a method which grabs JSON and appends individual values from the JSON into the array like so:
internal let methods = Methods()
var countryData = [""]
#IBOutlet weak var countryTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
displayCountries()
}
func displayCountries() {
// Wait for task to complete before grabbing JSON
methods.getCountriesData {() -> () in
for (_, value) in self.methods.getJSON() {
self.countryData.append(String(describing: value))
}
DispatchQueue.main.async {
self.countryTableView.reloadData()
print(self.countryData)
}
}
}
I have UITableView delegates declared like so also:
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return countryData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")! as UITableViewCell
let row = indexPath.row
cell.textLabel?.text = self.countryData[row]
return cell
}
The print(self.countryData) prints all of the countries within the log (100's of countries) however for some reason the countries aren't being displayed in the UITableView, does anyone understand why?
You need to specify the table view's data source. This can be done in code or in the storyboard.
In code:
countryTableView.dataSource = self
You will also need to have your data source object conform to the UITableViewDataSource protocol:
MyViewController: UIViewController, UITableViewDataSource {
Since the data is an array of strings and a property on the view controller, this should work fine.
In the storyboard:
Simply drag the dataSource outlet to the object which is providing the data (in this example it is the view controller):
I have searched for other questions and asking this because I couldn't find anything suitable for me.
I have some data in core data and want to update it when didSelectRowAtIndexPath is called. this is my code
class ViewController: UIViewController, UITableViewDelegate {
var todo = [NSManagedObject]()
var JSON = Data.sharedInstance
#IBOutlet var TableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.JSON.JSONData {
SVProgressHUD.dismiss()
self.TableView.reloadData()
}
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedobject = appDelegate.managedObjectContext
let fetchrequest = NSFetchRequest(entityName: "ToDo")
do {
let results = try managedobject.executeFetchRequest(fetchrequest)
todo = results as! [NSManagedObject]
} catch {
print("error")
}
TableView.reloadData()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return todo.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
let state = todo[indexPath.row].valueForKey("state") as? Float
print(state)
if state == 0.0 {
cell.textLabel?.text = todo[indexPath.row].valueForKey("name") as? String
}
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
}
Now, When the didSelectRowAtIndexPath is called, I want to update the key "state" that can be seen in cellForRowAtIndexPath to 1.0
Can someone one please me on how to update the value of "state". Thanks
Create a new method something like reloadData and move the code for fetching object from viewDidAppear. In didSelectRowAtIndexPath, do something like:
todo[indexPath.row].updateValue(NSNumber(float: 1.0), forKey: "state") // Sample syntax only. Not sure if it is correct.
managedObjectContext.save()
reloadData()
I don't know how your model and Core Data related, but the idea is update the model when didSelectedRowAtIndexPath called, and tell the tableView to reloadData().
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
// Update in todo array.
// Save it, even I don't recommend to save it here,
tableView.reloadData()
}
Usually, data could be saved when app enters background. Or some other place which will not be called constantly, since save will be a costing job to do.