Compare different rows and make them one at tableview - Firebase/Swift 3 - ios

I am new in Swift 3 and Firebase programming, but I am making good progress. But sometimes things come harder and here I am again because I could not find some text as specifically as I need, neither updated for Swift 3. I have tried many many ways.
The issue is that I am building an app for some teachers to make a kind of examination with their students. Each Student has the same examination by two different teachers at different times and the results are shown at a table view controller like the following image:
As you can see, I have two rows for each student with two different scores (partial). What I wish is to filter this Firebase data, search for those two “repeated” students and make an average score from them and show the data at a table view controller (exam1 + exam2 / 2) for the final average score. This is my currently Firebase structure (I will post an alternative later at this question):
Now, parts of the code:
1. Struct for variables and snapshot
import Foundation
import Firebase
struct Examination {
var nome: String?
var preceptor: String?
var dataHoje: String?
var nota: String?
var key: String?
init(nome: String, preceptor: String, nota: String, dataHoje: String, key: String) {
self.nome = nome
self.preceptor = preceptor
self.nota = nota
self.dataHoje = dataHoje
self.key = key
}
init(snapshot: FIRDataSnapshot) {
key = snapshot.key
let snapshotValue = snapshot.value as? [String: AnyObject]
nome = snapshotValue?["nome"] as? String
preceptor = snapshotValue?["preceptor"] as? String
nota = snapshotValue?["nota"] as? String
dataHoje = snapshotValue?["dataHoje"] as? String
notaAtitude = snapshotValue?["notaAtitude"] as? String
}
func toAnyObject() -> Any {
return [
"nome": nome,
"preceptor": preceptor,
"nota": nota,
"dataHoje": dataHoje,
"userKey": key,
]
}
}
Load method for main table view controller
import Foundation
import Firebase
var refAlunos: [Examination] = []
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
Load_HM1()
}
func Load_HM1() {
let refW = FIRDatabase.database().reference().child("semestre_1_2017").child("avaliacaoHM12017").child("cheklists")
refW.queryOrdered(byChild: "nome").observe(.value, with: {(snapshot) in
var newTeste2: [Examination] = []
for resposta in snapshot.children {
let itemsAadicionar = Examination(snapshot: resposta as! FIRDataSnapshot)
newTeste2.append(itemsAadicionar)
}
self.refAlunos = newTeste2
})
}
At last, the Firebase structure I have also tried, but I always receive nil as result, table view empty:
The code for this alternative way (method load), I could not implement:
func Load_HM1() {
let refW = FIRDatabase.database().reference().child("semestre_1_2017").child("avaliacaoHM12017").child("cheklists") ... I CRASHED HERE..
// The rest of this method is the same shown before
So, resuming the issue, I need a way to compare rows with the same student name (always will be 2) and calculate the average of these two scores and publish a table view with this final score and name, almost as the first image, but only changing the score to the final average. I really don’t know if there is a way and I don’t know which way to go. I have searched a lot, but nothing found. I guess this second firebase structure could be more friendly, but my rows are empty. If need more information to understand this issue, ask me that I update the question. Thank you very much for any help.

I'm not aware of Firebase providing such functionalities out of the box, therefore I would recommend doing your calculations manually.
Before setting self.refAlunos at the end of the callback function you can extract the corresponding pairs of examinations, compute the average grade for each pair and create a new instance containing the computed average grade and the remaining attributes. Afterwards you can store an array of the generated examinations in self.refAlunos.
Additionally, you should take a look at Structure Your Database and Structuring your firebase data. Applying these guidelines will help to flatten your data and improve performance as your data grows.
Edit:
I'm currently not on my Mac, but I hope my suggestions can point you in the right direction, although they might not be 100% syntactically correct.
func Load_HM1() {
let refW = FIRDatabase.database().reference().child("semestre_1_2017").child("avaliacaoHM12017").child("cheklists")
refW.queryOrdered(byChild: "nome").observe(.value, with: {(snapshot) in
var newTeste2: [Examination] = []
var examinations: [Examination] = []
var position = -1
for resposta in snapshot.children {
let itemsAadicionar = Examination(snapshot: resposta as! FIRDataSnapshot)
newTeste2.append(itemsAadicionar)
for exam in 0..<examinations.count {
if (examinations[exam].nome == itemsAadicionar.nome) {
position = exam
break
}
}
if (position != -1) {
examinations[position].nota += itemsAadicionar.nota
examinations[position].nota /= 2
position = -1
}
}
self.refAlunos = examinations
})
}
I have edited your code and I hope you will get the gist. However, this approach is only suitable if you always have two exams per student. If you have more, you need to adapt it a little bit.

Related

How can I create an algorithm that appends a name onto an array?

I've been going through Glassdoors for companies I am interested in interviewing with and saw one that said the company asked them a question about "appending a name onto a list". I have been trying to figure out how to go about that. I know the function will take an array and will return an array as well. Can someone help me write that out and explain it to me? Attached is my version, which does not work. It just prints out the existing string twice
func addNameToList(_ names: [String]) -> [String] {
var names = ["Ben", "Sam", "Ken"]
var results = [names[0]]
for name in names {
names.append(name)
print(names)
}
return results
}
addNameToList([String("Louis")])
if you are need to add one or more string value with Existing string array you can use like below
var globalArrayString = ["One","two","three"]
func appendString(_ currentString: String...) -> [String] {
globalArrayString.append(contentsOf: currentString)
return globalArrayString
}
// if you need to add more String,Call like below
let combinedArray = appendString("four","five")
print(combinedArray)

Pulling Data from Firebase into Array

I was recently told to structure my Firebase differently. Before I was putting everything related to a particular user under his or her tree. I was told however to flatten it and create his or her nodes separately and then to just link that node into that users tree when you need to.
So my tree looks like this
root
card
*card autoID*
nickname: "foo"
type: "bar"
user
*user uid*
card
*card autoID*: true
I am going to add more to the card as the user progresses through the app, and if I understand how I am supposed to structure the data I will be adding it to the the card node since that card is linked to the user.
My question is how do I pull data from Firebase then into say an array or a dictionary? If it was all in one tree I would do something like this
let ref = FIRDatabase.database().reference()
let user = FIRAuth.auth()?.currentUser
let userCard = ref.child((user?.uid)!).child("card")
But since that card under the user is only a reference how do I then go to the real place where the card is...the part that has the nickname and type?
Edit
So with some help from other SO posts, the documentation, and a friend I have the code 90% working.
What I am able to do is
1) find all of the card autoID under the user node that is associated to the user and store those strings into an array # 1
2) I am able to query all of the card autoID under the node card and then find the ones that match what is in array # 1 and store them in array # 2 (the rest are ignored)
3) **Here is where I am stuck. If I am inside of the .observe then I can do what I want with the array like printing its contents. HOWEVER, if I call print outside of the .observe I get nothing...
here is my code
func pullCurrentUserCardInfo() {
let userCardsRef = ref.child("users").child((user?.uid)!).child("cards")
userCardsRef.observeSingleEvent(of: .value, with: {(snapshot) in
if let snapDict = snapshot.value as? [String: AnyObject] {
for each in snapDict {
self.usersCardRefArray.append(each.key)
self.count = Int(snapshot.childrenCount)
}
}
})
self.ref.child("cards").observe(.value, with: { (snapshot) in
if snapshot.hasChildren() {
for item in snapshot.value as! [String: AnyObject] {
for test in self.usersCardRefArray {
if test == item.key {
self.allCurrentUsersCards.append(item.key)
}
}
}
} else {
print("no children")
}
})
}
if I were to say the following inside of the function but outside of the .observe ....}) then it doesn't do anything.....
for item in allCurrentUsersCards {
print(item)
}
Am I missing something small somewhere or is this something to do with firebase?
I think there's an unneeded level of complexity here. You do not need to store (in this use case at least) a separate card for each user. There's a 1-1 relationship between user and card so just storing the card data for each user within the user node would be the best answer.
However, to answer the question directly, here's how to do it. We going to slightly alter the Firebase structure:
root
cards
*user uid* <- CHANGE
nickname: "foo"
type: "bar"
users
user uid: true <- CHANGE
Since user uid's are always unique and created for you, leverage them when working with users. So in this case just store the user uid's in the user node and that same uid in the cards node.
Create a User Class and an array to store them in. This would typically be done right inside a viewController for example
class ViewController: UIViewController {
class UserClass {
var uid = ""
var nickname = ""
var type = ""
}
var usersArray = [UserClass]()
Then, craft a Firebase observer to populate the usersArray, getting each card for each user
//iterate over all of the users, get the user and its card data
let usersRef = ref.child("users")
usersRef.observeSingleEvent(of: .value, with: { snapshot in
for snap in snapshot.children { //iterate over all users
let userSnap = snapshot as! FIRDataSnapshot
let userKey = userSnap.key //the uid of each user
//now that we have the uid, get it's card data
let thisUserCardRef = cardsRef.child("uid")
thisUserCardRef.observeSingleEvent(of: .value, with: { userSnap in
let userCardSnap = userSnap as! FIRDataSnapshot
let userCardDict = userCardSnap.value as! [String:AnyObject]
let nickname = userCardDict["nickname"]
let type = userCardDict["type"]
let aUser = UserClass()
aUser.userKey = userKey
aUser.nickname = nickname
aUser.type = type
self.usersArray.append(aUser)
//In general, this is where the tableView is refreshed
// because the user data and card data is valid at this point
//usersTableView.reload data /
})
}
})
The key here is to remember that Firebase is asynchronous and that code is way faster than the internet. So this high level example will fail most of the time
func getData() {
loadDataFromFirebase()
print("data loaded, do something with it") //<- executes before the prior line completes
}
func loadDataFromFirebase() {
someRef.observeEvent...
print("data is now valid inside observe closure")
}
This will usually result in
data loaded, do something with it
data is now valid inside observe closure
Which is opposite of what is wanted. Code executes faster than the internet so the asynchronous observe closure will occur after the data loaded... is printed. Only reference and work with firebase data inside a closure and use the closure to pace your app.
If you notice in the first example code provided - we only work with the data once it's been returned from Firebase.
Also note that we completely eliminated queries! Queries are 'heavy' by comparison to observe events and since we are leveraging the uid of each user, it's path will be known, hence the change from a node created with childByAutoId to using the uid.

How to remove common items from two struct arrays in Swift

In my app I have two struct arrays and I want to remove common items from one of them. My struct:
struct PeopleSelectItem {
var name = ""
var id = ""
var added = false
}
My arrays:
var people : [PeopleSelectItem] = []
var selectedPeople : [PeopleSelectItem] = []
I want to remove items from people array if they exist (compare by id) on selectedPeople array.
I tried several array filtering and converting to set but none of them worked. What can I do here?
Thanks!
Get an array of all ids in selectedPeople
let selectedPeopleIDs = selectedPeople.map(\.id)
Filter the items whose id is not in the array
let filteredPeople = people.filter { !selectedPeopleIDs.contains($0.id) }
If you know that people equal each other if the id is the same then you can conform your struct to the Equatable protocol and you can use the array filter method.
struct PeopleSelectItem : Equatable {
var name = ""
var id = ""
var added = false
}
func ==(lhs: PeopleSelectItem, rhs: PeopleSelectItem) -> Bool {
return lhs.id == rhs.id
}
func filterPeople() {
//swift 2, 3:
people = people.filter{!selectedPeople.contains($0)}
//swift older versions:
people = people.filter{!contains(selectedPeople, $0)}
}
If people might have a significant amount of entries, performance should be considered. So, instead of searching with an n^2 algorithm, you should make use of Swifts dictionary and the corresponding hash-search to find items.
If Id is unique for people then I would store them in a dictionary like:
var peopleDict: [String:PeopleSelectItem] = [:]()
You can easily convert from the array you have to this dictionary:
people.foreach {peopleDict[$0.id] = $0}
With this dictionary it's very easy to delete single entries:
selectedPeople.foreach {peopleDict.remove($0.id)}
Optionally to switch back to an array for people you just say:
let filteredPeople = peopleDict.values as [PeopleSelectItem]
Remarks
I assumed, that selectedPeople is smaller then the base of all people. If this is not the case, you should pu selectedPeople in a dictionary.
did I say I like this Spark like api? I think I do so.
I just wrote that code from top of my mind. If it is not completely syntactically correct let me know and I correct it.

firebase queryendingatValue("") always returns first child in database

I am trying to do a keyword search using firebase queries but they do not seem to ever retrieve the correct value. Its always the first child of the database. the database is organized using child by autoid. my data looks like this
posts :
-KJj6DMQVcaOIBZ76X03
category:
"cleaner"
dishname:
"Lysol"
likes
8QCQPfShSTdYe1VbCxHK4dkJPFj1:
true
likesCount:
1
picURL:
"https://firebasestorage.googleapis.com/v0/b/dis..."
poster:
"8QCQPfShSTdYe1VbCxHK4dkJPFj1"
price:
"$5"
restaurant:
"house"
-KJj6PaHt9EfXQ5-EU-m
-KJnTUcb3BvZDMI0Pxgo
-KJnTl4giuy5QMvdeEo5
the function that is doing the search looks like this
func searchBarSearchButtonClicked(searchBar: UISearchBar) {
filteredPosts.removeAll()
let postref = firebase.child("posts")
let search = searchController.searchBar.text!
print(search)
let postQ = (postref.queryOrderedByKey().queryEndingAtValue(search))
// postQ.keepSynced(true)
postQ.observeSingleEventOfType(.ChildAdded, withBlock: { (snapshot) in
self.filteredPosts.append(snapshot)
print(snapshot)
self.shouldShowSearchResults = 2
self.tableView.reloadData()
})
searchController.searchBar.resignFirstResponder()
}
It is super frustrating. please help
You are ordering by key, postref.queryOrderedByKey(), and then querying by that key's value queryEndingAtValue(search).
So unless your search variables is one of the childByAutoId values (-KJnTUcb3BvZDMI0Pxgo), I don't think you'll get back what you want.
Instead order by the child property you are searching against. Let's say you want to search by a post's category.
var search = "cleaner"
let categoryQuery = postref
.queryOrderedByChild("category")
.queryEndingAtValue(search)
This would pull back the record of -KJj6DMQVcaOIBZ76X03.

A Good design in Swift for a model that has many aspects?

I would like to ask a question about a good example of how to define a model with many aspects in swift, especially when the project gets bigger and bigger and one model has many aspects. The questions is pretty long but I just would like to know how ppl design a model in a big project. Any comments or thought would be appreciated.
Let's say there is a model called "Book" and it is defined like below:
class Book {
var id: String
var title: String
var author: String
init?(json: [String: AnyObject]) {
// parse the model from JSON
}
}
Book has a failable initialiser that parses properties from JSON sent from the server.
On view controller A, it describes the more detailed information about the mode Book, so some properties are added to the model when it is used on view controller r A:
class Book {
var id: String
var title: String
var author: String
// required on View Controller A
var price: Int
var seriersName: String
var reviewNumber: Int
var detailedDescription: String
init?(json: [String: AnyObject]) {
// parse the model from JSON
}
}
On another view controller B, we want to show the history of book purchase. So the model needs additional properties as below:
class Book {
var id: String
var title: String
var author: String
// required on View Controller A
var price: Int
var seriersName: String
var reviewNumber: Int
var detailedDescription: String
// required on View Controller B (Not required on VC A)
var purchasedDate: NSDate
var expireDate: NSDate
init?(json: [String: AnyObject]) {
// parse the model from JSON
}
}
This definition of Book lacks flexibility because the JSON passed to the failabel initialiser must have all of the properties even on an VC that uses only some of the properties.
Solution A:
I think the simplest solution for this is just declaring those additional properties as optional, but I personally think this is not so cool because whenever those optional properties are used they need to be checked if they are not nil.
if let seriesName = book.seriesName {
self.seriesNameLable.title = seriesName
}
This kind of optional binding code will be overflowed all over the codes I assume. Implicit optional binding might be able to used but that is not really safe to use.
Solution B:
Another solution might be to define different models that inherits Book, like BookA and BookB. But what if we need a model that has BookA and BookB's aspects at the same time?
I guess there is no single solution for this kind of problem but I would like to know how other ppl define a model in a big project. (I wonder if someone would have a cool solution using some "swift specific" features like protocol and its extension :). I would appreciate any kind of opinions... Thank you.
Disclaimer: I'm not a Swift programmer, this are extrapolations I make from other languages with the same features and my Swift syntax might not be 100% accurate
Using protocols I would do something like:
class EBook: Book, HasOnlineSource {
...
}
class OtherKindOfBook: Book, WithCollectorEditions, HasBleh, WithFoo {...}
But you must ask yourself:
Do I need this to change dinamically?
If that is the case, you would need to go with Delegation through composition.
Are different parts of the application using the core models differently?
Or in other words, are there different users of those models needing different behavior? In that case, extensions are very useful since allow to expose different behaviors for the same model depending on their context. For instance, the Reporting module can send the message numberOfReaders while the Selling module could ask for promotionalCodes. Both are using the same model, but interacting with different protocols. In your case, you have different controllers wanting different things, so this might apply.
Using delegates
This follows the Composition over inheritance principle, but after reviewing how delegates work in Swift, I understood that they are not a native implementation but still a design pattern (a feature request you might say), the delegation is being made by hand.
Other languages allow you to make a JSSONSerializableBook with a BookProtocol, but instead of implementing what is required on BookProtocol you can set a delegate upon initialization which will implement such protocol. This delegate will be an internal collaborator of JSSONSerializableBook and all messages that are part of BookProtocol received by JSSONSerializableBook will be delegated to it.
Or in other words, the message passing is handled automatically (here is the Kotlin documentatin on delegates if you want to check how other language implements it).
If you want to do the same on Swift, you must explicitly make the message passing to your delegate.
This design has several advantages but since there is no native support for message passing it becomes very verbose to implement.
For further reference you can check the papers on mixins and traits to have some insight on the design decisions behind this features.
This is why Swift has optionals.
If not all Books have, say, a purchasedDate then that should be an optional and then your initialiser doesn't need to fail if that field isn't present in the JSON.
It is your view controllers that will enforce the business logic - so if ViewControllerB gets a Book without a purchasedDate it should generate some sort of error.
It also may be that your base class needs to be decomposed into smaller objects.
For example, you could probably have a PurchaseEvent that is associated with a Book rather than storing that data in the Book itself.
Think of the characteristics of a book in the real world; it has the basic properties you first listed and may even have a price (on the back cover) but it doesn't tell you when it was sold and for how much.
However, #Logain's idea of using Protocols is also good, although it may be tricky when it comes to keeping the server JSON side in sync with the app code.
First of all, if you have a view controller responsible for presenting “more detailed information” about a book, then let's call it BookDetailsViewController, instead of the meaningless “View Controller A”. Similarly we should talk about BookHistoryViewController, not “View Controller B”.
If you want to treat the “more detailed information” as all-or-nothing, then encapsulate it as such, in a Details object that the Book object optionally reference. Same with the history. So here's a model:
class Book {
let id: String
let title: String
let author: String
// These can't be lets because each requires self to initialize.
private(set) var details: Details?
private(set) var history: History?
init?(json: [String: AnyObject]) {
guard let
id = json["id"] as? String,
title = json["title"] as? String,
author = json["author"] as? String
else {
// These assignments will be unnecessary in the next version of Swift.
self.id = ""
self.title = ""
self.author = ""
return nil
}
self.id = id
self.title = title
self.author = author
self.details = Details(book: self, json: json)
self.history = History(book: self, json: json)
}
class Details {
unowned let book: Book
let price: Int
let seriesName: String
let reviewNumber: Int
let detailedDescription: String
init?(book: Book, json: [String: AnyObject]) {
self.book = book
guard let
price = json["price"] as? Int,
seriesName = json["seriesName"] as? String,
reviewNumber = json["reviewNumber"] as? Int,
detailedDescription = json["detailedDescription"] as? String
else {
// These assignments will be unnecessary in the next version of Swift.
self.price = 0
self.seriesName = ""
self.reviewNumber = 0
self.detailedDescription = ""
return nil
}
self.price = price
self.seriesName = seriesName
self.reviewNumber = reviewNumber
self.detailedDescription = detailedDescription
}
}
class History {
unowned let book: Book
let purchasedDate: NSDate
let expireDate: NSDate
init?(book: Book, json: [String: AnyObject]) {
self.book = book
guard let
purchasedDate = (json["purchasedDate"] as? Double).map({ NSDate(timeIntervalSince1970: $0) }),
expireDate = (json["expireDate"] as? Double).map({ NSDate(timeIntervalSince1970: $0) })
else {
// These assignments will be unnecessary in the next version of Swift.
self.purchasedDate = NSDate()
self.expireDate = NSDate()
return nil
}
self.purchasedDate = purchasedDate
self.expireDate = expireDate
}
}
}
Given this model, you can allow the user to ask for a BookDetailsViewController or a BookHistoryViewController only when the appropriate property isn't nil.
class BookViewController: UIViewController {
#IBOutlet var detailsButton: UIButton!
#IBOutlet var historyButton: UIButton!
var book: Book!
override func viewDidLoad() {
super.viewDidLoad()
detailsButton.hidden = book.details == nil
historyButton.hidden = book.history == nil
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
switch segue.identifier {
case .Some("details"): prepareForDetailsSegue(segue)
case .Some("history"): prepareForHistorySegue(segue)
case let other: fatalError("unknown segue identifier \(other)")
}
}
private func prepareForDetailsSegue(segue: UIStoryboardSegue) {
let destination = segue.destinationViewController as! BookDetailsViewController
destination.details = book.details!
}
private func prepareForHistorySegue(segue: UIStoryboardSegue) {
let destination = segue.destinationViewController as! BookHistoryViewController
destination.history = book.history!
}
}
class BookDetailsViewController: UIViewController {
var details: Book.Details!
// etc.
}
class BookHistoryViewController: UIViewController {
var history: Book.History!
// etc.
}

Resources