I got an array that populates a tableview, it works fine when I run the app.
I created a popover with a PickerView to choose one option to sort the TableView data.
I get the user choise in the popover, pass it to the main ViewController, sorted the data and called tableview.reloadData() but nothing happens.
I printed the array after the sort and the array is sorted but I can't saw the changes.
But if I go to other ViewController and came back the data is changed.
Why the changes are not showing when I call the tableview.reloadData().
Here's the code:
var dataModel = DataModel()
var ordenacao = String()
override func viewWillAppear(_ animated: Bool) {
dataModel.loadData()
tableView.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
ordenadados(ordem: ordenacao)
tableView.reloadData()
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: UITableViewCell! = tableView.dequeueReusableCell(withIdentifier: "cellIdentifier")
cell.textLabel?.text = dataModel.notas[indexPath.row].titulo
cell.detailTextLabel?.text = dataModel.notas[indexPath.row].datafinal
print("Ordena Tableview")
for nota in dataModel.notas {
print (nota.titulo ?? "")
}
return cell
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataModel.notas.count
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
dataModel.notas.remove(at: indexPath.row)
self.tableView.deleteRows(at: [indexPath as IndexPath], with: UITableViewRowAnimation.fade)
dataModel.saveData()
}
EDIT:
func ordenadados(ordem: String){
dataModel.loadData()
if(ordenacao == "Titulo Asc."){
print("Titulo Asc")
dataModel.notas.sort { $0.titulo! < $1.titulo! }
}else if(ordenacao == "Titulo Desc."){
print("Titulo Desc.")
dataModel.notas.sort { $0.titulo! > $1.titulo! }
}
dataModel.saveData()
for nota in dataModel.notas {
print (nota.titulo ?? "")
}
dataModel.loadData()
tableView.reloadData()
}
In the output the array was sorted but in the TableView nothing changed.
Save and Load Data methods:
//save data
func saveData() {
let data = NSMutableData()
let archiver = NSKeyedArchiver(forWritingWith: data)
archiver.encode(notas, forKey: "teste")
archiver.finishEncoding()
data.write(toFile: dataFilePath(), atomically: true)
}
//read data
func loadData() {
let path = self.dataFilePath()
let defaultManager = FileManager()
if defaultManager.fileExists(atPath: path) {
let url = URL(fileURLWithPath: path)
let data = try! Data(contentsOf: url)
let unarchiver = NSKeyedUnarchiver(forReadingWith: data)
notas = unarchiver.decodeObject(forKey: "teste") as! Array
unarchiver.finishDecoding()
}
}
Related
I am trying to load the updated search results but it doesn't populate the table view.
I used this link https://www.thorntech.com/how-to-search-for-location-using-apples-mapkit/ which belongs to the previous versions but it still works very well except showing the local search results. Please help
class LocationSearchTable : UITableViewController, UISearchResultsUpdating {
var matchingItems:[MKMapItem] = []
var mapView: MKMapView? = nil
}
extension LocationSearchTable {
func updateSearchResults(for searchController: UISearchController) {
guard let MapView = mapView,
let searchBarText = searchController.searchBar.text else { return }
let request = MKLocalSearch.Request()
request.naturalLanguageQuery = searchBarText
request.region = MapView.region
let search = MKLocalSearch(request: request)
search.start { response, _ in
guard let response = response else {
print("No response")
return
}
self.matchingItems = response.mapItems
self.tableView.reloadData()
}
}
}
extension LocationSearchTable {
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return matchingItems.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")!
let selectedItem = matchingItems[indexPath.row].placemark
cell.textLabel?.text = selectedItem.name
cell.detailTextLabel?.text = ""
return cell
}
}
//use IndexPath rather than NSIndexPath and you need to use
//override
override func tableView(_ tableView: UITableView,
cellForRowAtIndexPath
indexPath: IndexPath) -> UITableViewCell {
let cell =tableView.dequeueReusableCell(withIdentifier:"cell")!
let selectedItem = matchingItems[indexPath.row].placemark
cell.textLabel?.text = selectedItem.name
cell.detailTextLabel?.text = ""
return cell
}
Hope it is not too late to answer you!
I am trying to reload the tableview after UIBarbutton is clicked to save a multi line text in UserDefaults.I want to update the tableview cell which is in another view controller after the button is clicked and load the cell but its not working for me.I tried to add an observer but its not working.The cell will show after reopening the app.What i am missing here?
#objc func saveTapped(){
guard let fact = savedFact else {
return
}
let deviceID = UIDevice.current.identifierForVendor!.uuidString
savedValues.append(fact)
if let arr = UserDefaults.standard.array(forKey: deviceID){
var arrvalues = arr as! [String]
if !arrvalues.contains(fact){
arrvalues.append(fact)
UserDefaults.standard.set(arrvalues, forKey:deviceID)
UserDefaults.standard.synchronize()
print (arr)
}
else
{
UserDefaults.standard.set(savedValues, forKey:deviceID)
}
}
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "load"), object: nil)
}
FavoritesViewController
class FavoritesViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
var savedList = [String]()
private func deleteFavorites(text:String){
let deviceID = UIDevice.current.identifierForVendor!.uuidString
if let arr = UserDefaults.standard.array(forKey: deviceID){
var arrvalues = arr as! [String]
if let index = arrvalues.firstIndex(of:text){
arrvalues.remove(at: index)
savedList.remove(at: index)
UserDefaults.standard.set(arrvalues, forKey:deviceID)
UserDefaults.standard.synchronize()
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
tableView.separatorStyle = .none
NotificationCenter.default.addObserver(self, selector: #selector(loadList), name: NSNotification.Name(rawValue: "load"), object: nil)
let deviceID = UIDevice.current.identifierForVendor!.uuidString
savedList = UserDefaults.standard.value(forKey: deviceID) as? [String] ?? []
tableView.register(UINib(nibName: "FavoritesTableViewCell", bundle: nil), forCellReuseIdentifier: FavoritesTableViewCell.identifier)
}
#objc func loadList(notification: NSNotification){
//load data here
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
extension FavoritesViewController {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return savedList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: FavoritesTableViewCell.identifier, for: indexPath) as! FavoritesTableViewCell
cell.cellBgImageView.layer.cornerRadius = 9
cell.factsLabel.text = savedList[indexPath.row]
cell.selectionStyle = .none
return cell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
deleteFavorites(text: savedList[indexPath.row])
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
}
replace
#objc func loadList(notification: NSNotification){
//load data here
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
as
#objc func loadList(notification: NSNotification){
//load data here
savedList = UserDefaults.standard.value(forKey: deviceID) as? [String] ?? []
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
I know a way, but I don't know if it's the best one.
I always suggest having a shared variable document. In that document, store a variable, which type is FavoritesViewController (in your case).
In FavoritesViewController viewDidLoad(), assign self to the shared variable.
Then, in each other document, subsequently, you can call each method of your FavoritesViewController
I know the issue is regarding scope; I just dont know if theres an easy fix I can do without changing my code much. but open to anything
import UIKit
import Firebase
import FirebaseAuth
import FirebaseFirestore
class AdminViewController: UIViewController, UITableViewDelegate,
UITableViewDataSource {
#IBOutlet var custodianRunReportsTableView: UITableView!
var dbRef: DatabaseReference!
var data = [String]()
override func viewDidLoad() {
super.viewDidLoad()
startObservingDB()
custodianRunReportsTableView.delegate = self
custodianRunReportsTableView.dataSource = self
// Do any additional setup after loading the view.
}
// Gets users' names from Cloud Firestore Database
func startObservingDB() {
let db = Firestore.firestore()
let namesDocumentRef = db.collection("Users").document("Names")
namesDocumentRef.addSnapshotListener { DocumentSnapshot, error in
guard let document = DocumentSnapshot else {
print("Error fetching document: \(error!)")
return
}
guard let data = document.data() else {
print("Document data was empty.")
return
}
let values = data.values
let rowNumber = data.count
print("Current data: \(data)")
print("Current data has the values: \(values)")
print("Current data totals \(data.count) items.")
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("You tapped me!")
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return rowNumber
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = custodianRunReportsTableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = values[indexPath.row]
print("Names in cell: \(values)")
print("\(data)")
return cell
}
}
UPDATED CODE:
This is updated after an answer to the original post. The code no longer has the unresolved identified error; however, the table view does not display any cell text and is empty.
class AdminViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var custodianRunReportsTableView: UITableView!
var valuesArray:[String] = []
var data:[String] = []
var namesDocumentRef:DocumentReference!
override func viewDidLoad() {
super.viewDidLoad()
startObservingDB()
custodianRunReportsTableView.delegate = self
custodianRunReportsTableView.dataSource = self
}
// Gets users' names from Cloud Firestore Database
func startObservingDB() {
var namesDocumentRef:DocumentReference!
let db = Firestore.firestore()
namesDocumentRef = db.collection("Users").document("Names")
namesDocumentRef.addSnapshotListener { DocumentSnapshot, error in
if error != nil{
return
}
else {
guard let snapshot = DocumentSnapshot, snapshot.exists else {return}
guard let data = snapshot.data() else { return }
self.valuesArray = Array(data.values) as! Array<String>
}
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("You tapped me!")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return valuesArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = custodianRunReportsTableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = valuesArray[indexPath.row]
return cell
}
}
The variables data,values and rowCount are inside closure so you can't just write values[indexPath.row] because you can't return from inside closure. Usually completionHandlers are used for this purpose, but in this scenario, you should put the values inside an array and then use inside the tableview. Let me show you how to do.
At the start of your viewController, declare a string array.
var valuesArray:[String] = []
Then, inside modify your startObservingDB() function
func startObservingDB() {
var docRef:DocumentReference!
let db = Firestore.firestore()
docRef = db.collection("Users").document("Names")
docRef.addSnapshotListener { (docSnapshot, error) in
if error != nil {
return
}
else {
guard let snapshot = docSnapshot, snapshot.exists else {return}
guard let data = snapshot.data() else { return }
self.valuesArray = Array(data.values) as! Array<String>
self.tableView.reloadData()
}
}
}
In viewDidLoad call this function
startObservingDB()
Then in tableView methods
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return ValuesArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = custodianRunReportsTableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = valuesArray[indexPath.row]
return cell
}
In your class AdminViewController you can reference properties defined outside of methods with self (in your example you have dbRef that you can reference like self.dbRef in methods).
So I suggest you make properties for data, value and rowNumber and change them in startObservingDB method rather than declare them. This way you will be able to reference them in tableView methods.
I'm creating an audio voice recorder and after each upload, the name appends an int to the recording (e.g. 1, 2, 3, etc.) in a table view (note: regular view controller with a UITableView vs. Table View Controller).
I'm having trouble deleting each row, and I'm not sure if it is because 'numberOfRecords.remove(at: indexPath.row)' only accepts strings.
I get the error: "Value of type 'Int' has no member 'remove.'"
class ViewController2: UIViewController, RecordButtonDelegate, AVAudioRecorderDelegate, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var myTableView: UITableView!
var numberOfRecords : Int = 0
// Setting up Table View
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return numberOfRecords
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = String(indexPath.row + 1)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let path = getDirectory().appendingPathComponent("\(indexPath.row + 1).m4a")
do {
audioPlayer = try AVAudioPlayer(contentsOf: path)
audioPlayer.play()
}
catch {
}
}
// Delete rows
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == UITableViewCellEditingStyle.delete{
numberOfRecords.remove(at: indexPath.row)
tableView.beginUpdates()
tableView.deleteRows(at: [indexPath], with: .automatic)
tableView.endUpdates()
}
}
// Audio Player
var audioPlayer : AVAudioPlayer!
var recordingSession : AVAudioSession!
var audioRecorder : AVAudioRecorder!
var recordButton: RecordButton?
#IBOutlet weak var buttonLabel2: RecordButton!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
buttonLabel2.delegate = self
}
func tapButton(isRecording: Bool) {
// Check if we have an active recorder
if audioRecorder == nil {
numberOfRecords += 1
let filename = getDirectory().appendingPathComponent("\(numberOfRecords).m4a")
let settings = [AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
AVSampleRateKey: 12000,
AVNumberOfChannelsKey: 1,
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue]
// Start audio recording
do {
audioRecorder = try AVAudioRecorder(url: filename, settings: settings)
audioRecorder.delegate = self
audioRecorder.record()
}
catch {
displayAlert(title: "Oops!", message: "Recording failed")
}
// Play speaker instead of earpiece
let audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.overrideOutputAudioPort(AVAudioSessionPortOverride.speaker)
} catch let error as NSError {
print("Audio Session error: \(error.localizedDescription)")
}
}
else {
// Stop audio recording
audioRecorder.stop()
audioRecorder = nil
UserDefaults.standard.set(numberOfRecords, forKey: "myNumber")
myTableView.reloadData()
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Setting up Recording session
recordingSession = AVAudioSession.sharedInstance()
if let number : Int = UserDefaults.standard.object(forKey: "myNumber") as? Int {
numberOfRecords = number
}
AVAudioSession.sharedInstance().requestRecordPermission { (hasPermission) in
if hasPermission {
print ("Accepted")
}
}
The problem that you're experiencing is due you calling remove(at:) on an Int. An Int has no function called remove(at:).
You're declaring var numberOfRecords: Int to track your indices, then in
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath)
you're calling
// this is the line that's causing your problem
numberOfRecords.remove(at: indexPath.row)
If you want to continue using an int to track your cells you should subtract from numberOfRecords
numberOfRecords-=1
Or you could track your records using an array something like this:
// declare an array of Strings to hold your filenames
var records: [String] = []
then where you're saving your files add the new filename to your array for the tableview
// Stop audio recording
audioRecorder.stop()
audioRecorder = nil
// add your filename to your array of records for the tableview
records.append(filename)
// update your total number of records if desired
UserDefaults.standard.set(numberOfRecords, forKey: "myNumber")
myTableView.reloadData()
Then your delegate functions might look something like this
// Setting up Table View
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// return the total count of filenames in your array
return records.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
// set the filename in your text label
cell.textLabel?.text = records[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// get the filename for this row
let filename = records[indexPath.row]
// use your filename in your path
let path = getDirectory().appendingPathComponent("\(filename)")
do {
audioPlayer = try AVAudioPlayer(contentsOf: path)
audioPlayer.play()
}
catch {
}
}
and you could update your remove(at:) call to run on the records array instead
records.remove(at: indexPath.row)
EDIT:
When you add or remove filenames from your records array update user defaults with your updated records:
// save array to user defaults when you create a new record or when you delete a record
UserDefaults.standard.setValue(records, forKey: "storedRecords")
To retrieve your saved array of filenames pull them from user defaults and update your records array with the stored names.
Replace these lines in your viewDidLoad function:
if let number : Int = UserDefaults.standard.object(forKey: "myNumber") as? Int {
numberOfRecords = number
}
with this:
// load stored records from user defaults and verify it's what you expect to receive
if let stored = UserDefaults.standard.value(forKey: "storedRecords") as? [String] {
// update your records array with the stored values
records = stored
}
I'm trying to delete the data from firebase with no luck so far. This is the code I'm using, can anyone give me a hand with it please.
class TableViewController: UITableViewController {
var ref: FIRDatabaseReference?
var grocery = [Grocery]()
override func viewDidLoad() {
super.viewDidLoad()
loadData()
}
func loadData() {
let uid = FIRAuth.auth()?.currentUser?.uid
FIRDatabase.database().reference().child("Users").child(uid!).child("Grocery").observe(.childAdded) { (snspshot: FIRDataSnapshot) in
if let dict = snspshot.value as? [String: Any] {
let Items = dict["Item"] as! String
let Quintities = dict["Quintities"] as! String
let Done = dict["Done"] as! Bool
let themBe = Grocery(Items: Items, Quintitiess: Quintities, Dones: Done)
self.grocery.append(themBe)
print(themBe)
self.tableView.reloadData()
}
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return grocery.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TasksTableViewCell") as! TasksTableViewCell
cell.titleLabel?.text = grocery[indexPath.row].Item
cell.numLabel?.text = grocery[indexPath.row].Quintities
return cell
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
self.grocery.remove(at: indexPath.row)
self.tableView.deleteRows(at: [indexPath], with: .automatic)
}
}
----------
import Foundation
class Grocery {
var Item: String
var Quintities: String
var Done: Bool
init(Items: String, Quintitiess: String, Dones: Bool) {
Item = Items
Quintities = Quintitiess
Done = Dones
}
}
You are only deleting data for your UITableView. The logic that you need is to delete from your UITableView and Fireabase Database. As the firebase docs says you can either call removeValue, or setValue to nil or updateChildValues.
To make the deletion easier, I'd save the key of the object where the data is saved (snapshot.keys), so when you want to delete you can just get that key and perform actions.