How to get the document ID from firebase? - ios

I have a table view and when I swipe and delete the table view cell, I need the data in Firebase to delete as well. For that to be possible, I need to have the document ID. How can I get that so I can delete the table view cell and the data in Firebase?
This is the first ViewController:
import UIKit
import FirebaseDatabase
import Firebase
import Firestore
class TableViewController: UITableViewController {
var db:Firestore!
var employeeArray = [employee]()
var employeeKey:String = ""
override func viewDidLoad() {
super.viewDidLoad()
db = Firestore.firestore()
loadData()
checkForUpdates()
}
func loadData() {
db.collection("employee").getDocuments() {
querySnapshot, error in
if let error = error {
print("\(error.localizedDescription)")
}else{
self.employeeArray = querySnapshot!.documents.compactMap({employee(id: $0.documentID, xdictionary: $0.data())})
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
func checkForUpdates() {
db.collection("employee").whereField("timeStamp", isGreaterThan: Date())
.addSnapshotListener {
querySnapshot, error in
guard let snapshots = querySnapshot else {return}
snapshots.documentChanges.forEach {
diff in
if diff.type == .added {
self.employeeArray = querySnapshot!.documents.compactMap({employee(id: $0.documentID, xdictionary: $0.data())})
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
}
func UID() {
self.db.collection("employee").getDocuments() { (snapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in snapshot!.documents {
if document == document {
print(document.documentID)
}
}
}
}
}
#IBAction func addEmployee(_ sender: Any) {
let composeAlert = UIAlertController(title: "Add Employee", message: "Add Employee", preferredStyle: .alert)
composeAlert.addTextField { (textField:UITextField) in
textField.placeholder = "Name"
}
composeAlert.addTextField { (textField:UITextField) in
textField.placeholder = "Adress"
}
composeAlert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
composeAlert.addAction(UIAlertAction(title: "Add Employee", style: .default, handler: { (action:UIAlertAction) in
if let name = composeAlert.textFields?.first?.text,
let adress = composeAlert.textFields?.last?.text {
let newEmployee = employee(name: name, adress: adress,timeStamp: Date())
var ref:DocumentReference? = nil
ref = self.db.collection("employee").addDocument(data: newEmployee.dictionary) {
error in
if let error = error {
print("Error adding document: \(error.localizedDescription)")
}else{
print("Document added with ID: \(ref!.documentID)")
}
}
}
}))
self.present(composeAlert, animated: true, completion: nil)
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return employeeArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let tweet1 = employeeArray[indexPath.row]
cell.textLabel?.text = "\(tweet1.name) \(tweet1.adress)"
cell .detailTextLabel?.text = "\(tweet1.timeStamp) "
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) async {
print(UID())
}
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if (editingStyle == UITableViewCell.EditingStyle.delete) {
db.collection("employee").document("\(UID())")
}
}
}
This is the second ViewController:
import UIKit
import FirebaseDatabase
import Firebase
import Firestore
protocol DocumentSeriziable {
init?(id: String, xdictionary:[String:Any])
}
struct employee {
var name: String!
var adress: String!
var timeStamp: Date
var dictionary:[String: Any] {
return[
"name":name!,
"adress":adress!,
"timeStamp":timeStamp,
]
}
}
extension employee : DocumentSeriziable {
init?(id: String, xdictionary dictionary: [String : Any]) {
guard let name = dictionary["name"] as? String,
let adress = dictionary["adress"] as? String,
let timeStamp = dictionary["timeStamp"] as? Date else {return nil}
self.init(name: name, adress: adress, timeStamp: timeStamp)
}
}
I've tried to get the Document Id a couple of different ways but none of them worked

Add in struct employee var documentID: String
And in self.init(documentID: id, name: name, adress: adress, timeStamp: timeStamp) and use
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if (editingStyle == UITableViewCell.EditingStyle.delete) {
db.collection("cities").document(employeeArray[indexPath.row].documentID).delete() { err in
if let err = err {
print("Error removing document: \(err)")
} else {
print("Document successfully removed!")
}
}
}
}

Related

Problem with saving data using Core Data in swift

I'm trying to save data to the core data and then display it on another view controller. I have a table view with custom cell, which have a button. I've created a selector, so when we tap on the button in each of the cell, it should save all the data from the cell. Here is my parent view controller:
import UIKit
import SafariServices
import CoreData
class ViewController: UIViewController, UISearchBarDelegate {
#IBOutlet weak var pecodeTableView: UITableView!
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var savedNews = [SavedNews]()
var newsTitle: String?
var newsAuthor: String?
var urlString: String?
var newsDate: String?
var isSaved: Bool = false
private var articles = [Article]()
private var viewModels = [NewsTableViewCellViewModel]()
private let searchVC = UISearchController(searchResultsController: nil)
override func viewDidLoad() {
super.viewDidLoad()
pecodeTableView.delegate = self
pecodeTableView.dataSource = self
pecodeTableView.register(UINib(nibName: S.CustomCell.customNewsCell, bundle: nil), forCellReuseIdentifier: S.CustomCell.customCellIdentifier)
fetchAllNews()
createSearchBar()
loadNews()
saveNews()
countNewsToCategory()
}
#IBAction func goToFavouritesNews(_ sender: UIButton) {
performSegue(withIdentifier: S.Segues.goToFav, sender: self)
}
private func fetchAllNews() {
APICaller.shared.getAllStories { [weak self] result in
switch result {
case .success(let articles):
self?.articles = articles
self?.viewModels = articles.compactMap({
NewsTableViewCellViewModel(author: $0.author ?? "Unknown", title: $0.title, subtitle: $0.description ?? "No description", imageURL: URL(string: $0.urlToImage ?? "")
)
})
DispatchQueue.main.async {
self?.pecodeTableView.reloadData()
}
case .failure(let error):
print(error)
}
}
}
private func createSearchBar() {
navigationItem.searchController = searchVC
searchVC.searchBar.delegate = self
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 120
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModels.count
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: S.CustomCell.customCellIdentifier, for: indexPath) as! CustomNewsCell
cell.configure(with: viewModels[indexPath.row])
cell.saveNewsBtn.tag = indexPath.row
cell.saveNewsBtn.addTarget(self, action: #selector(didTapCellButton(sender:)), for: .touchUpInside)
return cell
}
#objc func didTapCellButton(sender: UIButton) {
guard viewModels.indices.contains(sender.tag) else { return }
print("Done")// check element exist in tableview datasource
if !isSaved {
saveNews()
print("success")
}
//Configure selected button or update model
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let article = articles[indexPath.row]
guard let url = URL(string: article.url ?? "") else {
return
}
let vc = SFSafariViewController(url: url)
present(vc, animated: true)
}
//Search
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
guard let text = searchBar.text, !text.isEmpty else {
return
}
APICaller.shared.Search(with: text) { [weak self] result in
switch result {
case .success(let articles):
self?.articles = articles
self?.viewModels = articles.compactMap({
NewsTableViewCellViewModel(author: $0.author ?? "Unknown", title: $0.title, subtitle: $0.description ?? "No description", imageURL: URL(string: $0.urlToImage ?? "")
)
})
DispatchQueue.main.async {
self?.pecodeTableView.reloadData()
self?.searchVC.dismiss(animated: true, completion: nil)
}
case .failure(let error):
print(error)
}
}
}
}
extension ViewController {
func loadNews() {
let request: NSFetchRequest<SavedNews> = SavedNews.fetchRequest()
do {
let savedNews = try context.fetch(request)
//Handle saved news
if savedNews.count > 0 {
isSaved = true
}
} catch {
print("Error fetching data from context \(error)")
}
}
func saveNews() {
//Initialize the context
let news = SavedNews(context: self.context)
//Putting data
news.title = newsTitle
news.author = newsAuthor
news.publishedAt = newsDate
news.url = urlString
do {
try context.save()
} catch {
print("Error when saving data \(error)")
}
}
func countNewsToCategory() {
//Initialize the context
let request: NSFetchRequest<SavedNews> = SavedNews.fetchRequest()
let predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
])
request.predicate = predicate
do {
savedNews = try context.fetch(request)
} catch {
print("Error fetching data from category \(error)")
}
}
}
I don't know where is the problem, I've created a correct data model, but data could not be saved. Here is my model:
import Foundation
struct APIResponse: Codable {
let articles: [Article]
}
struct Article: Codable {
let author: String?
let source: Source
let title: String
let description: String?
let url: String?
let urlToImage: String?
let publishedAt: String
}
struct Source: Codable {
let name: String
}
And also my model in Core Data:
My second view controller, to which I want display the data:
import UIKit
import CoreData
class FavouriteNewsViewController: UIViewController {
#IBOutlet weak var favTableView: UITableView!
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var savedNews = [SavedNews]()
override func viewDidLoad() {
super.viewDidLoad()
favTableView.delegate = self
favTableView.delegate = self
loadSavedNews()
favTableView.register(UINib(nibName: S.FavouriteCell.favouriteCell, bundle: nil), forCellReuseIdentifier: S.FavouriteCell.favouriteCellIdentifier)
// Do any additional setup after loading the view.
}
}
extension FavouriteNewsViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return savedNews.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = favTableView.dequeueReusableCell(withIdentifier: S.FavouriteCell.favouriteCellIdentifier, for: indexPath) as! FavouritesCell
print(savedNews)
let article = savedNews[indexPath.row]
if let articleTitle = article.title {
cell.favTitle.text = articleTitle
}
if let articleAuthor = article.author {
cell.favAuthor.text = articleAuthor
}
if let articleDesc = article.desc {
cell.favDesc.text = article.desc
}
return cell
}
}
extension FavouriteNewsViewController {
func loadSavedNews() {
let request: NSFetchRequest<SavedNews> = SavedNews.fetchRequest()
do {
savedNews = try context.fetch(request)
} catch {
print("Error fetching data from context \(error)")
}
}
func deleteNews(at indexPath: IndexPath) {
// Delete From NSObject
context.delete(savedNews[indexPath.row])
// Delete From current News list
savedNews.remove(at: indexPath.row)
// Save deletion
do {
try context.save()
} catch {
print("Error when saving data \(error)")
}
}
}
you did not assign your properties that you are trying to save
newsTitle,newsAuthor,newsDate,urlString
seems these properties have nil value . make sure these properties have valid value before save .

