Creating a second failable initializer using a convenience initializer - swift

I'm trying to create a second failable convenience initializer to use because I want to make two API calls and this second failable initializer contains all the properties I'll get from the original API unlike the first failable initializer that will contain all the original properties plus a few additional ones I've added on which I'll be posting to firebase.
class Alcohol {
var id: Int
var companyName: String
var address: String
var city: String
var state: String
var postal: String
var country: String
var phone: String
var email: String
var url: String
var checkedBy: String
var notes: String
var status: String
var statusColor: String
var identifier: NSUUID?
var barnivoreChecked: Bool?
var alcoholType: AlcoholType?
// This failable init is for the information I'll be retrieving from firebase.
init?(dictionary: [String: AnyObject], type: AlcoholType) {
guard
let id = dictionary[Constants.kID] as? Int,
let companyName = dictionary[Constants.kCompanyName] as? String,
let address = dictionary[Constants.kAddress] as? String,
let city = dictionary[Constants.kCity] as? String,
let state = dictionary[Constants.kState] as? String,
let postal = dictionary[Constants.kPostal] as? String,
let country = dictionary[Constants.kCountry] as? String,
let phone = dictionary[Constants.kPhone] as? String,
let email = dictionary[Constants.kEmail] as? String,
let url = dictionary[Constants.kURL] as? String,
let checkedBy = dictionary[Constants.kCheckedBy] as? String,
let notes = dictionary[Constants.kNotes] as? String,
let status = dictionary[Constants.kStatus] as? String,
let statusColor = dictionary[Constants.kStatusColor] as? String,
let barnivoreChecked = dictionary[Constants.kBarnivoreChecked] as? Bool else { return nil }
self.id = id
self.companyName = companyName
self.address = address
self.city = city
self.state = state
self.postal = postal
self.country = country
self.phone = phone
self.email = email
self.url = url
self.checkedBy = checkedBy
self.notes = notes
self.status = status
self.statusColor = statusColor
self.barnivoreChecked = barnivoreChecked
self.alcoholType = type
}
// This failable initializer has all the original properties I want to get from the initial API which I'll be retrieving the information from.
convenience init?(barnivoreDictionary: [String: AnyObject]) {
guard
let id = barnivoreDictionary[Constants.kID] as? Int,
let companyName = barnivoreDictionary[Constants.kCompanyName] as? String,
let address = barnivoreDictionary[Constants.kAddress] as? String,
let city = barnivoreDictionary[Constants.kCity] as? String,
let state = barnivoreDictionary[Constants.kState] as? String,
let postal = barnivoreDictionary[Constants.kPostal] as? String,
let country = barnivoreDictionary[Constants.kCountry] as? String,
let phone = barnivoreDictionary[Constants.kPhone] as? String,
let email = barnivoreDictionary[Constants.kEmail] as? String,
let url = barnivoreDictionary[Constants.kURL] as? String,
let checkedBy = barnivoreDictionary[Constants.kCheckedBy] as? String,
let notes = barnivoreDictionary[Constants.kNotes] as? String,
let status = barnivoreDictionary[Constants.kStatus] as? String,
let statusColor = barnivoreDictionary[Constants.kStatusColor] as? String else {
self.init(id: 0, companyName: "", address: "", city: "", state: "", postal: "", country: "", phone: "", email: "", url: "", checkedBy: "", notes: "", status: "", statusColor: "", alcoholType: alcoholType! )
return nil
}
self.id = id
self.companyName = companyName
self.address = address
self.city = city
self.state = state
self.postal = postal
self.country = country
self.phone = phone
self.email = email
self.url = url
self.checkedBy = checkedBy
self.notes = notes
self.status = status
self.statusColor = statusColor
self.alcoholType = nil
}
init(id: Int, companyName: String, address: String, city: String, state: String, postal: String, country: String, phone: String, email: String, url: String, checkedBy:String, notes: String, status: String, statusColor: String, barnivoreChecked: Bool = true, alcoholType: AlcoholType) {
self.id = id
self.companyName = companyName
self.address = address
self.city = city
self.state = state
self.postal = postal
self.country = country
self.phone = phone
self.email = email
self.url = url
self.checkedBy = checkedBy
self.notes = notes
self.status = status
self.statusColor = statusColor
self.identifier = NSUUID()
self.barnivoreChecked = barnivoreChecked
self.alcoholType = alcoholType
}
}
Unfortunately I get an error stating:
"self" used before self.init call.
And if I do try to just use self.init(), I get the error:
Cannot invoke 'Alcohol.init' with no arguments.
Any help or suggestions would be much appreciated.

