I am new to Swift development, so sorry if this is a stupid question. I'm having issues with saving the Firestore document ID to the cell of my to do.
My goal:
Save the document ID of the to-do so it can be used in my ChangeButton protocol.
The app is a to-do list-style app. The changeButton refers to changing the button from an empty circle to a filled circle.
My cellForRowAt in my mainViewController:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 && indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "inputCell", for: indexPath) as! InputCell
cell.delegate = self
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "taskCell", for: indexPath) as! TaskCell
let current = sections[indexPath.section].items[indexPath.row]
cell.taskNameLabel.text = current.name
if current.checked {
cell.checkBoxOutlet.setBackgroundImage(#imageLiteral(resourceName: "checkBoxFILLED "), for: UIControl.State.normal)
} else {
cell.checkBoxOutlet.setBackgroundImage(#imageLiteral(resourceName: "checkBoxOUTLINE "), for: UIControl.State.normal)
}
cell.delegate = self
cell.items = sections[indexPath.section].items
cell.indexSection = indexPath.section
cell.indexRow = indexPath.row
cell.itemID = sections[indexPath.section].items[indexPath.row].itemID
// print("cell.itemID is \(cell.itemID)")
// print("sections.itemID is \(sections[indexPath.section].items[indexPath.row].itemID)")
return cell
}
}
My changeButton function in mainViewController
func changeButton(state: Bool, indexSection: Int?, indexRow: Int?, itemID: String?) {
print("The item ID is \(itemID)")
print("The item ID section is \(sections[indexSection!].items[indexRow!].itemID)")
sections[indexSection!].items[indexRow!].checked = state
print("Line 175 ID is \(itemID)")
if let itemID = itemID {
let itemRef = db.collection(K.FStore.lists).document(currentListID!).collection(K.FStore.sections).document("\(indexSection!)").collection(K.FStore.items).document(itemID)
if sections[indexSection!].items[indexRow!].checked {
itemRef.updateData([
K.FStore.isChecked : true,
K.FStore.checkedBy: currentUserID!
]) { err in
if let err = err {
print("Error writing document: \(err)")
} else {
print("Document successfully written!")
}
}
} else {
itemRef.updateData([
K.FStore.isChecked : false
]) { err in
if let err = err {
print("Error writing document: \(err)")
} else {
print("Document successfully written!")
}
}
}
} else {
print("No item ID")
}
tableView.reloadData()
}
My loadItems and loadFunctions in my mainViewController
func loadItems(listID: String, section: Int) {
let itemRef = db.collection(K.FStore.lists).document(listID).collection(K.FStore.sections).document("(section)").collection(K.FStore.items)
var itemArray = Task
itemRef.getDocuments() { (querySnapshot, error) in
if let error = error {
print("Error getting documents: \(error)")
} else {
for document in querySnapshot!.documents {
let name = document.data()["name"] as? String
let isChecked : Bool = (document.data()["isChecked"] != nil)
let newItem = Task(name: name ?? "FIREBASE ERROR", isChecked: isChecked)
itemArray.append(newItem)
// print(newItem.checked)
}
}
// print(itemArray)
self.sections[section].items = itemArray
self.tableView.reloadData()
}
}
//MARK: - Load sections
func loadSections(listID: String) {
let listRef = db.collection(K.FStore.lists).document(listID)
listRef.getDocument { (document, error) in
if let document = document, document.exists {
let dataDescription = document.data().map(String.init(describing:)) ?? "nil"
let sectionNames = document.data()!["sections"] as? [String]
if let sectionNames = sectionNames {
for (index, item) in sectionNames.enumerated() {
let newSection = Section(name: item, isExpanded: true, items: [])
self.sections.append(newSection)
self.loadItems(listID: listID, section: index)
}
}
self.tableView.reloadData()
} else {
print("Document does not exist")
}
}
}
My Task class
class Task {
var name = ""
var checked = false
var date = Date()
var category: String
var number: Int
var itemID: String?
My TaskCell
protocol ChangeButton {
func changeButton(state: Bool, indexSection: Int?, indexRow: Int?, itemID: String?)
}
class TaskCell: UITableViewCell {
#IBAction func checkBoxAction(_ sender: Any) {
// print("The item ID is \(itemID)")
if items![indexRow!].checked {
delegate?.changeButton(state: false, indexSection: indexSection!, indexRow: indexRow!, itemID: itemID)
print("Line 22 \(itemID)")
} else {
delegate?.changeButton(state: true, indexSection: indexSection!, indexRow: indexRow!, itemID: itemID)
print("Line 25 \(itemID)")
}
}
#IBOutlet weak var taskNameLabel: UILabel!
#IBOutlet weak var checkBoxOutlet: UIButton!
var delegate: ChangeButton?
var indexSection: Int?
var indexRow: Int?
var tasks: [[Task]]?
var items: [Task]?
var itemID: String?
}
I am completely lost in how I can fix this. As you can see, I've tried a lot of print statements to figure out where the itemID can be loaded.
This is what I get back from those print statements:
Line 25 nil
The item ID is nil
The item ID section is nil
Line 175 ID is nil
No item ID
Line 22 nil
Please let me know if I forgot to include anything, and sorry for this extremely long post.
Thanks a ton,
Matt
Firstly,
We don't trust cells because it's reusable so we need a static class.
When cell will be reuse you can lost your data
I create some examples. Maybe it'll be helpful
// This is our model
class Task {
var name = ""
var checked = false
var date = Date()
var category: String
var number: Int
var itemID: String?
public init() {
self.category = ""
self.number = 0
}
}
// Extension for init from firebase response
extension Task {
convenience init(with firebase: [String: Any]) {
self.init()
self.name = (firebase["name"] as? String) ?? ""
}
}
// We create service for document
// We use this service like an API
final class DocumentService {
static let shared = DocumentService()
private let database: FirebaseDatabase
private var tasks: [[Task]] = []
public init(database: FirebaseDatabase = FirebaseDatabase()) {
self.database = database
}
func load(in section: Int, completion: #escaping (([Task]) -> Void)) {
database.loadData(section: section) { [unowned self] tasks in
self.tasks[section] = tasks.map(Task.init)
completion(self.tasks[section])
}
}
func check(at indexPath: IndexPath, isChecked: Bool) {
tasks[indexPath.section][indexPath.row].checked = isChecked
}
}
// We create firebase database class we can add some features in here
final class FirebaseDatabase {
func loadData(section: Int, completion: #escaping (([[String: Any]]) -> Void)) {
// TODO: firebase load data
let response: [[String: Any]] = [
["name": "Stackoverflow"]
]
completion(response)
}
}
final class TestController: UIViewController {
private let service = DocumentService.shared
override func viewDidLoad() {
super.viewDidLoad()
service.load(in: 0) { tasks in
// TODO
}
}
}
Thank you for your answer, #Vicaren. Fortunately, the solution was more simple than that. In the end, I found that I forgot to pass in the itemID argument in the loadItems() function. Thank you.
Related
I have a problem with passing the data
To make it clear, I will explain the idea of what I worked on, and what is the problem.
The idea is in the first view controller the user will enter the title and description and then chooses from the options of the pop-up button, the options are (exchange, borrow, donation, sell). The data entered will be saved in the option chosen by the user. then the data will be displayed in the second view controller in the table view. If the user chooses the exchange option and enters the data, his data will be displayed in the table view in the exchange (index 0) and this works for me the data is displayed in the table view in the correct form as I want.
The problem I am experiencing is when I pass the data to the other view controller.
When the user clicks on any cell, it will pass the same data regardless of the difference in the index. If the user chooses the borrow (index 1) and clicks any cell, it'll display the exchange (index 0) data. No matter what indexes you choose and the cell you click on it will pass the same data!!!!!
first view controller
here I'm entering the data
it's shown in the table view in the right index of the segment no problem with that
after I click it pass the right data
look here if I change the index and click to any cell it will pass the same data!!
look here if I change the index and click to any cell it will pass the same data!!
Here's my code for the first vc
import UIKit
import FirebaseFirestore
class ViewController4: UIViewController {
#IBOutlet weak var mssglabel: UILabel!
#IBOutlet weak var selectservice: UIButton!
#IBOutlet weak var titleTextField: UITextField!
#IBOutlet weak var descriptionTextField: UITextView!
#IBOutlet weak var custombtun: UIButton!
let db = Firestore.firestore()
var chooseOption = ""
override func viewDidLoad() {
super.viewDidLoad()
setpopupbutn()
selectservice.layer.cornerRadius = 25
descriptionTextField.layer.cornerRadius = 25
custombtun.layer.cornerRadius = 25
}
#IBAction func containbutn(_ sender: Any) {
let vc = (storyboard?.instantiateViewController(withIdentifier: "vc3"))!
navigationController?.pushViewController(vc, animated: true)
spcificOption()
}
func saveDataDonation() {
if let description = descriptionTextField.text,
let tittle = titleTextField.text{
// Save Data to Database
db.collection("userDonationDatabase")
.addDocument(data: [
"description" : description,
"BookTitle": tittle ]) {
(error) in
if let err = error {
print(err.localizedDescription)
}else {
print("تم حفظ البيانات بنجاح")
print(description)
print(tittle)
}
} // end of closure
}
}
func saveDataSale() {
if let description = descriptionTextField.text,
let tittle = titleTextField.text{
// Save Data to Database
db.collection("userSaleDatabase")
.addDocument(data: [
"description" : description,
"BookTitle": tittle ]) {
(error) in
if let err = error {
print(err.localizedDescription)
}else {
print("تم حفظ البيانات بنجاح")
print(description)
print(tittle) }
}
}
}
func saveDataExchange() {
if let description = descriptionTextField.text,
let tittle = titleTextField.text {
// Save Data to Database
db.collection("userExchangeDatabase")
.addDocument(data: [
"description" : description,
"BookTitle": tittle ]) {
(error) in
if let err = error {
print(err.localizedDescription)
}else {
print("تم حفظ البيانات بنجاح")
print(description)
print(tittle) }
}
}
}
func saveDataBorrow() {
if let description = descriptionTextField.text,
let tittle = titleTextField.text {
// Save Data to Database
db.collection("userBorrowDatabase")
.addDocument(data: [
"description" : description,
"BookTitle": tittle]) {
(error) in
if let err = error {
print(err.localizedDescription)
}else {
print("تم حفظ البيانات بنجاح")
print(description)
print(tittle) }
}
}
}
func setpopupbutn () {
let option = {( ACTION : UIAction ) in
self.chooseOption = ACTION.title
print("حفظ الداتا في ",self.chooseOption)}
selectservice.menu = UIMenu (children : [
UIAction (title : "تبرع" , state: .on , handler: option),
UIAction (title : "بيع" , handler: option),
UIAction (title : "تبادل" , handler: option),
UIAction (title : "إستعارة" , handler: option),
])
saveDataDonation()
selectservice.showsMenuAsPrimaryAction = true
selectservice.changesSelectionAsPrimaryAction = true
}
func spcificOption() {
if chooseOption == ("تبرع") {
saveDataDonation()
} else if chooseOption == ("بيع") {
saveDataSale()
} else if chooseOption == ("تبادل") {
saveDataExchange()
} else if chooseOption == ("إستعارة") {
saveDataBorrow()
}
}
}
and this is the second vc (Table view)
import UIKit
import FirebaseFirestore
import Firebase
class ViewController3: UIViewController, UITableViewDataSource, UITableViewDelegate {
let db = Firestore.firestore()
var exchange : [exchange] = []
var borrow : [borrow] = []
var donation : [donation] = []
var sale : [sale] = []
#IBOutlet weak var segmentOutlet: UISegmentedControl!
#IBOutlet weak var userDataTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
userDataTableView.dataSource = self
userDataTableView.delegate = self
getDataDonation()
getDataSale()
getDataExchange()
getDataBorrow()
userDataTableView.reloadData()
}
#IBAction func serviceSeg(_ sender: UISegmentedControl) {
if sender.selectedSegmentIndex == 0 {
getDataExchange()
}
else if sender.selectedSegmentIndex == 1 {
getDataBorrow()
}
else if sender.selectedSegmentIndex == 2 {
getDataDonation()
}
else if sender.selectedSegmentIndex == 3 {
getDataSale()
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if segmentOutlet.selectedSegmentIndex == 0 {
return exchange.count
} else if segmentOutlet.selectedSegmentIndex == 1 {
return borrow.count
}else if segmentOutlet.selectedSegmentIndex == 2 {
return donation.count
} else if segmentOutlet.selectedSegmentIndex == 3 {
return sale.count
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
if segmentOutlet.selectedSegmentIndex == 0 {
cell.textLabel?.text = exchange [indexPath.row].passTitle
} else if segmentOutlet.selectedSegmentIndex == 1 {
cell.textLabel?.text = borrow [indexPath.row].passTitle
} else if segmentOutlet.selectedSegmentIndex == 2 {
cell.textLabel?.text = donation [indexPath.row].passTitle
} else if segmentOutlet.selectedSegmentIndex == 3 {
cell.textLabel?.text = sale [indexPath.row].passTitle
}
return cell
}
func getDataDonation(){
donation.removeAll()
db.collection("userDonationDatabase")
.getDocuments { querySnapshot, error in
if let err = error { print(err.localizedDescription)}
else {
for document in querySnapshot!.documents {
let data = document.data()
print( data["BookTitle"] as! String )
self.donation.append(finalProject.donation(passTitle3:data["BookTitle"] as! String , passDes3: data["description"] as! String))
}
DispatchQueue.main.async {
self.userDataTableView.reloadData()
}
}
}
}
func getDataSale(){
sale.removeAll()
db.collection("userSaleDatabase")
.getDocuments { querySnapshot, error in
if let err = error { print(err.localizedDescription)}
else {
for document in querySnapshot!.documents {
let data = document.data()
print( data["BookTitle"] as! String )
self.sale.append(finalProject.sale(passTitle4:data["BookTitle"] as! String , passDes4: data["description"] as! String))
}
DispatchQueue.main.async {
self.userDataTableView.reloadData()
}
}
}
}
func getDataExchange(){
exchange.removeAll()
db.collection("userExchangeDatabase")
.getDocuments { querySnapshot, error in
if let err = error { print(err.localizedDescription)}
else {
for document in querySnapshot!.documents {
let data = document.data()
print( data["BookTitle"] as! String )
self.exchange.append(finalProject.exchange(passTitle1:data["BookTitle"] as! String , passDes1: data["description"] as! String))
}
DispatchQueue.main.async {
self.userDataTableView.reloadData()
}
}
}
}
func getDataBorrow(){
borrow.removeAll()
db.collection("userBorrowDatabase")
.getDocuments { querySnapshot, error in
if let err = error { print(err.localizedDescription)}
else {
for document in querySnapshot!.documents {
let data = document.data()
print( data["BookTitle"] as! String )
self.borrow.append(finalProject.borrow(passTitle2:data["BookTitle"] as! String , passDes2: data["description"] as! String))
}
DispatchQueue.main.async {
self.userDataTableView.reloadData()
}
}
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let vc = storyboard?.instantiateViewController(withIdentifier:"vc10") as? ViewController10 {
vc.recivedE = exchange[indexPath.row]
self.navigationController?.pushViewController(vc, animated: true)
}
}
}
Note... I tried to do this but it didn't work
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let vc = storyboard?.instantiateViewController(withIdentifier:"vc10") as? ViewController10 {
vc.recivedE = exchange[indexPath.row]
vc.recivedB = borrow[indexPath.row]
vc.recivedD = donation[indexPath.row]
vc.recivedS = sale[indexPath.row]
self.navigationController?.pushViewController(vc, animated: true)
}
}
This is the struct,
I created a struct for each index
public struct exchange {
var passTitle : String
var passDes : String
init (passTitle1:String, passDes1:String) {
self.passTitle = passTitle1
self.passDes = passDes1
}
}
public struct borrow {
var passTitle : String
var passDes : String
init (passTitle2:String, passDes2:String) {
self.passTitle = passTitle2
self.passDes = passDes2
}
}
public struct donation {
var passTitle : String
var passDes : String
init (passTitle3:String, passDes3:String) {
self.passTitle = passTitle3
self.passDes = passDes3
}
}
public struct sale {
var passTitle : String
var passDes : String
init (passTitle4:String, passDes4:String) {
self.passTitle = passTitle4
self.passDes = passDes4
}
}
this is the last vc
import UIKit
import FirebaseStorage
import Firebase
import FirebaseFirestore
import SDWebImage
class ViewController10: UIViewController {
#IBOutlet weak var userBookTitle: UILabel!
#IBOutlet weak var userBookDescription: UILabel!
var recivedE:exchange?
var recivedB:borrow?
var recivedD:donation?
var recivedS:sale?
override func viewDidLoad() {
super.viewDidLoad()
userBookTitle.text = recivedE?.passTitle
userBookDescription.text = recivedE?.passDes
}
}
Note... I tried to do this but it didn't work
override func viewDidLoad() {
super.viewDidLoad()
if let et = recivedE?.passTitle ,
let ed = recivedE?.passDes{
userBookTitle.text = et
userBookDescription.text = ed
}
else if let bt = recivedB?.passTitle ,
let bd = recivedB?.passDes {
userBookTitle.text = bt
userBookDescription.text = bd
}
else if let dt = recivedD?.passTitle ,
let dd = recivedD?.passDes {
userBookTitle.text = dt
userBookDescription.text = dd
}
else if let st = recivedS?.passTitle ,
let sd = recivedS?.passDes {
userBookTitle.text = st
userBookDescription.text = sd
}
}
and this also not working
override func viewDidLoad() {
super.viewDidLoad()
userBookTitle.text = recivedE?.passTitle
userBookDescription.text = recivedE?.passDes
userBookTitle.text = recivedB?.passTitle
userBookDescription.text = recivedB?.passDes
userBookTitle.text = recivedD?.passTitle
userBookDescription.text = recivedD?.passDes
userBookTitle.text = recivedS?.passTitle
userBookDescription.text = recivedS?.passDes
}
help me, please
In both of your numberOfRowsInSection and cellForRowAt functions, you are checking the selected segment index to determine which data to use:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if segmentOutlet.selectedSegmentIndex == 0 {
return exchange.count
} else if segmentOutlet.selectedSegmentIndex == 1 {
return borrow.count
}else if segmentOutlet.selectedSegmentIndex == 2 {
return donation.count
} else if segmentOutlet.selectedSegmentIndex == 3 {
return sale.count
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
if segmentOutlet.selectedSegmentIndex == 0 {
cell.textLabel?.text = exchange [indexPath.row].passTitle
} else if segmentOutlet.selectedSegmentIndex == 1 {
cell.textLabel?.text = borrow [indexPath.row].passTitle
} else if segmentOutlet.selectedSegmentIndex == 2 {
cell.textLabel?.text = donation [indexPath.row].passTitle
} else if segmentOutlet.selectedSegmentIndex == 3 {
cell.textLabel?.text = sale [indexPath.row].passTitle
}
return cell
}
However, in didSelectRowAt, you only use the exchange data:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let vc = storyboard?.instantiateViewController(withIdentifier:"vc10") as? ViewController10 {
vc.recivedE = exchange[indexPath.row]
self.navigationController?.pushViewController(vc, animated: true)
}
}
If you implement the same if / else if structure in didSelectRowAt you should get the desired results.
I have my firebase database structured like this:
Snap (-KWLSAIh5WJvNJOkxBEr) {
beschrijving = "description";
image = "link to image";
title = "title";
}
Snap (-KWLSTak0H20X_2Qnanv) {
beschrijving = "description";
image = "link to image";
title = "title";
}
This is the code I am using to display this in a TableView:
import UIKit
import Firebase
class NieuwsTableViewController: UITableViewController {
var users = [UsersII]()
let cellId = "IdCell"
override func viewDidLoad() {
super.viewDidLoad()
fetchUser()
}
func fetchUser() {
Database.database().reference().child("Blog").observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let user = UsersII(dictionary: dictionary)
self.users.append(user)
print(snapshot)
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}
}, withCancel: nil)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return users.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> lllTableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath)
let user = users.reversed()[indexPath.row]
cell.textLabel?.text = user.name
return cell as! lllTableViewCell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let message = users.reversed()[indexPath.row]
guard let beschrijving = message.beschrijving else {
return
}
guard let image = message.plaatje else {
return
}
guard let titel = message.name else {
return
}
UserDefaults.standard.set(beschrijving, forKey: "nieuwsBeschrijving")
UserDefaults.standard.set(image,forKey: "nieuwsPlaatje")
UserDefaults.standard.set(titel, forKey: "nieuwsTitel")
self.performSegue(withIdentifier: "gotonews", sender: nil)
}
}
And I don't know if you will need this to answer this question but I'll also post the "UsersII" (defined as users just above the viewDidLoad method) in case this is needed to answer the question.
import UIKit
class UsersII: NSObject {
var name: String?
var beschrijving: String?
var plaatje: String?
init(dictionary: [String: Any]) {
self.name = dictionary["title"] as? String ?? ""
self.beschrijving = dictionary["beschrijving"] as? String ?? ""
self.plaatje = dictionary["image"] as? String ?? ""
}
}
so what I want to achieve is that if you click on one of the cells, you get the parent id of the article, so in this case that would be the "-KWLSAIh5WJvNJOkxBEr or -KWLSTak0H20X_2Qnanv" I mentioned above in my firebase database structure.
Here is what i was saying you to do:
Your model class:
class UsersII: NSObject {
var parentId: String?
var name: String?
var beschrijving: String?
var plaatje: String?
init(dictionary: [String: Any],parentId:String) {
self.name = dictionary["title"] as? String ?? ""
self.beschrijving = dictionary["beschrijving"] as? String ?? ""
self.plaatje = dictionary["image"] as? String ?? ""
self.parentId = parentId
}
}
Fetch user method:
func fetchUser() {
Database.database().reference().child("Blog").observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let user = UsersII(dictionary: dictionary,parentId:snapshot.key)
self.users.append(user)
print(snapshot)
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}
}, withCancel: nil)
}
And finaly you didSelect:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let message = users.reversed()[indexPath.row]
guard let beschrijving = message.beschrijving else {
return
}
guard let image = message.plaatje else {
return
}
guard let titel = message.name else {
return
}
guard let parentId = message.name else
{
return
}
UserDefaults.standard.set(beschrijving, forKey: "nieuwsBeschrijving")
UserDefaults.standard.set(image,forKey: "nieuwsPlaatje")
UserDefaults.standard.set(titel, forKey: "nieuwsTitel")
UserDefaults.standard.set(parentId,forKey: "nieuwsParentId")
self.performSegue(withIdentifier: "gotonews", sender: nil)
}
}
I just started to learn firestore, i created simple app like a example from googleFirestore (in github).
When i change or create new data in firestore i get an error when my app is start in this line:
fatalError("Error")
I so understand the app is not like creating new data, how can I avoid this error and create data in real time?
My code:
private var hall: [Hall] = []
private var documents: [DocumentSnapshot] = []
fileprivate var query: Query? {
didSet {
if let listener = listener {
listener.remove()
observeQuery()
}
}
}
private var listener: ListenerRegistration?
fileprivate func observeQuery() {
guard let query = query else { return }
stopObserving()
listener = query.addSnapshotListener { [unowned self] (snapshot, error) in
guard let snapshot = snapshot else {
print("Error fetching snapshot results: \(error!)")
return
}
let models = snapshot.documents.map { (document) -> Hall in
if let model = Hall(dictionary: document.data()) {
return model
} else {
fatalError("Error")
}
}
self.hall = models
self.documents = snapshot.documents
self.tableView.reloadData()
}
}
func stopObserving() {
listener?.remove()
}
func baseQuery() -> Query {
return Firestore.firestore().collection("searchStudios").limit(to: 50)
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.tableFooterView = UIView()
query = baseQuery()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
self.setNeedsStatusBarAppearanceUpdate()
observeQuery()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(true)
stopObserving()
}
override var preferredStatusBarStyle: UIStatusBarStyle {
set {}
get {
return .lightContent
}
}
deinit {
listener?.remove()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ResultTableViewCell
cell.populate(hall: hall[indexPath.row])
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return hall.count
}
I can delete data, but not can add new data.
UPDATE:
struct Hall:
import Foundation
protocol DocumentSerializable {
init?(dictionary: [String: Any])
}
struct Hall {
var description: String
var image: String
var meters: Double
var name: String
var price: Int
var studioHallAddress: String
var studioHallName: String
var studioHallLogo: String
var dictionary: [String: Any] {
return [
"description": description,
"image": image,
"meters": meters,
"name": name,
"price": price,
"studioHallAddrees": studioHallAddress,
"studioHallName": studioHallName,
"studioHallLogo": studioHallLogo
]
}
}
extension Hall: DocumentSerializable {
init?(dictionary: [String : Any]) {
guard let description = dictionary["description"] as? String,
let image = dictionary["image"] as? String,
let meters = dictionary["meters"] as? Double,
let name = dictionary["name"] as? String,
let price = dictionary["price"] as? Int,
let studioHallAddress = dictionary["studioHallAddress"] as? String,
let studioHallName = dictionary["studioHallName"] as? String,
let studioHallLogo = dictionary["studioHallLogo"] as? String else { return nil }
self.init(description: description,
image: image,
meters: meters,
name: name,
price: price,
studioHallAddress: studioHallAddress,
studioHallName: studioHallName,
studioHallLogo: studioHallLogo)
}
}
This question already has answers here:
What does "Fatal error: Unexpectedly found nil while unwrapping an Optional value" mean?
(16 answers)
Closed 6 years ago.
I am pretty new with swift. I tried to finger out myself. I know this is the common question, but I hope I could get help. When I run the application. I got the "fatal error: unexpectedly found nil while unwrapping an Optional value".
import Foundation
import Firebase
import FirebaseDatabase
import FirebaseStorage
import FirebaseAuth
struct TodoItemDatabase {
var eventID: String!
var title: String!
var staff: String!
var location: String!
var starts: String!
var ends: String!
var rpeat: String!
var imageName: String!
var description: String!
var secondPhoto: String!
var ref: FIRDatabaseReference?
var key: String!
var isCompleted: Bool
init (eventID: String!, title: String,staff:String, location: String,starts: String, ends: String, rpeat: String, imageName: String, description: String, secondPhoto: String, key: String = "", isCompleted: Bool){
self.eventID = eventID
self.title = title
self.staff = staff
self.location = location
self.starts = starts
self.ends = ends
self.rpeat = rpeat
self.imageName = imageName
self.description = description
self.secondPhoto = secondPhoto
self.key = key
self.ref = FIRDatabase.database().reference()
self.isCompleted = isCompleted
}
init(snapshot: FIRDataSnapshot){
**//I get the error from here. However, I think the main reason in tableview below**
self.eventID = snapshot.value!["eventID"] as! String
self.title = snapshot.value!["title"] as! String
self.staff = snapshot.value!["staff"] as! String
self.location = snapshot.value!["location"] as! String
self.starts = snapshot.value!["starts"] as! String
self.ends = snapshot.value!["ends"] as! String
self.rpeat = snapshot.value!["rpeat"] as! String
self.imageName = snapshot.value!["imageName"] as! String
self.description = snapshot.value!["description"] as! String
self.secondPhoto = snapshot.value!["secondPhoto"] as! String
self.key = snapshot.key
self.ref = snapshot.ref
self.isCompleted = snapshot.value!["isCompleted"] as! Bool
}
func toAnyObject() -> [String: AnyObject] {
return ["eventid": eventID, "title": title, "staff": staff, "location": location, "starts": starts, "ends": ends, "rpeat": rpeat, "imageName": imageName, "description": description, "secondPhoto": secondPhoto, "isCompleted": isCompleted]
}
}
However, When I run the application, and load the tableview. It appears that error.
import UIKit
import Firebase
import FirebaseAuth
import FirebaseDatabase
import FirebaseStorage
var toDoList:[TodoItemDatabase] = [TodoItemDatabase]()
class CurrentEventViewController: UIViewController, UITableViewDelegate {
var databaseRef: FIRDatabaseReference!{
return FIRDatabase.database().reference()
}
var storageRef: FIRStorageReference!
#IBOutlet var toDoListTable: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return toDoList.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! myCell
let todoItem = toDoList[indexPath.row]
storageRef = FIRStorage.storage().referenceForURL(toDoList[indexPath.row].imageName)
storageRef.dataWithMaxSize(1 * 1024 * 1024) { (data, error) in
if error == nil {
dispatch_async(dispatch_get_main_queue(), {
if let data = data {
cell.myImageView.image = UIImage(data: data)
}
})
} else {
print(error!.localizedDescription)
}
}
cell.myLabel.text = todoItem.title!
return cell
}
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == UITableViewCellEditingStyle.Delete {
let ref = toDoList[indexPath.row]
ref.ref?.removeValue()
toDoList.removeAtIndex(indexPath.row)
toDoListTable.reloadData()
}
}
override func viewDidAppear(animated: Bool) {
toDoListTable.reloadData()
}
override func viewWillAppear(animated: Bool) {
let postRef = FIRDatabase.database().reference().child("posts").queryOrderedByChild("isCompleted").queryEqualToValue(false)
postRef.observeEventType(.Value, withBlock: { (snapshot) in
var newPosts = [TodoItemDatabase]()
for post in snapshot.children{
**// I think the reason is the line after.**
let post = TodoItemDatabase(snapshot: post as! FIRDataSnapshot)
newPosts.insert(post, atIndex: 0)
}
toDoList = newPosts
dispatch_async(dispatch_get_main_queue(), {
self.toDoListTable.reloadData()
})
}) { (error) in
print(error.localizedDescription)
}
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
storageRef = FIRStorage.storage().referenceForURL(toDoList[indexPath.row].imageName)
let storageRef1 = FIRStorage.storage().referenceForURL(toDoList[indexPath.row].secondPhoto)
let itemSelected = toDoList[indexPath.row]
storageRef.dataWithMaxSize(1 * 1024 * 1024) { (data, error) in
if error == nil
{
dispatch_async(dispatch_get_main_queue(), {
if let data = data
{
storageRef1.dataWithMaxSize(1 * 1024 * 1024) { (data1, error) in
if error == nil
{
dispatch_async(dispatch_get_main_queue(), {
if let data1 = data1
{
let detailVC:DetailViewController = self.storyboard?.instantiateViewControllerWithIdentifier("DetailViewController") as! DetailViewController
detailVC.titleEvent = itemSelected.title
detailVC.staffEvent = itemSelected.staff
detailVC.locationEvent = itemSelected.location
detailVC.startEvent = itemSelected.starts
detailVC.endEvent = itemSelected.ends
detailVC.repeatEvent = itemSelected.rpeat
detailVC.imageDetail = UIImage(data: data)!
detailVC.descriptionDetail = itemSelected.description
detailVC.secondPhotoEvent = UIImage(data: data1)!
detailVC.key = itemSelected.key
self.presentViewController(detailVC, animated: true, completion: nil)
}
})
}
else
{
print(error!.localizedDescription)
}
}}
})
}
else
{
print(error!.localizedDescription)
}
}
}
}
You need to conditionally unwrap or nil coalesce these values. Force unwrapping the way you are is not safe.
self.eventID = snapshot.value!["eventID"] as! String
should probably be
eventID = snapshot.value?["eventID"] as? String ?? ""
Unfortunately, the compiler loves to suggest force-unwrapping when it encounters optional values. This is almost always a terrible suggestion. You need to get into the habit of handling Optionals gracefully when you encounter .None since Optionals are such an integral part of the Swift language.
I use a service in a background thread to fetch a post request. Then I use NSJSONSerialization to turn that into an array. I loop thorough the array to create an array of teams. Then i go back to the main queue and call the completion handler.
Team:
class Team
{
private (set) var id: Int
private (set) var city: String
private (set) var name: String
private (set) var abbreviation: String
init(data: JSONDictionary)
{
id = data["team_id"] as? Int ?? 0
city = data["city"] as? String ?? ""
name = data["team_name"] as? String ?? ""
abbreviation = data["abbreviation"] as? String ?? ""
}
}
Service:
func getTeams(urlString: String, completion: [Team] -> Void)
{
let config = NSURLSessionConfiguration.ephemeralSessionConfiguration()
let session = NSURLSession(configuration: config)
let url = NSURL(string: urlString)!
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
let task = session.dataTaskWithRequest(request) {
(data, response, error) in
if error != nil {
print(error!.localizedDescription)
} else {
print(data)
do {
if let json = try NSJSONSerialization.JSONObjectWithData(data!, options: .AllowFragments) as? JSONArray {
var teams = [Team]()
for team in json {
let team = Team(data: team as! JSONDictionary)
teams.append(team)
}
let priority = DISPATCH_QUEUE_PRIORITY_HIGH
dispatch_async(dispatch_get_global_queue(priority, 0)) {
dispatch_async(dispatch_get_main_queue()) {
completion(teams)
}
}
}
} catch {
print("error in NSJSONSerialization")
}
}
}
task.resume()
}
I then try to use data to populate a tableView. I also loop through and print out all the team names to the console with success. The problem I am having It populate the tableView but everything is all white. I cant see any txt from my labels until I touch it. While the table cell is selected I can see the contents of the labels which are in black. But if i touch another one only the currently selected label is showing. It seems they should all just show up visible once the data is loaded.
custom cell:
class TeamTableViewCell: UITableViewCell {
var team: Team? {
didSet {
updateCell()
}
}
#IBOutlet weak var title: UILabel!
#IBOutlet weak var abbreviation: UILabel!
func updateCell()
{
title.text = team?.name ?? ""
abbreviation.text = team?.abbreviation ?? ""
}
}
Controller:
var teams = [Team]()
override func viewDidLoad() {
super.viewDidLoad()
title = "Teams"
let service = NBAService()
service.getTeams("https://probasketballapi.com/teams?api_key=\(Constants.API.APIKey)", completion: didLoadTeams )
}
func didLoadTeams(teams: [Team])
{
self.teams = teams
tableView.reloadData()
// This actuall works returns an list of team names to the console.
for team in teams {
print("Team: \(team.name)")
}
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return teams.count
}
struct Storyboard {
static let TeamCell = "TeamCell"
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(Storyboard.TeamCell, forIndexPath: indexPath) as! TeamTableViewCell
// Configure the cell...
cell.team = self.teams[indexPath.row]
return cell
}
When i print the teams names to the console that prints fine so I know that I have successfully got the data back from the request. And one team at a time is visible when the cell is selected. What am I missing
This is kind of strange:
dispatch_async(dispatch_get_global_queue(priority, 0)) {
dispatch_async(dispatch_get_main_queue()) {
completion(teams)
}
}
I would replace this with:
dispatch_async(dispatch_get_main_queue()) {
completion(teams)
}