duplication using headers section in tableview using Swift

well I'm trying to add date to my header sections every time the user is done with his task. the problem is im kinda new with headers section and for some reason every time I add more than two tasks into the "Done" section, it seems like it duplicates itself.
have looked around stackOverFlow and couldn't find what I needed, and I hope u guys can help me up :)
Here's a pic of the app with the problem: https://imgur.com/wjVy9Uy
Here's my code:
import Foundation
import CoreData
import UIKit
import SwipeCellKit
protocol TaskDelegate {
func updateTaskName(name:String)
}
class TasksManViewController: UITableViewController, SwipeTableViewCellDelegate {
#IBOutlet weak var Sege: UISegmentedControl!
var tasksArray = [Task](){
didSet {
// because we perform this operation on the main thread, it is safe
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
let isSwipeRightEnabled = true
var delegate: TaskDelegate?
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
override func viewDidLoad() {
}
override func viewWillAppear(_ animated: Bool) {
loadTasks()
}
// MARK: - DataSource + Delegate Methods:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tasksArray.count
}
override func numberOfSections(in tableView: UITableView) -> Int {
return tasksArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "taskCellRow") as! SwipeTableViewCell
cell.delegate = self
cell.textLabel?.text = tasksArray[indexPath.row].title
return cell
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> [SwipeAction]? {
if orientation == .left {
guard isSwipeRightEnabled else { return nil }
let doneAction = SwipeAction(style: .destructive, title: "Done") { (action, indexPath) in
//STEP1: Append the task to the doneTasksArr:
self.tasksArray[indexPath.row].isDone = true
//STEP3: Remove the Row:
self.tasksArray.remove(at: indexPath.row)
//STEP4: Update the Model:
self.saveTasks()
self.delegate?.updateTaskName(name: "")
self.tableView.reloadData()
}
let unDoneAction = SwipeAction(style: .destructive, title: "Undone") { (unDoneAction, indexPath) in
self.tasksArray[indexPath.row].isDone = false
self.tasksArray.remove(at: indexPath.row)
self.saveTasks()
}
//configure btn:
doneAction.backgroundColor = .cyan
unDoneAction.backgroundColor = .blue
if Sege.selectedSegmentIndex == 0 { //Doing this to allow the user to unDone tasks.
return [doneAction]
} else { return [unDoneAction] }
} else {
let deleteAction = SwipeAction(style: .destructive, title: "Delete") { action, indexPath in
self.context.delete(self.tasksArray[indexPath.row])
self.tasksArray.remove(at: indexPath.row)
self.saveTasks()
self.delegate?.updateTaskName(name: "")
self.tableView.reloadData()
}
return [deleteAction]
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
// MARK: - Class Methods:
#IBAction func addBtnTapped(_ sender: UIBarButtonItem) {
Sege.selectedSegmentIndex = 0 // Doing this to avoid the user to insert a task into the DoneTasks by mistake! and avoiad a bug :)
insertNewTask()
}
func insertNewTask() {
var textField = UITextField()
let alert = UIAlertController(title: "New Task", message: "Please Add Your Task", preferredStyle: .alert)
alert.addTextField { (alertTextField) in
alertTextField.placeholder = "Create New Task"
textField = alertTextField
}
let action = UIAlertAction(title: "Add", style: .default) { (action) in
let newItem = Task(context: self.context)
newItem.title = textField.text!
newItem.isDone = false
newItem.date = Date()
self.tasksArray.append(newItem)
self.delegate?.updateTaskName(name: newItem.title!)
self.saveTasks()
}
alert.addAction(action)
self.present(alert, animated: true, completion: nil)
}
// MARK: - Sege Section:
#IBAction func segeControlTapped(_ sender: UISegmentedControl) {
switch Sege.selectedSegmentIndex
{
case 0:
//Loading normal tasks whose are not done
loadTasks()
case 1:
//Loading the doneTasks:
loadDoneTasksFrom()
default:
print("There's something wrong with Sege!")
}
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let completedTasksToDisply = 0
if Sege.selectedSegmentIndex == 1 {
if let firstTask = tasksArray[section].date {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd/MM/yyyy"
let dateString = dateFormatter.string(from: firstTask)
return " " + dateString + " " + " " + "Completed Tasks: \(completedTasksToDisply)"
}
}
return ""
}
override func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
let header: UITableViewHeaderFooterView = view as! UITableViewHeaderFooterView
header.textLabel?.font = UIFont(name: "Verdana", size: 13.0)
header.textLabel?.textAlignment = NSTextAlignment.center
}
//MARK: - Model Manipulation Methods:
func saveTasks() {
do {
try! context.save()
} catch let err {
print("Error Saving context \(err)")
}
}
func loadTasks() {
let request: NSFetchRequest<Task> = Task.fetchRequest()
request.predicate = NSPredicate(format: "isDone == %#", NSNumber(value: false))
request.sortDescriptors = [NSSortDescriptor(key: "isDone", ascending: false)]
do{
tasksArray = try! context.fetch(request)
} catch {
print("There was an error with loading items \(error)")
}
tableView.reloadData()
}
func loadDoneTasksFrom() {
let request:NSFetchRequest<Task> = Task.fetchRequest()
request.predicate = NSPredicate(format: "isDone == %#", NSNumber(value: true))
request.sortDescriptors = [NSSortDescriptor(key: "isDone", ascending: false)]
do{
tasksArray = try context.fetch(request)
} catch {
print("Error fetching data from context\(error)")
}
tableView.reloadData()
}
}
EDIT: Okay I see what you are trying to do. Give this a shot - we will need a double array - each array inside will have to be split by date - our query would look something like this:
var tasksArray = [[Task]]()
var sectionDates = [String]()
func loadTasks() {
tasksArray.removeAll(keepingCapacity: false)
sectionDates.removeAll(keepingCapacity: false)
let request: NSFetchRequest<Task> = Task.fetchRequest()
request.predicate = NSPredicate(format: "isDone == %#", NSNumber(value: false))
request.sortDescriptors = [NSSortDescriptor(key: "isDone", ascending: false)]
do{
let tasks = try! context.fetch(request)
for task in tasks {
if let taskDate = task.date {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd/MM/yyyy"
let dateString = dateFormatter.string(from: taskDate)
if self.sectionDates.contains(dateString) {
if let section = self.sectionDates.firstIndex(of: dateString) {
self.tasksArray[section].append(task)
}
} else {
self.sectionDates.append(dateString)
// EDIT - ADD THIS LINE (and self. where necessary)
self.tasksArray.append([])
if let section = self.sectionDates.firstIndex(of: dateString) {
self.tasksArray[section].append(task)
}
}
}
}
} catch {
print("There was an error with loading items \(error)")
}
tableView.reloadData()
}
Could also be helpful to load your objects in order but with this set up it should still group everything correctly regardless of the order - unless you want your sections in a specific order then you can specify in query
Now changes to your tableView delegate methods
override func numberOfSections(in tableView: UITableView) -> Int {
return sectionDates.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tasksArray[section].count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "taskCellRow") as! SwipeTableViewCell
cell.delegate = self
cell.textLabel?.text = tasksArray[indexPath.section][indexPath.row].title
return cell
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let completedTasksToDisply = 0
if Sege.selectedSegmentIndex == 1 {
let dateString = sectionDates[section]
return " " + dateString + " " + " " + "Completed Tasks: \(completedTasksToDisply)"
}
return ""
}
I figured I'd leave the rest of the tableView delegate methods to you but for the most part we just need to identify the section before the indexPath row when working with the double array - as we did in cellForRow
I ran this code and this was the result:

