Why is this Swift struct instantiation crashing? - ios

I have a Swift struct Reflection like this:
struct Reflection {
let title: String
let body: String
let author: String
let favorite: Bool
let creationDate: Date
let id: UUID
}
extension Reflection {
var plistRepresentation: [String: AnyObject] {
return [
"title": title as AnyObject,
"body": body as AnyObject,
"author": author as AnyObject,
"favorite": favorite as AnyObject,
"creationDate": creationDate as AnyObject,
"id": id as AnyObject
]
}
init(plist: [String: AnyObject]) {
title = plist["title"] as! String
body = plist["body"] as! String
author = plist["author"] as! String
favorite = plist["favorite"] as! Bool
creationDate = plist["creationDate"] as! Date
id = plist["id"] as! UUID
}
}
class StorageController {
fileprivate let documentsDirectoryURL = FileManager.default
.urls(for: .documentDirectory, in: .userDomainMask)
.first!
fileprivate var notesFileURL: URL {
return documentsDirectoryURL
.appendingPathComponent("Notes")
.appendingPathExtension("plist")
}
func save(_ notes: [Reflection]) {
let notesPlist = notes.map { $0.plistRepresentation } as NSArray
notesPlist.write(to: notesFileURL, atomically: true)
}
func fetchNotes() -> [Reflection] {
guard let notePlists = NSArray(contentsOf: notesFileURL) as? [[String: AnyObject]] else {
return []
}
return notePlists.map(Reflection.init(plist:))
}
}
class StateController {
fileprivate let storageController: StorageController
fileprivate(set) var notes: [Reflection]
init(storageController: StorageController) {
self.storageController = storageController
self.notes = storageController.fetchNotes()
}
func add(_ note: Reflection) {
notes.append(note)
storageController.save(notes)
}
func update(_ note: Reflection) {
for (index, storedNote) in notes.enumerated() {
guard storedNote.id == note.id else {
continue
}
notes[index] = note
storageController.save(notes)
break
}
}
}
Instantiating a Reflection like this in viewWillAppear crashes my app:
import UIKit
class NotesViewController: UIViewController {
var stateController: StateController!
fileprivate var dataSource: FeedDataSource!
#IBOutlet var tableView: UITableView!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let reflection = Reflection(title: "Hello", body: "world", author: "Alex", favorite: true, creationDate: Date(), id: UUID())
//stateController.add(reflection)
dataSource = FeedDataSource(notes: stateController.notes)
tableView.dataSource = dataSource
tableView.reloadData()
}
class FeedDataSource: NSObject {
var notes: [Reflection]!
init(notes: [Reflection]) {
self.notes = notes
}
}
extension FeedDataSource: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return notes.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reflectionCell", for: indexPath) as! ReflectionCell
let index = indexPath.row
let note = notes[index]
cell.model = ReflectionCell.Model(data: note)
return cell
}
}
The cell class:
class ReflectionCell: UITableViewCell {
#IBOutlet weak fileprivate var titleLabel: UILabel!
#IBOutlet weak fileprivate var bodyLabel: UILabel!
#IBOutlet weak fileprivate var authorLabel: UILabel!
#IBOutlet weak fileprivate var bookmarkButton: UIButton!
fileprivate var id: UUID!
var model: Model? {
didSet {
guard let model = model else {
return
}
titleLabel.text = model.title
bodyLabel.text = model.body
authorLabel.text = model.author
bookmarkButton.isSelected = model.favorite
id = model.id
}
}
override func awakeFromNib() {
super.awakeFromNib()
bookmarkButton.setImage(#imageLiteral(resourceName: "Bookmark-Highlighted"), for: .selected)
}
}
extension ReflectionCell {
struct Model {
let title: String
let body: String
let author: String
let favorite: Bool
let id: UUID
init(data: Reflection) {
title = data.title
body = data.body
author = data.author
favorite = data.favorite
id = data.id
}
}
}
I get no console output, just a main thread SIGABRT error. What could be going on?

Like an idiot I was cleaning up my code and commented out the line registering the nib for the reuse identifier. However, I do think it would help if Xcode could print out a useful error message for such a mistake.

Related

How to save data from one view controller and show in another view controller using core data

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!

how do I fetch data from the Firestore?

I'm trying to pass data onto my tableview, from my cloud firestore but it just not able to retrieve the data and post it on the tableview and so far most of of my attempts have failed. since I'm transition from real-time database to Firestore.
I've used multiple resources on stack, restructured my code multiple times and have now come down to this
here is also an image of my collection in Firestore firestore collection
import Foundation
class ProductList {
var id: String?
var name: String?
var dispensaryName: String?
var category: String?,
var brand: String?
var imageUrl: String?
init(id: String?,
name: String?,
dispensaryName: String?,
brand: String?,
category: String?,
imageUrl: String?) {
self.id = id
self.name = name
self.dispensaryName = dispensaryName
self.brand = brand
self.category = category,
self.imageUrl = imageUrl
}
}
import UIKit
class ProductListCell: UITableViewCell {
#IBOutlet weak var productImage: UIImageView!
#IBOutlet weak var dispensaryName: UILabel!
#IBOutlet weak var productName: UILabel!
#IBOutlet weak var categoryLabel: UILabel!
#IBOutlet weak var categoryStrain: UILabel!
}
import UIKit
import Firebase
import FirebaseFireStore
class ProductListController: UIViewController {
#IBOutlet weak var productListTableView: UITableView!
#IBOutlet weak var menuButton: UIBarButtonItem!
var dbRef: DatabaseReference!
var productSetup: [ProductList] = []
override func viewDidLoad() {
super.viewDidLoad()
productListTableView.dataSource = self
productListTableView.delegate = self
self.productListTableView.rowHeight = UITableView.automaticDimension
self.productListTableView.estimatedRowHeight = 363
menuButton.target = self.revealViewController()
menuButton.action = #selector(SWRevealViewController.revealToggle(_:))
self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
dbRef = Database.database().reference().child("products");
//observing the data changes
dbRef.observe(DataEventType.value, with: { (snapshot) in
//if the reference have some values
if snapshot.childrenCount > 0 {
//clearing the list
self.productSetup.removeAll()
//iterating through all the values
for producting in snapshot.children.allObjects as! [DataSnapshot] {
//getting values
let productObject = producting.value as? [String: AnyObject]
let id = productObject?["id"]
let name = productObject?["name"]
let dispensaryName = productObject?["dispensaryName"]
let category = productObject?["category"]
let strain = productObject?["strain"]
let imageUrl = productObject?["imageUrl"]
//creating artist object with model and fetched values
let massProducts = ProductList(id: id as! String?,
name: name as! String?,
dispensaryName: dispensaryName as! String?,
category: category as! String?,
strain: strain as! String?,
imageUrl: imageUrl as! String?)
//appending it to list
self.productSetup.append(massProducts)
}
//reloading the tableview
print(self.productSetup)
self.productListTableView.reloadData()
}
})
}
}
extension ProductListController: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return productSetup.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ProductListCell") as!
ProductListCell
let production: ProductList
production = productSetup[indexPath.row]
cell.productName.text = "\(String(describing: production.brand)): \(String(describing: production.name))"
cell.dispensaryName.text = production.dispensaryName
cell.categoryLabel.text = production.category
cell.productImage.text = production.imageUrl
return cell
}
}
I have reformatted the code quickly to make it easier to understand but it could be one of many things;
Check user authenticated with firebase on the device.
Ensure you have setup security settings correctly to allow reads in firebase.
Reformatted Code
ProductListController.swift
import Firebase
class ProductListController: UIViewController {
#IBOutlet weak var productListTableView: UITableView!
#IBOutlet weak var menuButton: UIBarButtonItem!
var productSetup = [ProductList]()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override func viewDidLoad() {
super.viewDidLoad()
productListTableView.dataSource = self
productListTableView.delegate = self
productListTableView.rowHeight = UITableView.automaticDimension
productListTableView.estimatedRowHeight = 363
menuButton.target = self.revealViewController()
menuButton.action = #selector(SWRevealViewController.revealToggle(_:))
self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
fetchProducts { (products) in
self.productSetup = products
self.productListTableView.reloadData()
}
}
func fetchProducts(_ completion: #escaping ([ProductList]) -> Void) {
let ref = Firestore.firestore().collection("products")
ref.addSnapshotListener { (snapshot, error) in
guard error == nil, let snapshot = snapshot, !snapshot.isEmpty else {
return
}
completion(snapshot.documents.compactMap( {ProductList(dictionary: $0.data())} ))
}
}
}
extension ProductListController: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return productSetup.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "ProductListCell") as?
ProductListCell else { return UITableViewCell() }
cell.configure(withProduct: productSetup[indexPath.row])
return cell
}
}
ProductListCell.swift
import Firebase
class ProductListCell: UITableViewCell {
#IBOutlet weak var productImage: UIImageView!
#IBOutlet weak var dispensaryName: UILabel!
#IBOutlet weak var productName: UILabel!
#IBOutlet weak var categoryLabel: UILabel!
#IBOutlet weak var categoryStrain: UILabel!
func configure(withProduct product: ProductList) {
productName.text = "\(String(describing: product.brand)): \(String(describing: product.name))"
dispensaryName.text = product.dispensaryName
categoryLabel.text = product.category
fetchImage(withURL: product.imageUrl ) { (image) in
productImage.image = image
}
}
func fetchImage(withURL url: String, _ completion: #escaping (UIImage) -> Void) {
let ref = Storage.storage().reference(forURL: url)
ref.getData(maxSize: 1 * 1024 * 1024) { (data, error) in
guard error == nil, let imageData = data, let image = UIImage(data: imageData) else {
return
}
completion(image)
}
}
}
ProductList.swift
class ProductList {
var id: String
var name: String
var dispensaryName: String
var category: String
var brand: String
var imageUrl: String
init(id: String, name: String, dispensaryName: String, brand: String, category: String, imageUrl: String) {
self.id = id
self.name = name
self.dispensaryName = dispensaryName
self.brand = brand
self.category = category
self.imageUrl = imageUrl
}
convenience init(dictionary: [String : Any]) {
let id = dictionary["id"] as? String ?? ""
let name = dictionary["name"] as? String ?? ""
let dispensaryName = dictionary["dispensaryName"] as? String ?? ""
let brand = dictionary["brand"] as? String ?? ""
let category = dictionary["category"] as? String ?? ""
let imageUrl = dictionary["imageUrl"] as? String ?? ""
self.init(id: id, name: name, dispensaryName: dispensaryName, brand: brand, category: category, imageUrl: imageUrl)
}
}
I Hope you found this helpful.

