I've been trying to add a search users mechanism for quite a while now, but to no success. I can't seem to find much out there, and where there is explanation it is almost always for FirebaseDatabase rather than Firestore. I'm very new to Swift, Firebase and coding in general so any explanation about where I'm going wrong with this, and how I can implement this would be appreciated beyond words.
This is my latest attempt (I'm sure this will make many of you shudder):
import UIKit
import Firebase
import FirebaseAuth
import FirebaseFirestore
class FindUsersViewController: UITableViewController {
var usersArray = [String]()
var filteredUsers = [String]()
override func viewDidLoad() {
super.viewDidLoad()
fetchUser()
// Do any additional setup after loading the view.
}
func fetchUser() {
let db = Firestore.firestore()
let usernameSearch = db.collection("users")
usernameSearch.getDocuments { (snapshot, error) in
if error != nil {
print("Error obtaining usernames")
} else {
for document in snapshot!.documents {
let field = document.data()["username"]
self.usersArray.append(field as! String)
print(self.usersArray)
}
}
}
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 5
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "Cell")
cell.textLabel?.text = usersArray
return cell
}
}
I can't quite seem to work it out, and because I'm new to everything, the Firebase Documentation seems more mystifying than enlightening, so any help with this would be greatly appreciated!
(I think I'm going wrong with Arrays...maybe...idk...)
--
I should add:
I have a collection of "users", with documents for each user containing the "username" field (along with uid, password and email fields).
You're almost there!
First let's create a User object that will represent all the properties for your user. For now let's say a user has an id, an name and an email, but feel free to match your project requirements:
import FirebaseFirestore
struct User {
let id: String
let name: String
let email: String
}
We also add an extension with a convenience initializer for our User, this will allow us to convert quickly and easily a Firestore Document to a User:
extension User {
init(queryDocument: QueryDocumentSnapshot) {
guard let name = queryDocument.data()["name"] as? String else { fatalError() }
guard let email = queryDocument.data()["email"] as? String else { fatalError() }
self.init(id: queryDocument.documentID, name: name, email: email)
}
}
Now, this is how your FindUsersViewController could look like:
import UIKit
import FirebaseFirestore
class FindUsersViewController: UITableViewController {
// Keep a reference to your database, and your users collection:
private let database = Firestore.firestore()
private lazy var usersReference = database.collection("users")
private let cellReuseIdentifier = "cellReuseIdentifier"
// This is the array that will keep track of your users, you will use it to populate your table view data:
private var users = [User]()
override func viewDidLoad() {
super.viewDidLoad()
// Set your table view datasource and delegate:
tableView.dataSource = self
tableView.delegate = self
// Register your cell identifier:
tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellReuseIdentifier)
fetchUsers()
}
private func fetchUsers() {
// Create your query, for instance we will return only the first 10 users and ordered by name:
let query = usersReference.order(by: "name", descending: true).limit(to: 10)
// Now simply fetch the documents that match the query:
query.getDocuments { [weak self] (querySnapshot, error) in
guard let self = self else { return }
guard let querySnapshot = querySnapshot, error == nil else {
// Oops, something wrong happened here
return
}
// You get successfully your documents here, so just transform them to your User model:
let users = querySnapshot.documents.map { User(queryDocument: $0) }
// Then fill your array, and make sure you reload your table data on the main thread:
DispatchQueue.main.async {
self.users = users
self.tableView.reloadData()
}
}
}
// MARK: UITableViewDataSource methods
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Returns the number of users in your array:
return users.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier, for: indexPath)
// Configure your cell here via your users array:
cell.textLabel?.text = users[indexPath.row].name
return cell
}
// MARK: UITableViewDelegate methods
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
}
This will give you a nice list of your users. I added a lot of comments in the view controller so I think it's pretty self-explanatory, but feel free to let me know if you still have a doubt.
Good luck with your app! :)
Related
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 2 years ago.
Improve this question
I have a view with a list of items with an Add button leading to another view in which you can add new items. The problem is what happens after you add the item. The new item appears correctly in the Firebase database but is not visible when you return to the list. I'm a little confused because I'm not sure what I'm doing wrong. I am loading data again in the overrided functions such as vievDidLoad and vievWillAppear, but to be honest, new items do not appear until the application is closed and restarted.There is my code of my list:
import UIKit
import Firebase
import FirebaseFirestore
class MainViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var listOfItemsTableView: UITableView!
var elements: [Element] = []
override func viewDidLoad() {
super.viewDidLoad()
let db = Firestore.firestore()
db.collection("recipes").whereField("uid", isEqualTo: Auth.auth().currentUser!.uid).getDocuments() { (snapshot, err) in
if let snapshot = snapshot {
for doc in snapshot.documents {
if let itemName = doc.get("name") as? String {
self.elements.append(Element(name: itemName))
}
}
self.listOfItemsTableView.reloadData()
} else {
if let err = err {
print(err)
}
}
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.listOfItemsTableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return elements.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "elementCell", for: indexPath) as! ElementCell
cell.elementNameLabel.text = elements[indexPath.row].name
return cell
}
#IBAction func addItemButtonClicked(_ sender: Any) {
self.performSegue(withIdentifier: "toAddItemView", sender: self)
}
}
class Element {
var name = ""
convenience init(name: String) {
self.init()
self.name = name
}
}
That is because you're using getDocuments, which is a one-time get of the query. What you want are realtime updates of the query, which requires adding a snapshot listener.
var elements = [Element]()
db.collection("recipes").whereField("uid", isEqualTo: Auth.auth().currentUser!.uid).addSnapshotListener({ (snapshot, error) in
if let snapshot = snapshot {
var elementsTemp = [Element]()
for doc in snapshot.documents {
if let itemName = doc.get("name") as? String {
elementsTemp.append(Element(name: itemName))
}
}
self.elements = elementsTemp
self.listOfItemsTableView.reloadData()
} else {
if let error = error {
print(error)
}
}
})
You'll want to populate a temporary array inside the return and then hand that off to the datasourced array because updating the datasourced array incrementally (first removing all of its elements and then reconstructing it one element at a time) while the user may be scrolling is very prone to throwing an out-of-bounds error.
I have a firestore database that has a number of orders in a restaurant app. I want to group these orders by the table number so as to present the user with a bill/check for each table number.
I have spent a lot of time researching it and I believe I need to using Dictionary and Grouping. As you will see from the code below I have made an attempt which I think might be close.
Here is how my data is structured in firestore:
Here is my bill struct:
import Foundation
struct Bill {
var billID: String
var userID: String
var tableNumber: String
var itemsOrdered: [[String: Any]]
}
And here is my main view controller:
import FirebaseAuth
import FirebaseFirestore
import UIKit
class BillsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var billTable: UITableView!
var billsArray = [Bill]()
var groupedBills = [[Bill]]()
let db = Firestore.firestore()
func getData() {
let userID = Auth.auth().currentUser!.uid
db.collection("Orders")
.whereField("User_ID", isEqualTo: userID)
.whereField("Order_Complete", isEqualTo: true)
.addSnapshotListener { querySnapshot, error in
if let error = error {
print("Error:\(error.localizedDescription)")
} else {
self.billsArray.removeAll()
for document in querySnapshot!.documents {
let userID = document.get("User_ID") as! String
let tableNumber = document.get("Table_Number") as! String
let docID = document.documentID
let itemsOrdered = document.get("Items_Ordered") as! [[String: Any]]
let bill = Bill(billID: docID, userID: userID, tableNumber: tableNumber, itemsOrdered: itemsOrdered)
self.billsArray.append(bill)
}
}
let groupedDictionaryInFunc = Dictionary(grouping: self.billsArray) { (bill) -> String in
bill.tableNumber
}
print(self.groupedBills)
let keys = groupedDictionaryInFunc.keys.sorted()
print("Keys\(keys)")
keys.forEach {
self.groupedBills.append(groupedDictionaryInFunc[$0]!)
}
}
billTable.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("From table func: \(groupedBills)")
return groupedBills[section].count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "billCell", for: indexPath)
let bill = groupedBills[indexPath.section][indexPath.row]
cell.textLabel?.text = "\(bill.itemsOrdered)"
return cell
}
func numberOfSections(in tableView: UITableView) -> Int {
return groupedBills.count
}
override func viewDidLoad() {
super.viewDidLoad()
getData()
billTable.delegate = self
billTable.dataSource = self
}
}
The expected (or desired!) result is it populates my tableview with each section title the table number and then the the contents of the section being the items that were ordered.
Currently I'm getting a blank and my grouped array appears to be empty.
Any help would be appreciated!
I can load my current tableview data onto the database and then print out the new data onto my console but can't get the new data back into the tableview and I'm tearing my hair out because I know it should be simple!
I've tried all sorts of things but I just can't figure out where I'm going wrong.
//Saves to database without any problems
//Class
var ref: DatabaseReference!
//ViewDidLoad
ref = Database.database().reference()
func save()
{
let ref = Database.database().reference(withPath: "Admin")
let adding = ref.child(me)
let addData: [String: [String]] = ["addJokes": data]
adding.setValue(addData)
{
(error:Error?, ref:DatabaseReference) in
if let error = error
{
print("Data could not be saved: \(error).")
}
else
{
print("Data saved successfully!")
}
}
}
Can print out the database data to my console but can't get it into my tableview
let ref = Database.database().reference(withPath: "Admin")
ref.observe(.value, with:
{
(snapshot) in
let new = snapshot.value as? String
print(snapshot.value as Any)
if let newData = new
{
self.data.append(newData)
self.mainTable.reloadData()
}
})
Update
TableView details-
TableView Class Ext
extension TableView: UITableViewDataSource, UITableViewDelegate
{
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
if isSearching {
return filteredArray.count
}
else
{
return data.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
var array: String?
if isSearching
{
array = filteredArray[indexPath.row]
}
else
{
array = data[indexPath.row]
}
let cell = mainTable.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as UITableViewCell
cell.textLabel?.text = array
return cell
}
TableView Class-
class TableView: UIViewController
{
let cellId = "cellId"
var filteredArray = [String]()
var ref: DatabaseReference!
var data = [
"""
multiple line
data array
"""
]
lazy var mainTable: UITableView =
{
let table = UITableView()
table.translatesAutoresizingMaskIntoConstraints = false
table.register(UITableViewCell.self, forCellReuseIdentifier: cellId)
return table
}()
override func viewDidLoad() {
super.viewDidLoad()
mainTable.delegate = self
mainTable.dataSource = self
}
Console prints exactly what I want back into my tableview. Turning print function into results is usually the easy part.
The problem lies in let new = snapshot.value as? String. Here, new is null thus if let newData = new is always false and if block won't be executed. First, check snapshot.value's data type and value then use it accordingly.
I am trying to use RealmSwift in order to save items to the phone storage in Swift 4. I have two different Views; one for the save functionality and another which will display all saved items into a TableView. I have a buildable form coded but i am throwing an error Thread 1: signal SIGABRT specifically on the line when i call realm.add. When i am in my view which is saving, i am using a IBAction with a button to initiate the save functionality. Can anyone help me with this issue? I think the issue is when i set the var of realm however i am unsure.
UPDATE:
I have changed my implementation to reflect the idea given in this thread about my original issue. After doing so, when the call to add the item to the realm is called i crash EXC_BAD_ACCESS (code=EXC_I386_GPFLT) inside the source code of the API. Specifically I crash at this function of the API
//CODE EXCERPT FROM REALMSWIFT API
// Property value from an instance of this object type
id value;
if ([obj isKindOfClass:_info.rlmObjectSchema.objectClass] &&
prop.swiftIvar) {
if (prop.array) {
return static_cast<RLMListBase *>(object_getIvar(obj,
prop.swiftIvar))._rlmArray;
}
else { // optional
value = static_cast<RLMOptionalBase *>(object_getIvar(obj,
prop.swiftIvar)).underlyingValue; //CRASH OCCURS HERE!!!!!!!!
}
}
else {
// Property value from some object that's KVC-compatible
value = RLMValidatedValueForProperty(obj, [obj
respondsToSelector:prop.getterSel] ? prop.getterName : prop.name,
_info.rlmObjectSchema.className);
}
return value ?: NSNull.null;
import UIKit
import RealmSwift
class DetailsViewController: UIViewController {
var titleOfBook: String?
var author: String?
#IBAction func SavetoFavorites(_ sender: Any) {
DispatchQueue.global().async { [weak self] in
guard let strongSelf = self else { return }
guard let realm = try? Realm() else {
return
}
let newItem = Favorites()
newItem.title = strongSelf.titleOfBook
newItem.author = strongSelf.author
try? realm.write {
realm.add(newItem) // Crashes on this line
}
}
}
import UIKit
import RealmSwift
final class Favorites: Object {
var title: String?
var author: String?
}
class FavoritesTableViewController: UITableViewController {
var items: Array<Favorites> = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UITableViewCell.self, forCellReuseIdentifier:
"cell")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 0
}
override func tableView(_ tableView: UITableView?,
numberOfRowsInSection section: Int) -> Int {
return items.count
}
override func tableView(_ tableView: UITableView, cellForRowAt
indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell",
for: indexPath)
let item = items[indexPath.row]
cell.textLabel?.text = item.title
cell.detailTextLabel?.text = item.author
return cell
}
var selectedIndexPath: NSIndexPath = NSIndexPath()
override func tableView(_ tableView: UITableView, willSelectRowAt
indexPath: IndexPath) -> IndexPath? {
selectedIndexPath = indexPath as NSIndexPath
return indexPath
}
You have to wrap realm.add(newItem) inside a transaction:
try! realm.write {
realm.add(newItem)
}
Please note, that write transactions block the thread they are made on so if you're writing big portions of data you should do so on background thread (realm has to be instantiated on that thread too). You could do it like this:
#IBAction func saveToFavorites(_ sender: Any) {
DispatchQueue.global().async { [weak self] in
guard let strongSelf = self else { return }
guard let realm = try? Realm() else {
// avoid force unwrap, optionally report an error
return
}
let newItem = Favorites()
newItem.title = strongSelf.titleOfBook
newItem.author = strongSelf.author
try? realm.write {
realm.add(newItem)
}
}
}
Update: I haven't noticed that you have an issue with your model too – since Realm is written with Objective C you should mark your model properties with #objc dynamic modifiers:
final class Favorites: Object {
#objc dynamic var title: String?
#objc dynamic var author: String?
}
All changes to Realm managed objects (either creation, modification or deletion) need to happen inside write transactions.
do {
try realm.write {
realm.add(newItem)
}
} catch {
//handle error
print(error)
}
For more information, have a look at the writes section of the official docs.
Another problem you have in there is that in your Favorites class properties are missing #objc dynamic attributes. You can read about why you need that in realm docs.
Your code should look like this then:
final class Favorites: Object {
#objc dynamic var title: String?
#objc dynamic var author: String?
}
I can't seem to get this right. I want to get core data from my Database and display all in table view. Running this only displays the last ID multiple times on my table. Could someone advise what I'm doing wrong and/or possibly assist? Thanks.
import Foundation
import CoreData
extension MyFavourites {
#NSManaged var id: String?
}
-
var myFavs : [MyFavourites]?
override func viewDidLoad() {
super.viewDidLoad()
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let context: NSManagedObjectContext = appDel.managedObjectContext
let freq = NSFetchRequest(entityName: "MyFavourites")
freq.returnsObjectsAsFaults = false
do {
myFavs = try context.executeFetchRequest(freq) as? [MyFavourites]
} catch _ {
myFavs = nil
}
tableView.reloadData()
}
-
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return (myFavs?.count)!
}
-
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
if myFavs!.count > 0 {
for result: AnyObject in myFavs! {
if let favID: String = result.valueForKey("id") as? String {
cell.textLabel?.text = favID
}
}
} else {
print("No Record")
}
return cell
}
If I am reading your code correctly, it will display last recorded favId in all cells. The cellForRowAtIndexPath asks you for value for current cell, but instead of providing that, you loop through all of them and repeatedly assign the same label with favID rewriting it multiple times. At the end of the cycle the label will have the last ID from the list.
You need to remove the loop and assign cell.label.text with ID value from myFavs[indexPath.row].