Modifying Realm data crashes on tableView.reloadData()

You get one thing figured out and something else comes along and that's why I love this stuff.
My workout app uses realm for data persistence and when I modify some of the data elements of the file, it crashes when I reload the tableView. Here is a layout of the tableview in WorkoutViewController.
I select an item to edit it's three properties (Name, Date and average time). When selecting Edit the following view controller is presented. This is WorkoutAlertViewController.
Here is the relevant code:
class WorkoutViewController: UIViewController {
let realm = try! Realm()
var workoutItems: Results<Workout>?
var passedWorkout = Workout()
#IBOutlet weak var tableView: UITableView!
func editWorkout(workout: Workout, name: String, time: String, date: Date) {
do {
try self.realm.write {
passedWorkout = workout
print("passedWorkout \(workout)")
print("changes to be made \(name), \(time), \(date)")
passedWorkout.name = name
passedWorkout.time = time
passedWorkout.dateCreated = date
print("workout changed \(passedWorkout)")
}
} catch {
print("Error editing workout \(error)")
}
// loadWorkout()
}
func loadWorkout() {
workoutItems = realm.objects(Workout.self).sorted(byKeyPath: "name", ascending: false)
print("workoutItems \(String(describing: workoutItems))")
tableView.reloadData()
}
}
extension WorkoutViewController: PassNewWorkout {
func didTapAdd(name: String, time: String, date: Date) {
workoutName = name
averageTime = time
creationDate = date
let newWorkout = Workout()
newWorkout.name = workoutName
newWorkout.dateCreated = creationDate
newWorkout.time = averageTime
saveWorkout(workout: newWorkout)
}
}
extension WorkoutViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return workoutItems?.count ?? 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! WorkoutCell
if let workout = workoutItems?[indexPath.row] {
cell.workoutLabel.text = workout.name
cell.dateLabel.text = String("Created: \(functions.convertTime(timeToConvert: workout.dateCreated!))")
cell.timeLabel.text = String("Avg Time: \(workout.time)")
} else {
cell.textLabel?.text = "No Workouts Added"
}
return cell
}
}
extension WorkoutViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "workoutListSegue", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "workoutListSegue" {
if let destinationVC = segue.destination as? WorkoutListViewController, let indexPath = tableView.indexPathForSelectedRow {
if let workout = workoutItems?[indexPath.row] {
destinationVC.selectedWorkout = workout
destinationVC.workoutName = workout.name
}
}
}
}
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let deleteAction = UIContextualAction(style: .destructive, title: "Delete") { (action, view, actionPerformed) in
if let _ = tableView.cellForRow(at: indexPath){
self.passedWorkout = self.workoutItems![indexPath.row]
self.deleteWorkout(workout: self.passedWorkout)
}
tableView.reloadData()
}
deleteAction.backgroundColor = .red
let editAction = UIContextualAction(style: .normal, title: "Edit") { (action, view, actionPerformed) in
if let _ = tableView.cellForRow(at: indexPath){
let alertVC = self.storyboard!.instantiateViewController(identifier: "WorkoutVC") as! WorkoutAlertViewController
self.passedWorkout = self.workoutItems![indexPath.row]
print("editAction \(self.passedWorkout)")
alertVC.didTapEdit(workout: self.passedWorkout)
self.present(alertVC, animated: true, completion: nil)
}
}
editAction.backgroundColor = .gray
return UISwipeActionsConfiguration(actions: [deleteAction, editAction])
}
}
The app crashes on loadWorkout() with:
Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value: file /Users/kevin/Desktop/FitTrax/FitTrax/Controller/Fitness/WorkoutViewController.swift, line 88
Line 88 is tableView.reloadData()
Here is the WorkoutAlertViewController file.
protocol PassNewWorkout {
func didTapAdd(name: String, time: String, date: Date)
}
class WorkoutAlertViewController: UIViewController {
var workoutName = String()
var averageTime = String()
var creationDate = Date()
var receivedWorkout = Workout()
#IBAction func addButtonPressed(_ sender: UIButton) {
dismiss(animated: true, completion: nil)
if workoutName == "" {
workoutName = workoutTextField.text!
creationDate = datePicker.date
averageTime = timeTextField.text!
alertDelegate.didTapAdd(name: workoutName, time: averageTime, date: creationDate)
} else {
workoutName = workoutTextField.text!
creationDate = datePicker.date
averageTime = timeTextField.text!
let workoutVC = storyboard!.instantiateViewController(withIdentifier: "Main") as! WorkoutViewController
workoutVC.editWorkout(workout: receivedWorkout, name: workoutName, time: averageTime, date: creationDate)
}
}
func didTapEdit(workout: Workout) {
workoutName = workout.name
averageTime = workout.time
creationDate = workout.dateCreated!
receivedWorkout = workout
}
}
The edit does work without the reload but does not immediately show the changes. If I go back to the Fitness Menu and back to the Workout Menu, it displays the changes then. It's something with that reload that I can't figure out.
Thank you for any suggestions.