In Swift 3, a convenience initializer must call a designated initializer in the same class before any attempt to access self is made in the convenience initializer.
I suggest you change your convenience initializer to be like this:
convenience init?(barnivoreDictionary: [String: AnyObject]) {
guard
let id = barnivoreDictionary[Constants.kID] as? Int,
let companyName = barnivoreDictionary[Constants.kCompanyName] as? String,
let address = barnivoreDictionary[Constants.kAddress] as? String,
let city = barnivoreDictionary[Constants.kCity] as? String,
let state = barnivoreDictionary[Constants.kState] as? String,
let postal = barnivoreDictionary[Constants.kPostal] as? String,
let country = barnivoreDictionary[Constants.kCountry] as? String,
let phone = barnivoreDictionary[Constants.kPhone] as? String,
let email = barnivoreDictionary[Constants.kEmail] as? String,
let url = barnivoreDictionary[Constants.kURL] as? String,
let checkedBy = barnivoreDictionary[Constants.kCheckedBy] as? String,
let notes = barnivoreDictionary[Constants.kNotes] as? String,
let status = barnivoreDictionary[Constants.kStatus] as? String,
let statusColor = barnivoreDictionary[Constants.kStatusColor] as? String else {
return nil
}
self.init(id: id, companyName: companyName, address: address, city: city, state: state, postal: postal, country: country, phone: phone, email: email, url: url, checkedBy: checkedBy, notes: notes, status: status, statusColor: statusColor, alcoholType: alcoholType)
}
Note that you don't need to call self.init... inside the guard.

Related

Unpacking Firestore array with objects to a model in swift

I have a project in swift with Firestore for the database. My firestore dataset of a user looks like this. User details with an array that contains objects.
I have a function that gets the specifick user with all firestore data:
func fetchUser(){
db.collection("users").document(currentUser!.uid)
.getDocument { (snapshot, error ) in
do {
if let document = snapshot {
let id = document.documentID
let firstName = document.get("firstName") as? String ?? ""
let secondName = document.get("secondName") as? String ?? ""
let imageUrl = document.get("imageUrl") as? String ?? ""
let joinedDate = document.get("joinedDate") as? String ?? ""
let coins = document.get("coins") as? Int ?? 0
let challenges = document.get("activeChallenges") as? [Challenge] ?? []
let imageLink = URL(string: imageUrl)
let imageData = try? Data(contentsOf: imageLink!)
let image = UIImage(data: imageData!) as UIImage?
let arrayWithNoOptionals = document.get("activeChallenges").flatMap { $0 }
print("array without opt", arrayWithNoOptionals)
self.user = Account(id: id, firstName: firstName, secondName: secondName, email: "", password: "", profileImage: image ?? UIImage(), joinedDate: joinedDate, coins: coins, activeChallenges: challenges)
}
else {
print("Document does not exist")
}
}
catch {
fatalError()
}
}
}
This is what the user model looks like:
class Account {
var id: String?
var firstName: String?
var secondName: String?
var email: String?
var password: String?
var profileImage: UIImage?
var coins: Int?
var joinedDate: String?
var activeChallenges: [Challenge]?
init(id: String, firstName: String,secondName: String, email: String, password: String, profileImage: UIImage, joinedDate: String, coins: Int, activeChallenges: [Challenge]) {
self.id = id
self.firstName = firstName
self.secondName = secondName
self.email = email
self.password = password
self.profileImage = profileImage
self.joinedDate = joinedDate
self.coins = coins
self.activeChallenges = activeChallenges
}
init() {
}
}
The problem is I don't understand how to map the activeChallenges from firestore to the array of the model. When I try : let challenges = document.get("activeChallenges") as? [Challenge] ?? []
The print contains an empty array, but when i do: let arrayWithNoOptionals = document.get("activeChallenges").flatMap { $0 } print("array without opt", arrayWithNoOptionals)
This is the output of the flatmap:
it returns an optional array
System can not know that activeChallenges is array of Challenge object. So, you need to cast it to key-value type (Dictionary) first, then map it to Challenge object
let challengesDict = document.get("activeChallenges") as? [Dictionary<String: Any>] ?? [[:]]
let challenges = challengesDict.map { challengeDict in
let challenge = Challenge()
challenge.challengeId = challengeDict["challengeId"] as? String
...
return challenge
}
This is the same way that you cast snapshot(document) to Account object