The app crashes when adding new keys in firestore

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.

How to pull users from database and list them in a table view using firebase?

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.

Table view cell information resetting when scrolling

I am currently using a table view to display a menu, in which people can click on the add or subtract buttons to increase/decrease their order.
This table view has approximately 30 items and so you have to scroll down to get to all the menu items. The problem is, when you scroll down, the table view cells above the scroll (that are now hidden) lose the data that they have just contained.
For example, if you have a menu item that you have ordered 2 of an item, that 2 on the label has now turned back to 0. This is very object oriented so I am not sure why this is happening.
My table view class:
#IBOutlet weak var appetizerTableView: UITableView!
var appetizerList = [OrderModel]()
let ref = FIRDatabase.database().reference()
override func viewDidLoad() {
appetizerTableView.delegate = self
appetizerTableView.dataSource = self
ref.child("Broadway").child("AppetizerDishes").observeSingleEventOfType(.Value, withBlock: { snapshot in
if let snapshot = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshot {
print("WILL: \(snap)")
if let postDict = snap.value as? Dictionary<String, AnyObject> {
let key = snap.key
let post = OrderModel(postkey: key, postData: postDict)
self.appetizerList.append(post)
}
}
}
self.appetizerTableView.reloadData()
})
}
var data=[OrderModel]()
func addButtonAction(addedList:[String:Float]) {
print("WILLCOHEN:\(addedList)")
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return appetizerList.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let post = appetizerList[indexPath.row]
if let cell = appetizerTableView.dequeueReusableCellWithIdentifier("OrderCell") as? OrderCell{
cell.configureCell(post)
cell.delegate = self
return cell
} else {
return OrderCell()
}
}
}
My cell model class:
class OrderModel {
private var _dishName: String!
private var _dishDescription: String!
private var _numberOfOrders: Int!
private var _postKey: String!
private var _dishPrice: Float!
var dishName: String {
return _dishName
}
var dishDescription: String {
return _dishDescription
}
var numberOfOrders: Int {
get {
return _numberOfOrders
}
set (newVal) {
_numberOfOrders = newVal
}
}
var postKey: String {
return _postKey
}
var dishPrice: Float {
return _dishPrice
}
init(dishName: String, dishDescription: String, numberOfOrders: Int) {
self._dishName = dishName
self._dishDescription = dishDescription
}
init(postkey: String, postData: Dictionary<String, AnyObject>) {
self._postKey = postkey
if let dishName = postData["dishName"] as? String {
self._dishName = dishName
}
if let dishDescription = postData["dishDescription"] as? String {
self._dishDescription = dishDescription
}
if let numberOfOrders = postData["anumberOfOrders"] as? Int {
self._numberOfOrders = numberOfOrders
}
if let dishPrice = postData["dishPrice"] as? Float32 {
self._dishPrice = dishPrice
}
}
}
My cell class:
protocol ClassNameDelegate:class {
func addButtonAction(addedList:[String:Float])
}
var addedList: [String:Float] = [:]
class OrderCell: UITableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
}
var post: OrderModel!
var link: Link!
#IBOutlet weak var dishName: UILabel!
#IBOutlet weak var dishDescriptionAndPrice: UILabel!
#IBOutlet weak var numberOfOrders: UILabel!
#IBOutlet weak var addOrderBtn: UIButton!
#IBOutlet weak var subtractOderBtn: UIButton!
weak var delegate: ClassNameDelegate?
#IBAction func addButtonPressed(sender: AnyObject) {
if post.numberOfOrders == 9 {
numberOfOrders.text = "9"
} else {
if addedList[post.dishName] != nil {
addedList[post.dishName] = post.dishPrice
} else {
addedList["\(post.dishName) \(Int(numberOfOrders.text!)! + 1)"] = post.dishPrice
}
post.numberOfOrders = post.numberOfOrders - 1
numberOfOrders.text = "\(post.numberOfOrders)"
}
if delegate != nil {
delegate?.addButtonAction(addedList)
}
}
#IBAction func subtractButtonPressed(sender: AnyObject) {
if post.numberOfOrders == 0 {
numberOfOrders.text = "0"
} else {
post.numberOfOrders = post.numberOfOrders + 1
numberOfOrders.text = "\(post.numberOfOrders)"
}
}
func getOrders() -> Dictionary<String, Float> {
return addedList
}
func configureCell(post: OrderModel) {
self.post = post
self.dishName.text = post.dishName
self.dishDescriptionAndPrice.text = post.dishDescription
self.numberOfOrders.text = "0"
}
}
I should mention that I am pulling my table view data from Firebase.
Thank you in advance for any help or suggestions, it is very much appreciated.
You are calling configureCell(post:) on every cell, and in the configureCell(post:) function you set the text value of the numbersOfOrders label to "0". You should probably set the text to a value in the OrderModel/post argument.
Also, you should be sure to always call configureCell(post:) (you are not calling it if you have to manually create an OrderCell), so your tableView(tableView:cellForRowAtIndexPath:) function should look like this:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let post = appetizerList[indexPath.row]
let cell = appetizerTableView.dequeueReusableCellWithIdentifier("OrderCell") as? OrderCell ?? OrderCell()
cell.configureCell(post)
cell.delegate = self
return cell
}

Resources