Completion handler issues

So I have a tableview controller which has headers and cells which are supposed to be present under those headers. I have already done the job of correctly creating the headers. I currently have prepared the datasource for the cells which takes its content from the a list of currentEvents that a user is currently attending. The issue isn't the pulling of the data it seems to be the completion block. It takes so long to come back that the currentEvents array never gets appended when it is supposed to. It also keeps appending events onto the original array even after it should reset upon entering the new section. I have tried many things and moved code around many places but nothing seems to be having any effect
The key function in all this is
self.fetchEventsFromServer()
specifically the part where the EventService.show takes place
import UIKit
import Firebase
class FriendsEventsView: UITableViewController{
var cellID = "cellID"
var friends = [Friend]()
var attendingEvents = [Event]()
//label that will be displayed if there are no events
var currentUserName: String?
var currentUserPic: String?
var currentEventKey: String?
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Friends Events"
view.backgroundColor = .white
// Auto resizing the height of the cell
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableViewAutomaticDimension
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: #imageLiteral(resourceName: "close_black").withRenderingMode(.alwaysOriginal), style: .done, target: self, action: #selector(self.goBack))
tableView.register(EventDetailsCell.self, forCellReuseIdentifier: cellID)
self.tableView.tableFooterView = UIView(frame: CGRect.zero)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
DispatchQueue.global(qos: .background).async {
print("This is run on the background queue")
self.fetchEventsFromServer { (error) in
if error != nil {
print(error)
return
} else {
DispatchQueue.main.async {
self.tableView.reloadData()
print("This is run on the main queue, after the previous code in outer block")
}
}
}
}
}
#objc func goBack(){
dismiss(animated: true)
}
override func numberOfSections(in tableView: UITableView) -> Int {
// print(friends.count)
return friends.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// print(friends[section].events.count)
return friends[section].collapsed ? 0 : friends[section].events.count
}
func tableView(_ tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellID) as! EventDetailsCell? ?? EventDetailsCell(style: .default, reuseIdentifier: cellID)
// print(indexPath.row)
cell.details = friends[indexPath.section].events[indexPath.row]
return cell
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: "header") as? CollapsibleTableViewHeader ?? CollapsibleTableViewHeader(reuseIdentifier: "header")
// print(section)
header.arrowLabel.text = ">"
header.setCollapsed(friends[section].collapsed)
print(friends[section].collapsed)
header.section = section
// header.delegate = self
header.friendDetails = friends[section]
return header
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 50
}
func fetchEventsFromServer(_ completion: #escaping (_ error: Error?) -> Void ){
//will grab the uid of the current user
guard let myUserId = Auth.auth().currentUser?.uid else {
return
}
let ref = Database.database().reference()
//checking database for users that the current user is following
ref.child("following").child(myUserId).observeSingleEvent(of: .value, with: { (followingSnapshot) in
//handling potentail nil or error cases
guard let following = followingSnapshot.children.allObjects as? [DataSnapshot]
else {return}
//validating if proper data was pulled
let group = DispatchGroup()
for followingId in following {
group.enter()
print(followingId.key)
ref.child("users").child(followingId.key).observeSingleEvent(of: .value, with: { (userInfoSnapShot) in
guard let followingUserInfo = userInfoSnapShot.children.allObjects as? [DataSnapshot] else {
return
}
//validating if proper data was pulled for each follower
for currentUserInfo in followingUserInfo {
//will add this back when I want to event implementation
if currentUserInfo.key == "Attending" {
guard let eventKeys = currentUserInfo.children.allObjects as? [DataSnapshot] else{return}
for event in eventKeys {
print(event.key)
EventService.show(forEventKey: event.key, completion: { (event) in
guard let currentEvent = event else{
return
}
self.attendingEvents.append(currentEvent)
print(self.attendingEvents.count)
})
}
}
if currentUserInfo.key == "profilePic"{
self.currentUserPic = currentUserInfo.value as! String
//print(self.currentUserPic)
}
if currentUserInfo.key == "username"{
self.currentUserName = currentUserInfo.value as! String
//print(self.currentUserName)
var friend = Friend(friendName: self.currentUserName!, events: self.attendingEvents, imageUrl: self.currentUserPic!)
print(friend.events.count)
self.friends.append(friend)
//print(self.friends.count)
}
}
group.leave()
let result = group.wait(timeout: .now() + 0.01)
completion(nil)
}, withCancel: { (err) in
completion(err)
print("Couldn't grab info for the current list of users: \(err)")
})
}
completion(nil)
}) { (err) in
completion(err)
print("Couldn't grab people that you are currently following: \(err)")
}
}
}
If anyone sees something I don't please dont hesitate to say something
How would I alter my dispatch group implementation to accomplish this goal
EventService Function
struct EventService {
static func show(forEventKey eventKey: String, completion: #escaping (Event?) -> Void) {
// print(eventKey)
let ref = Database.database().reference().child("events").child(eventKey)
// print(eventKey)
//pull everything
ref.observeSingleEvent(of: .value, andPreviousSiblingKeyWith: { (snapshot,eventKey) in
// print(snapshot.value ?? "")
guard let event = Event(snapshot: snapshot) else {
return completion(nil)
}
completion(event)
})
}
}