Getting Couldn’t communicate with a helper application. When trying to save a LocalNotification

Im gettin Couldn't comunicate with a helper application in the console when I'm trying to save a local notification in UNUserNotificationCenter with userInfo.
If I remove de userInfo instruccion works perfectly.
This is the class that implement NSCoding methods. In de notification.userInfo I'm storing an object of this class
class Homework: NSObject, NSCoding, NSSecureCoding {
let name: String
let homeworkDescription: String
let date: Date
let score: Double
let status: String
let subject: String
let db = Firestore.firestore()
static var supportsSecureCoding: Bool {
return true
}
init(name: String, description: String, date: Date, score: Double, status: String, subject: String){
self.name = name
self.homeworkDescription = description
self.date = date
self.score = score
self.status = status
self.subject = subject
}
func encode(with coder: NSCoder) {
coder.encode(name, forKey: Keys.name.rawValue)
coder.encode(homeworkDescription, forKey: Keys.description.rawValue)
coder.encode(date, forKey: Keys.date.rawValue)
coder.encode(score, forKey: Keys.score.rawValue)
coder.encode(status, forKey: Keys.status.rawValue)
coder.encode(subject, forKey: Keys.subject.rawValue)
}
required convenience init?(coder: NSCoder) {
let decodedName = coder.decodeObject(forKey: Keys.name.rawValue) as! String
let decodedDescription = coder.decodeObject(forKey: Keys.description.rawValue) as! String
let decodedDate = coder.decodeObject(forKey: Keys.date.rawValue) as! Date
let decodedScore = coder.decodeDouble(forKey: Keys.score.rawValue)
let decodedStatus = coder.decodeObject(forKey: Keys.status.rawValue) as! String
let decodedSubject = coder.decodeObject(forKey: Keys.subject.rawValue) as! String
self.init(name: decodedName, description: decodedDescription, date: decodedDate, score: decodedScore, status: decodedStatus, subject: decodedSubject)
}
enum Keys: String {
case name = "Name"
case description = "Description"
case date = "Date"
case score = "Score"
case status = "Status"
case subject = "Subject"
}
This is the code that add the notification and when I get the error:
for notification in notifications
{
let content = UNMutableNotificationContent()
content.title = notification.title
content.subtitle = notification.subTitle
content.body = notification.body
content.sound = .default
content.badge = UIApplication.shared.applicationIconBadgeNumber as NSNumber
content.targetContentIdentifier = notification.type
content.userInfo[notification.type] = notification.homework
let trigger = UNCalendarNotificationTrigger(dateMatching: notification.dateTime, repeats: false)
let request = UNNotificationRequest(identifier: notification.id, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request) { error in
if let error = error{
print("This is the error: " + error.localizedDescription)
}else{
print("Notification scheduled! --- ID = \(notification.id)")
}
}
This is the notification class:
class LocalNotification {
var id: String
var title: String
var subTitle: String
var dateTime: DateComponents
var type: String
var body: String
var homework: Homework
init(id: String, title: String, subtitle: String, dateTime: DateComponents, type: String, description: String, homeWork: Homework) {
self.id = id
self.title = title
self.subTitle = subtitle
self.dateTime = dateTime
self.type = type
self.body = description
self.homework = homeWork
}
Does anyone one know why this happened?

Is it possible to read from multiple child nodes?

I want to read all three data sourcing from "Arts & Humanities" and "Beauty & Style". Is this possible?
Let ref = Database.database().reference().child("posts")
//CODE A: Pulls 2 snapshot, but doesn't display anything
let ref = Database.database().reference().child("posts").child("Arts & Humanities")
//CODE B: only pulls up the two feeds but excludes beauty and style. Vice versa
//Below is the listener code I have. This works only works with CODE B above; but ideally id like to read the post under "Beauty & Style" as well.
postsRef.observeSingleEvent(of: .value, with: { snapshot in
var tempPosts = [PostModel]()
for child in snapshot.children {
print(snapshot.childrenCount)
if let childSnapshot = child as? DataSnapshot,
let dict = childSnapshot.value as? [String:Any],
let author = dict["author"] as? [String:Any],
let uid = author["uid"] as? String,
let username = author["username"] as? String,
let fullname = author["fullname"] as? String,
let patthToImage = author["patthToImage"] as? String,
let url = URL(string:patthToImage),
let pathToImage = dict["pathToImage"] as? String,
let likes = dict["likes"] as? Int,
let postID = dict["postID"] as? String,
let message = dict["message"] as? String,
let genre = dict["genre"] as? String,
let timestamp = dict["timestamp"] as? Double {
if childSnapshot.key != lastPost?.id {
let userProfile = UserProfile(uid: uid, fullname: fullname, username: username, patthToImage: url)
let post = PostModel(genre: genre, likes: likes, message: message, pathToImage: pathToImage, postID: postID, userID: pathToImage, timestamp: timestamp, id: childSnapshot.key, author: userProfile)
tempPosts.insert(post, at: 0)
if lastPost?.id != nil {
lastPostIdChecker = lastPost!.id
}
}
}
}
return completion(tempPosts)
})

Swift Retrieving Data Firebase

I'm trying to retrieve string key which I have saved it in the object
func retrieveData() {
let refAll = Database.database().reference().child("Playground")
refAll.observe(.value) { (snapshot) in
if let snapshotValue = snapshot.value as? [String:Any] {
var playgroundSnapshot = snapshotValue
let playgroundKeys = Array(playgroundSnapshot.keys)
self.playgroundArray.removeAll()
for key in playgroundKeys {
guard
let value = playgroundSnapshot[key] as? [String : Any]
else {
continue
}
let title = value["title"] as! String
let city = value["city"] as! String
let location = value["location"] as! String
let price = value["price"] as! String
let playground = Playground(title: title, price: price, location: location, city: city, availblePlayground: true)
self.playgroundArray.append(playground)
}
self.tabeView.reloadData()
}
}
}
and in the playgroundArray there is key for each object
keySelected = playgroundArray[indexPath.row].key
but I don't know why keySelected is nil even tho playgroundArray has objects
playgroundArray does not contain "key" . You have to set the Playground Struct. Add var key:String? and also add this to init() func.
let playground = Playground(title: title, price: price, location: location, city: city, availblePlayground: true)
to
let playground = Playground(title: title, price: price, location: location, city: city, availblePlayground: true, key: key)
self.playgroundArray.append(playground)

Firebase duplicate cell

Is possible to avoid duplicating of cell with content of the same name during import customer from database to tableview? In my example if customer Ben Smith has two children with values I want only one cell with his name.
This is my database structure...
And result in tableview:
let userID = Auth.auth().currentUser!.uid
let usersDatabaseRef = Database.database().reference().child("usersDatabase").child(userID).child("Customers")
usersDatabaseRef.observe(.value, with: { snapshot in
print("there are \(snapshot.childrenCount) users")
for child in snapshot.children {
let childSnap = child as! DataSnapshot
print("user: \(childSnap.key)")
let userCustomerSnap = childSnap
for customer in userCustomerSnap.children.allObjects as! [DataSnapshot] {
let customerSnap = customer
let dict = customerSnap.value as! [String: Any]
let name = dict["Name and surname"]
let phone = dict["Phone"]
let company = dict["Company name"]
let customerID = dict["ID"]
let email = dict["Email"]
let nip = dict["Nip1"]
let postal = dict["Postal code"]
let street = dict["Street"]
let town = dict["Town"]
let myCustomer = CustomerModel(name: name as? String, phone: phone as? String, company: company as? String, customerID: customerID as? String, email: email as? String, nip: nip as? String, postal: postal as? String, street: street as? String, town: town as? String)
self.candies.append(myCustomer)
self.filteredCandies.append(myCustomer)
}
self.tableViewCustomer.reloadData()
You're adding an item to the list each time you find an order (or whatever the level under "Ben Smith" represents in your data) from that customer. So your list is a list of orders, not a list of customers.
In general in NoSQL/Firebase, it is recommended to model your database for what you want to display. So if you want a list of customers, that's what I'd store in the database. But given your data structure, you can also fix it in code:
usersDatabaseRef.observe(.value, with: { snapshot in
print("there are \(snapshot.childrenCount) users")
for child in snapshot.children {
let childSnap = child as! DataSnapshot
let myCustomer = CustomerModel(name: child.key, phone: "", company: "", customerID: "", email: "", nip: "", postal: "", street: "", town: ")
self.candies.append(myCustomer)
self.filteredCandies.append(myCustomer)
}
self.tableViewCustomer.reloadData()
})
Or alternatively, only add the new customer when their name is different from the previous order you saw:
var previousName: String = ""
usersDatabaseRef.observe(.value, with: { snapshot in
print("there are \(snapshot.childrenCount) users")
for child in snapshot.children {
let childSnap = child as! DataSnapshot
print("user: \(childSnap.key)")
let userCustomerSnap = childSnap
for customer in userCustomerSnap.children.allObjects as! [DataSnapshot] {
let customerSnap = customer
let dict = customerSnap.value as! [String: Any]
let name = dict["Name and surname"]
if name != previousName {
let phone = dict["Phone"]
let company = dict["Company name"]
let customerID = dict["ID"]
let email = dict["Email"]
let nip = dict["Nip1"]
let postal = dict["Postal code"]
let street = dict["Street"]
let town = dict["Town"]
let myCustomer = CustomerModel(name: name as? String, phone: phone as? String, company: company as? String, customerID: customerID as? String, email: email as? String, nip: nip as? String, postal: postal as? String, street: street as? String, town: town as? String)
self.candies.append(myCustomer)
self.filteredCandies.append(myCustomer)
previousName = name
}
}
}
self.tableViewCustomer.reloadData()
You really don't need that for loop. That might be what's causing you issue. The code is called for the same number of times that you have children anyway so you don't need that loop. Try the code below. If that doesn't work, you might need to try .childAdded instead of .value
guard let userID = Auth.auth().currentUser?.uid else { return }
let usersDatabaseRef = Database.database().reference().child("usersDatabase").child(userID).child("Customers")
usersDatabaseRef.observe(.childAdded, with: { snapshot in
guard let dict = snapshot.value as? [String: Any] else { return }
let name = dict["Name and surname"] as? String
let phone = dict["Phone"] as? String
let company = dict["Company name"] as? String
let customerID = dict["ID"] as? String
let email = dict["Email"] as? String
let nip = dict["Nip1"] as? String
let postal = dict["Postal code"] as? String
let street = dict["Street"] as? String
let town = dict["Town"] as? String
let myCustomer = CustomerModel(name: name, phone: phone, company: company, customerID: customerID, email: email, nip: nip, postal: postal, street: street, town: town)
self.candies.append(myCustomer)
self.filteredCandies.append(myCustomer)
DispatchQueue.main.async {
self.tableViewCustomer.reloadData()
}
}, withCancel: nil)