I made a test application following the example of Google with github, but with a few changes (less keys used in the firestore and less filters).
The problem is this, the app crashing when I added new keys in the firestore, but the app works with two keys previously added.
Crashes and shows error on fatalError("error"). I can not understand why with two keys the application works, but if i begin to use the third key (hall) then the app crashes.
What could be the problem?
It's my code:
class ViewControllerTwo: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableView: UITableView!
private var sweets: [Sweet] = []
private var document: [DocumentSnapshot] = []
fileprivate var query: Query? {
didSet {
if let listener = listener {
listener.remove()
}
}
}
private var listener: FIRListenerRegistration?
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) -> Sweet in
if let model = Sweet(dictionary: document.data()) {
return model
} else {
fatalError("error")
}
}
self.sweets = models
self.document = snapshot.documents
self.tableView.reloadData()
}
}
#IBAction func filterButton(_ sender: Any) {
present(filters.navigationController, animated: true, completion: nil)
}
lazy private var filters: (navigationController: UINavigationController, filtersController: FilterViewController) = {
return FilterViewController.fromStoryboard(delegate: self)
}()
fileprivate func stopObserving() {
listener?.remove()
}
fileprivate func baseQuery() -> Query {
return Firestore.firestore().collection("sweets").limit(to: 50)
}
override func viewDidLoad() {
super.viewDidLoad()
query = baseQuery()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
observeQuery()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(true)
stopObserving()
}
deinit {
listener?.remove()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sweets.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! ViewControllerCell
let sweet = sweets[indexPath.row]
cell.studioNameLabel.text = sweet.name
cell.studioAddressLabel.text = sweet.content
cell.hallNameLabel.text = sweet.hall
return cell
}
}
extension ViewControllerTwo: FiltersViewControllerDelegate {
func query(withCategory title: String?) -> Query {
var filtered = baseQuery()
if let title = title, !title.isEmpty {
filtered = filtered.whereField("title", isEqualTo: title)
}
return filtered
}
func controller(_ controller: FilterViewController, didSelectCategory title: String?) {
let filtered = query(withCategory: title)
self.query = filtered
observeQuery()
}
}
class ViewControllerCell: UITableViewCell {
#IBOutlet weak var studioNameLabel: UILabel!
#IBOutlet weak var studioAddressLabel: UILabel!
#IBOutlet weak var hallNameLabel: UILabel!
}
And my struct:
protocol DocumentSerializable {
init?(dictionary:[String:Any])
}
struct Sweet {
var name:String
var content:String
var hall:String
var dictionary:[String:Any] {
return [
"name": name,
"content" : content,
"hall" : hall
]
}
}
extension Sweet : DocumentSerializable {
static let title = [
"one",
"two",
"three",
"four"
]
init?(dictionary: [String : Any]) {
guard let name = dictionary["name"] as? String,
let content = dictionary["content"] as? String,
let hall = dictionary["hall"] as? String else { return nil }
self.init(name: name, content: content, hall: hall)
}
}
My project in google drive
google drive
google service info.plist
You just need to reinstall app once you add any new key to you existing structure.
So you should decide before structure implementation that what keys you will need. Or you can reinstall app if you add new key in future.
Related
I need help with solving one problem with core data. I have a news app with two view controllers. In the main view controller I'm loading news data in table view in custom cell. Here I have a button, on which should I tap and save news to another view controller. How it looks now: How it looks So when we tap on this blue button, it should save news from the cell and display on second view controller. I have created a core data model like this: Core data model Here is code in my first view controller:
import UIKit
import SafariServices
import CoreData
class ViewController: UIViewController, UISearchBarDelegate, UpdateTableViewDelegate {
#IBOutlet weak var pecodeTableView: UITableView!
private var articles = [News]()
// private var viewModels = [NewsTableViewCellViewModel]()
private var viewModel = NewsListViewModel()
var newsTitle: String?
var newsAuthor: String?
var newsDesc: String?
var urlString: String?
var newsDate: String?
private let searchVC = UISearchController(searchResultsController: nil)
var selectedRow = Int()
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()
viewModel.delegate = self
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
categoryMenu()
loadData()
}
private func loadNewsData(api: String){
let apiService = APIService(categoryCode: api)
apiService.getNewsData {(result) in
switch result{
case .success(let NewsOf):
CoreData.sharedInstance.saveDataOf(news: NewsOf.articles)
case .failure(let error):
print("Error processing json data: \(error)")
}
}
}
func reloadData(sender: NewsListViewModel) {
self.pecodeTableView.reloadData()
}
//MARK: - Networking
private func loadData(){
viewModel.retrieveDataFromCoreData()
}
//MARK: - UIView UImenu
func categoryMenu(){
var categoryAction: UIMenu{
let menuAction = Category.allCases.map { (item) -> UIAction in
let name = item.rawValue
return UIAction(title: name.capitalized, image: UIImage(systemName: item.systemImage)) { [weak self](_) in
self?.loadNewsData(api: name)
self?.loadData()
self?.reloadData(sender: self!.viewModel)
}
}
return UIMenu(title: "Change Category", children: menuAction)
}
let categoryButton = UIBarButtonItem(image: UIImage(systemName: "scroll"), menu: categoryAction)
navigationItem.leftBarButtonItem = categoryButton
}
#IBAction func goToFavouritesNews(_ sender: UIButton) {
performSegue(withIdentifier: S.Segues.goToFav, sender: self)
}
private func createSearchBar() {
navigationItem.searchController = searchVC
searchVC.searchBar.delegate = self
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 150
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.numberOfRowsInSection(section: section)
}
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
let object = viewModel.object(indexPath: indexPath)!
cell?.setCellWithValuesOf(object)
cell?.saveNewsBtn.tag = indexPath.row
cell?.saveNewsBtn.addTarget(self, action: #selector(didTapCellButton(sender:)), for: .touchUpInside)
return cell!
}
#objc func didTapCellButton(sender: FavouritesCell) {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context: NSManagedObjectContext = appDelegate.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "SavedNews", in: context)
let newNewsSave = SavedNews(entity: entity!, insertInto: context)
newNewsSave.author = newsAuthor
newNewsSave.desc = newsDesc
newNewsSave.title = newsTitle
do {
try context.save()
savedNews.append(newNewsSave)
navigationController?.popViewController(animated: true)
} catch {
print("Error saving")
}
print("Done")
}
Also I want to show you my parsing functions and model for this: *APIService.swift*
import Foundation
class APIService{
private var dataTask: URLSessionDataTask?
public let resourceURL: URL
private let API_KEY = "e2a69f7f9567451ba484c85614356c30"
private let host = "https://newsapi.org"
private let headlines = "/v2/top-headlines?"
init(categoryCode: String){
let resourceString = "\(host)\(headlines)country=us&category=\(categoryCode)&apiKey=\(API_KEY)"
print(resourceString)
guard let resourceURL = URL(string: resourceString) else {
fatalError()
}
self.resourceURL = resourceURL
}
//MARK: - Get News
func getNewsData(completion: #escaping (Result<Articles, Error>) -> Void){
dataTask = URLSession.shared.dataTask(with: resourceURL) { (data, response, error) in
if let error = error{
completion(.failure(error))
print("DataTask error: - \(error.localizedDescription)")
}
guard let response = response as? HTTPURLResponse else{
print("Empty Response")
return
}
print("Response status code: - \(response.statusCode)")
guard let data = data else {
print("Empty Data")
return
}
do{
let decoder = JSONDecoder()
let jsonData = try decoder.decode(Articles.self, from: data)
DispatchQueue.main.async {
completion(.success(jsonData))
}
}catch let error{
completion(.failure(error))
}
}
dataTask?.resume()
}
}
And here is NewsModel.swift:
import Foundation
struct Articles: Codable {
let articles: [News]
private enum CodingKeys: String, CodingKey{
case articles = "articles"
}
}
struct News: Codable {
let author: String?
let source: Source
let title: String
let description: String?
let url: URL?
let urlToImage: URL?
let publishedAt: String?
private enum CodingKeys: String, CodingKey{
case author = "author"
case title = "title"
case url = "url"
case urlToImage = "urlToImage"
case publishedAt = "publishedAt"
case description = "description"
case source = "source"
}
}
struct Source: Codable {
let name: String?
}
Here is my code in CustomNewsCell.swift:
import UIKit
protocol CustomNewsDelegate: AnyObject {
func btnFavPress(cell: CustomNewsCell)
}
private var loadImage = LoadToImage()
private var formatDate = FormatDate()
class CustomNewsCell: UITableViewCell {
weak var delegate: CustomNewsDelegate?
#IBOutlet weak var saveNewsBtn: UIButton!
#IBOutlet weak var imageOutlet: UIImageView!
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var descLabel: UILabel!
#IBOutlet weak var authorLabel: UILabel!
#IBOutlet weak var dateLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func setCellWithValuesOf(_ news: SavedNews){
updateUI(title: news.title, url: news.url, urlToImage: news.urlToImage, publishedAt: news.publishedAt, author: news.author ?? "No author", description: news.desc, source: news.source)
}
private func updateUI(title: String?, url: URL?, urlToImage: URL?, publishedAt: String?, author: String, description: String?, source: String?){
//title
self.titleLabel.text = title
self.authorLabel.text = author
self.descLabel.text = description
//date
let dateString = formatDate.formatDate(from: publishedAt ?? "")
let date = formatDate.formatDateString(from: dateString)
self.dateLabel.text = date
//image
guard let urlToImageString = urlToImage else {return}
imageOutlet.image = nil
loadImage.getImageDataFrom(url: urlToImageString) { [weak self] data in
guard let data = data, let image = UIImage(data: data) else{
DispatchQueue.main.async {
self?.imageOutlet.image = UIImage(named: "noImage")
}
return
}
self?.imageOutlet.image = image
}
}
#IBAction func saveBtnPressed(_ sender: UIButton) {
delegate?.btnFavPress(cell: self)
}
}
First time I've tried with delegate method for this blue button, but now as you can see I've created a selector method. Maybe it's not correct and need to fix it. Here is the code in the second view controller, which should show saved news from first view controller:
import UIKit
import CoreData
var savedNews = [SavedNews]()
class FavouriteNewsViewController: UIViewController {
#IBOutlet weak var favTableView: UITableView!
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var result: [SavedNews] = []
var newsTitleNew: String?
var newsDescNew: String?
var newsAuthor: String?
override func viewDidLoad() {
super.viewDidLoad()
favTableView.delegate = self
favTableView.delegate = self
fetch()
// loadSavedNews()
favTableView.register(UINib(nibName: S.FavouriteCell.favouriteCell, bundle: nil), forCellReuseIdentifier: S.FavouriteCell.favouriteCellIdentifier)
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
// fetch()
favTableView.reloadData()
}
#IBAction func goToNewsFeed(_ sender: UIButton) {
performSegue(withIdentifier: S.Segues.goToNewsFeed, sender: self)
}
}
extension FavouriteNewsViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return savedNews.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 140
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = favTableView.dequeueReusableCell(withIdentifier: S.FavouriteCell.favouriteCellIdentifier, for: indexPath) as! FavouritesCell
let newRslt: SavedNews!
newRslt = savedNews[indexPath.row]
cell.favAuthor.text = newRslt.author
cell.favDesc.text = newRslt.desc
cell.favTitle.text = newRslt.title
return cell
}
func fetch() {
let request = NSFetchRequest<SavedNews>(entityName: "SavedNews")
do {
savedNews = try context.fetch(request)
} catch {
print(error)
}
}
}
And code for cell for this controller:
import UIKit
import CoreData
class FavouritesCell: UITableViewCell {
#IBOutlet weak var favImage: UIImageView!
#IBOutlet weak var favTitle: UILabel!
#IBOutlet weak var favDesc: UILabel!
#IBOutlet weak var favAuthor: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
If somebody can help with this, it will be amazing. Because I really don't how to do this. Thank you!
I'm working on study project of social network. Stuck on the stage of deleting user comments from Firebase database. To delete a specific comment I need to know the comment Id, but I do not understand how to access it. I'm really appreciate any help on this!
Example of Firebase database:
CommentViewController:
class CommentViewController: UIViewController {
#IBOutlet weak var sendButton: UIButton!
#IBOutlet weak var commentTextField: UITextField!
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var constraintToBottom: NSLayoutConstraint!
var postId: String!
var comments = [Comment]()
var users = [User]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
title = "Comment"
tableView.estimatedRowHeight = 77
tableView.rowHeight = UITableView.automaticDimension
empty()
handleTextField()
loadComments()
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
view.endEditing(true)
}
#objc func keyboardWillShow(_ notification: NSNotification) {
let keyboardFrame = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as AnyObject).cgRectValue
UIView.animate(withDuration: 0.3) {
self.constraintToBottom.constant = keyboardFrame!.height
self.view.layoutIfNeeded()
}
}
#objc func keyboardWillHide(_ notification: NSNotification) {
UIView.animate(withDuration: 0.3) {
self.constraintToBottom.constant = 0
self.view.layoutIfNeeded()
}
}
var comment: Comment?
func loadComments() {
Api.Post_Comment.REF_POST_COMMENTS.child(self.postId).observe(.childAdded, with: {
snapshot in
Api.Comment.observeComments(withPostId: snapshot.key, completion: {
comment in
self.fetchUser(uid: comment.uid!, completed: {
self.comments.append(comment)
self.tableView.reloadData()
})
})
})
}
func fetchUser(uid: String, completed: #escaping() -> Void ) {
Api.User.observeUser(withId: uid, completion: {
user in
self.users.append(user)
completed()
})
}
func handleTextField() {
commentTextField.addTarget(self, action: #selector(self.textFieldDidChange), for: UIControl.Event.editingChanged)
}
#objc func textFieldDidChange() {
if let commentText = commentTextField.text, !commentText.isEmpty {
sendButton.setTitleColor(UIColor.black, for: UIControl.State.normal)
sendButton.isEnabled = true
return
}
sendButton.setTitleColor(UIColor.lightGray, for: UIControl.State.normal)
sendButton.isEnabled = false
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.tabBarController?.tabBar.isHidden = true
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.tabBarController?.tabBar.isHidden = false
}
#IBAction func sendButton_TouchUpInside(_ sender: Any) {
let commentsReference = Api.Comment.REF_COMMENTS
let newCommentId = commentsReference.childByAutoId().key!
let newCommentReference = commentsReference.child(newCommentId)
guard let currentUser = Api.User.CURRENT_USER else {
return
}
let currentUserId = currentUser.uid
newCommentReference.setValue(["uid": currentUserId, "commentText": commentTextField.text!], withCompletionBlock: {
(error, ref) in
if error != nil {
ProgressHUD.showError(error!.localizedDescription)
return
}
let postCommentRef = Api.Post_Comment.REF_POST_COMMENTS.child(self.postId).child(newCommentId)
postCommentRef.setValue(true, withCompletionBlock: { (error, ref) in
if error != nil {
ProgressHUD.showError(error!.localizedDescription)
return
}
})
self.empty()
self.view.endEditing(true)
})
}
func empty() {
self.commentTextField.text = ""
sendButton.setTitleColor(UIColor.lightGray, for: UIControl.State.normal)
sendButton.isEnabled = false
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "Comment_ProfileSegue" {
let profileVC = segue.destination as! ProfileUserViewController
let userId = sender as! String
profileVC.userId = userId
}
}
extension CommentViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return comments.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CommentCell", for: indexPath) as! CommentTableViewCell
let comment = comments[indexPath.row]
let user = users[indexPath.row]
cell.comment = comment
cell.user = user
cell.delegate = self
return cell
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if (editingStyle == .delete) {
}
}
CommentTableViewCell:
class CommentTableViewCell: UITableViewCell {
#IBOutlet weak var profileImageView: UIImageView!
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var commentLabel: UILabel!
var delegate: CommentTableViewCellDelegate?
var comment: Comment? {
didSet {
updateView()
}
}
var user: User? {
didSet {
setupUserInfo()
}
}
func updateView() {
commentLabel.text = comment?.commentText
}
func setupUserInfo() {
nameLabel.text = user?.username
if let photoUrlString = user?.profileImageUrl {
let photoUrl = URL(string: photoUrlString)
profileImageView.sd_setImage(with: photoUrl, placeholderImage: UIImage(named: "photo_placeholder"))
}
}
override func awakeFromNib() {
super.awakeFromNib()
nameLabel.text = ""
commentLabel.text = ""
let tapGestureForNameLabel = UITapGestureRecognizer(target: self, action: #selector(self.nameLabel_TouchUpInside))
nameLabel.addGestureRecognizer(tapGestureForNameLabel)
nameLabel.isUserInteractionEnabled = true
}
#objc func nameLabel_TouchUpInside() {
if let id = user?.id {
delegate?.goToProfileUserVC(userId: id)
}
}
override func prepareForReuse() {
super.prepareForReuse()
profileImageView.image = UIImage(named: "placeholderImg")
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
Comment Api
class CommentApi {
var REF_COMMENTS = Database.database().reference().child("comments")
func observeComments(withPostId id: String, completion: #escaping (Comment) -> Void) {
REF_COMMENTS.child(id).observeSingleEvent(of: .value, with: {
snapshot in
if let dict = snapshot.value as? [String: Any] {
let newComment = Comment.transformComment(dict: dict, key: snapshot.key)
completion(newComment)
}
})
}
func observeComment(withId id: String, completion: #escaping (Comment) -> Void) {
REF_COMMENTS.child(id).observeSingleEvent(of: DataEventType.value, with: {
snapshot in
if let dict = snapshot.value as? [String: Any] {
let comment = Comment.transformComment(dict: dict, key: snapshot.key)
completion(comment)
}
})
}
Comment Model:
class Comment {
var commentText: String?
var uid: String?
var id: String?}
extension Comment {
static func transformComment(dict: [String: Any], key: String) -> Comment {
let comment = Comment()
comment.id = key
comment.commentText = dict["commentText"] as? String
comment.uid = dict["uid"] as? String
return comment
}
Speaking at a high level, your tableView is backed by a dataSource, typically an array, which is the source for the content displayed in the tableView.
var userCommentArray = [UserComment]()
you should be loading data from Firebase and storing that data in the array as UserComment objects
class UserComment {
var firebaseKey = ""
var commentText = ""
var uid = ""
}
the firebase_key property is the key to the node in Firebase, shown as -MH_xxxx in the screenshot and then the commentText and uid are is the child data of that node.
The indexes of the elements in the array match what's being shown in the tableView, so row0 matches the array index 0, row 1 matches the array index 1 etc.
When the user deletes row 1, you know that's index 1 in the array. read the object, get it's firebaseKey and then delete it from firebase, updating the array accordingly and then reloading your UI.
See my answer to your Other Question for details on that process.
I'm only a month into learning Swift and programming in general. So please bear with be.
I'm trying to change a child value inside a Child created by ChildbyAutoID. The value i want to change is the isChecked Value. But all it does it create a new Child and set's the value inside there.
My View Controller
import UIKit
import FirebaseDatabase
import Firebase
class guestListViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var guestListTableView: UITableView!
var guestListDBRef : DatabaseReference!
var guestListText = [AdminTextModel]()
override func viewDidLoad() {
super.viewDidLoad()
guestListDBRef = Database.database().reference().child("RSVP")
guestListDBRef.queryOrdered(byChild: "name").observe(DataEventType.value, with: {(snapshot) in
if snapshot.childrenCount > 0 {
for guestListLabel in snapshot.children.allObjects as! [DataSnapshot] {
let guestListTextObject = guestListLabel.value as? [String: AnyObject]
let name = guestListTextObject?["name"]
let date = guestListTextObject?["date"]
let isChecked = guestListTextObject?["isChecked"]
let key = guestListTextObject?["keyID"]
let guestListTextLabels = AdminTextModel(key: key as! String?, name: name as! String?, date: date as! String?, isChecked: isChecked as! Bool? )
self.guestListText.append(guestListTextLabels)
self.guestListTableView.rowHeight = 45
self.guestListTableView.reloadData()
}
}
})
// Do any additional setup after loading the view.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return guestListText.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let GuestListTextCell = tableView.dequeueReusableCell(withIdentifier: "guestList") as! GuestListTableViewCell
let text: AdminTextModel
text = guestListText[indexPath.row]
GuestListTextCell.guestListNameLabel.text = text.name
GuestListTextCell.guestListDateLabel.text = text.date
if text.isChecked! {
GuestListTextCell.accessoryType = .checkmark
}else {
GuestListTextCell.accessoryType = .none
}
return GuestListTextCell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let isChecked = self.guestListText[indexPath.row].isChecked!
self.guestListText[indexPath.row].isChecked! = true
let key = self.guestListText[indexPath.row].key
Checkedservice.checkuncheck(key: key!, isChecked: isChecked) { (seccess) in
guard seccess else { return }
self.guestListText[indexPath.row].isChecked = true
self.guestListTableView.reloadRows(at: [indexPath], with: .none)
print("\(isChecked)")
print(key!)
}
}
struct Checkedservice {
static func checkuncheck(key: String, isChecked: Bool, success: #escaping (Bool) -> Void) {
let onlinesRef = Database.database().reference().child("RSVP").child(key).child("isChecked")
onlinesRef.setValue(isChecked) {(error, _ ) in
if let error = error {
assertionFailure(error.localizedDescription)
success(false)
}
success(true)
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
My Custom Text Model
import Foundation
class AdminTextModel {
var key: String?
var name: String?
var date: String?
var isChecked: Bool?
init(key: String?, name: String?, date: String?, isChecked: Bool?) {
self.key = key
self.name = name
self.date = date
self.isChecked = isChecked
}
}
My TableViewCell Class
import UIKit
class GuestListTableViewCell: UITableViewCell {
#IBOutlet weak var guestListDateLabel: UILabel!
#IBOutlet weak var guestListNameLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
and this is how I am writing to my Database.
#IBAction func rsvpButton(_ sender: Any) {
let key = rsvpRef?.child("RSVP").childByAutoId().key
let guestList = [
"keyID": key,
"name": nameTextField.text!,
"date": dateTextField.text!,
"isChecked": false] as [String : Any]
rsvpRef?.child("RSVP").childByAutoId().setValue(guestList)
if (nameTextField.text == nil) || dateTextField.text == nil {
print("No RSVP Entered")
self.dateTextField.text = nil
self.nameTextField.text = nil
}
dateTextField.text = ""
nameTextField.text = ""
}
My end goal is to check and uncheck cells. But when i reload the view, the check and unchecked cells are still there. Thank You so much!
My firebase structure
#IBAction func rsvpButton(_ sender: Any)
{
// Get first ID of your record
let databaseRef = Database.database().reference(withPath: "RSVP").observe(.childAdded)
{ (snapshot:DataSnapshot) in
// Here you will get your ID now Update value
print(snapshot.key)
//here is your value
let guestList = [
"keyID": key,
"name": nameTextField.text!,
"date": dateTextField.text!,
"isChecked": false] as [String : Any]
// Update your Child Valus
Database.database().reference(withPath: "RSVP").child(snapshot.key).updateChildValues(guestList)
}
}
I think You have confused "How to add or update the record in firebase". Let understand when we will use `childByAutoId(). As per firebase documentation.
childByAutoId generates a new child location using a unique key and
returns a Firebase reference to it. This is useful when the children
of a Firebase database location represent a list of items.
That means it will create new items every time with unique key value once you have executed childByAutoId() with the specific reference.
Solution:
I suggest please use below line of code to update or add value.
Update:
You can also do it as below.
let key = rsvpRef?.child("RSVP").childByAutoId().key
rsvpRef.child("key").observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.hasChild(key){
print("exist")
let guestList = [
"keyID": key,
"name": nameTextField.text!,
"date": dateTextField.text!,
"isChecked": false] as [String : Any]
rsvpRef?.child("RSVP").updateChildValues(guestList)
}else{
print("doesn't exist")
rsvpRef?.child("RSVP").childByAutoId().setValue(guestList)
}
})
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)
}
}
I'm using firebase to make an iOS app. I want to retrieve all the users on my database and display their name and profile picture in a table view. Here is my code for my TableViewCell:
import UIKit
import FirebaseDatabase
import FirebaseAuth
import SDWebImage
class HomeTableViewCell: UITableViewCell {
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var profileImageView: UIImageView!
#IBOutlet weak var likeImageView: UIImageView!
#IBOutlet weak var messageImageView: UIImageView!
#IBOutlet weak var likeCountButton: UIButton!
var homeVC: HomeViewController?
var postReference: DatabaseReference!
var post: UserFile?{
didSet {
updateView()
}
}
var user: UserFile? {
didSet {
updateUserInfo()
}
}
override func awakeFromNib() {
super.awakeFromNib()
nameLabel.text = ""
let berryTapGesture = UITapGestureRecognizer(target: self, action: #selector(handleLikeTap))
likeImageView.addGestureRecognizer(berryTapGesture)
likeImageView.isUserInteractionEnabled = true
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
func updateView() {
if let photoURL = post?.picURL {
profileImageView.sd_setImage(with: URL(string: photoURL))
}
API.Post.REF_POSTS.child(post!.id!).observeSingleEvent(of: .value, with: { postSnapshot in
if let postDictionary = postSnapshot.value as? [String:Any] {
let post = UserFile.transformPost(postDictionary: postDictionary, key: postSnapshot.key)
self.updateLike(post: post)
}
})
API.Post.REF_POSTS.child(post!.id!).observe(.childChanged, with: { snapshot in
if let value = snapshot.value as? Int {
self.likeCountButton.setTitle("\(value) berries", for: .normal)
}
})
}
func updateLike(post: UserFile) {
let imageName = post.berries == nil || !post.isBerried! ? "berry" : "berrySelected"
likeImageView.image = UIImage(named: imageName)
// display a message for berries
guard let count = post.berryCount else {
return
}
if count != 0 {
likeCountButton.setTitle("\(count) berries", for: .normal)
} else if post.berryCount == 0 {
likeCountButton.setTitle("Be the first to Like this", for: .normal)
}
}
func incrementberries(forReference ref: DatabaseReference) {
ref.runTransactionBlock({ (currentData: MutableData) -> TransactionResult in
if var post = currentData.value as? [String : AnyObject], let uid = Auth.auth().currentUser?.uid {
var berries: Dictionary<String, Bool>
berries = post["berries"] as? [String : Bool] ?? [:]
var likeCount = post["berryCount"] as? Int ?? 0
if let _ = berries[uid] {
// Unlike the post and remove self from stars
likeCount -= 1
berries.removeValue(forKey: uid)
} else {
// Like the post and add self to stars
likeCount += 1
berries[uid] = true
}
post["berryCount"] = likeCount as AnyObject?
post["berries"] = berries as AnyObject?
currentData.value = post
return TransactionResult.success(withValue: currentData)
}
return TransactionResult.success(withValue: currentData)
}) { (error, committed, snapshot) in
if let error = error {
print(error.localizedDescription)
}
if let postDictionary = snapshot?.value as? [String:Any] {
let post = UserFile.transformPost(postDictionary: postDictionary, key: snapshot!.key)
self.updateLike(post: post)
}
}
}
func handleLikeTap() {
postReference = API.Post.REF_POSTS.child(post!.id!)
incrementberries(forReference: postReference)
}
override func prepareForReuse() {
super.prepareForReuse()
profileImageView.image = UIImage(named: "industribune-default-no-profile-pic")
}
func updateUserInfo() {
nameLabel.text = user?.username
if let photoURL = user?.profileImageURL {
profileImageView.sd_setImage(with: URL(string: photoURL), placeholderImage: UIImage(named: "industribune-default-no-profile-pic"))
}
}
}
I am displaying this cell on my HomeViewController:
import UIKit
import FirebaseAuth
import FirebaseDatabase
import FirebaseStorage
import Firebase
class HomeViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var activityIndicatorView: UIActivityIndicatorView!
var posts = [UserFile]()
var users = [UserFile]()
override func viewDidLoad() {
super.viewDidLoad()
// for performance set an estimated row height
tableView.estimatedRowHeight = 1
// but also request to dynamically adjust to content using AutoLayout
tableView.rowHeight = UITableViewAutomaticDimension
//tableView.delegate = self
tableView.dataSource = self
loadPosts()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func loadPosts() {
activityIndicatorView.startAnimating()
API.User.observePosts { (newPost) in
guard let userID = newPost.uid else { return }
self.fetchUser(uid: userID, completed: {
// append the new Post and Reload after the user
// has been cached
self.posts.append(newPost)
self.activityIndicatorView.stopAnimating()
self.tableView.reloadData()
})
}
}
func fetchUser(uid: String, completed: #escaping () -> Void) {
API.User.observeUser(withID: uid) { user in
self.users.append(user)
completed()
}
}
}
extension HomeViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "HomeTableViewCell", for: indexPath) as! HomeTableViewCell
cell.post = posts[indexPath.row]
cell.user = users[indexPath.row]
cell.homeVC = self
return cell
}
}
I have a lot of craziness going on in my project so let me know if you have any questions and what I'm doing wrong. If it's too complicated to understand I'm ready to erase everything and start over too.
And I do honestly think that I followed all the guidelines to ask a question so don't like shut this question down or something.
That's a lot of code. Try this super reduced example. For this, the users node only stores the name as a child node but it could also have an image, email, address, etc.
Example users node
users
uid_0:
name: "Bert"
uid_1:
name: "Ernie"
and some code
var usersArray = [ [String: Any] ]() //an array of dictionaries.
class ViewController: UIViewController {
//set up firebase references here
override func viewDidLoad() {
super.viewDidLoad()
let usersRef = self.ref.child("users")
usersRef.observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot.children {
let snap = child as! DataSnapshot
let userDict = snap.value as! [String: Any]
self.usersArray.append(userDict)
}
self.tableView.reloadData()
})
and the tableView delegate methods
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.usersArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "HomeTableViewCell", for: indexPath) as! HomeTableViewCell
let userDict = self.usersArray[indexPath.row]
cell.text = userDict["name"] as! String
//cell.imge = userDict["image"] etc etc
return cell
}
Now... that all being said. This is the perfect use for an array of UserClass objects instead of the dictionaries.
Here's a starting point....
class UserClass {
var name = ""
var image = ""
func init(snap: DataSnapshot) {
//populate the vars from the snapshot
}
}
var userClassArray = [UserClass]()
Don't copy and paste this as there are probably typos but it should point you in the right direction.