Core Data not fetching in table view

My code below is using core data to add entities to a tableview. The problem is that when I hit the button to add a new entry the data is not displayed on the tableview. But if I quit the simulator and re install the app the entity is there. To get the entities displayed I have to enter it then reinstall the app to get the entity displayed.
import UIKit
import CoreData
class ViewController: UIViewController {
#IBOutlet var rot: UITableView!
#IBOutlet var enterName: UITextField!
var people : [NSManagedObject] = []
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
showdata()
}
#IBAction func iDontLikeSchool(_ sender: Any) {
if(enterName.text?.isEmpty)! {
alertmsg(name: "d")
showdata()
} else {
saveData(name: enterName.text!)
}}
func alertmsg(name:String){
let alert = UIAlertController(title: "D", message: "d", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "ok", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
func saveData(name: String) {
guard let appDelage = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelage.persistentContainer.viewContext
let ee = NSEntityDescription.entity(forEntityName: "PersonalInfo", in: managedContext)!
let person = NSManagedObject(entity : ee , insertInto: managedContext)
person.setValue(name, forKey: "userName")
do {
try managedContext.save()
people.append(person)
alertmsg(name: "saved")
enterName.text = ""
}
catch let err as NSError {
print("judo",err)
}
///
}
func showdata() {
guard let appDelage = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelage.persistentContainer.viewContext
let fetchReuqst = NSFetchRequest<NSManagedObject>(entityName: "PersonalInfo")
do {
people = try managedContext.fetch(fetchReuqst)
if people.count == 0 {
} else {
}}
catch let err as NSError {
print("judo", err)
}
///
}
}
extension ViewController: UITableViewDataSource, UITableViewDelegate{
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = rot.dequeueReusableCell(withIdentifier: "Person")
let personn = people[indexPath.row]
cell?.textLabel?.text = personn.value(forKey: "userName") as? String
return cell!
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return people.count
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
}
The reason why the entry doesn't show up is that you don't tell the view that your model has changed so it doesn't refresh itself.
The tableView gets rendered once on viewDidLoad that's why the data is displayed correctly after the view is loaded freshly (after your app restart).
To solve this issue, keep your viewController's tableView as a property (#IBOutlet if you use the Storyboard) and call
tableView.reloadData()
after you changed your data, in your case e.g. at the end of your func showData() like this.
func showdata() {
guard let appDelage = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelage.persistentContainer.viewContext
let fetchReuqst = NSFetchRequest<NSManagedObject>(entityName: "PersonalInfo")
do {
people = try managedContext.fetch(fetchReuqst)
if people.count == 0 {
//sth
} else {
// sth
}
} catch let err as NSError {
print("judo", err)
}
tableView.reloadData()
}

Resources