Implementing comments on feed post in swift using firebase - arrays

I am trying to implement a comment section on each feed post in my app using swift and firebase, but am having trouble with the code that will get the comments. In my function it is returning a empty array of messageComments but I do not know what I am doing wrong. If I want my firebase database structure to look like that in the picture how can I implement the code that will download those comments in an array?
func getFeedMessages(handler: #escaping (_ feedMessages:[FeedMessages]) -> ()){
var feedMessagesArray = [FeedMessages]()
var commentArray = [messageComments]()
REF_FEEDMESSAGES.observeSingleEvent(of: .value) { (feedMessagesSnapshot) in
guard let feedMessagesSnapshot = feedMessagesSnapshot.children.allObjects as? [DataSnapshot] else {return}
for messages in feedMessagesSnapshot {
let content = messages.childSnapshot(forPath: "content").value as? String ?? "Joe Flacco is an elite QB"
let icon = messages.childSnapshot(forPath: "icon").value as? String ?? "none"
let color = messages.childSnapshot(forPath: "color").value as? String ?? "bop"
self.REF_FEEDCOMMENTS.observeSingleEvent(of: .value, with: { (feedCommentsSnapshot) in
guard let feedCommentsSnapshot = feedCommentsSnapshot.children.allObjects as? [DataSnapshot] else {return}
for comments in feedCommentsSnapshot {
commentArray.append((comments.childSnapshot(forPath: "comments").value as? messageComments!)!)
}
})
print(" comment: ")
print(commentArray)
let messages = FeedMessages(content: content, color: color, icon: icon, comments: commentArray)
feedMessagesArray.append(messages)
}
handler(feedMessagesArray)
}
}

If you also have the same data structure, there would be no need for another request for the comments since they are nested in the feed messages. This will only require some simple parsing, which can be made easier to read and understand with a few extensions.
extension DataSnapshot {
var string: String? {
return value as? String
}
var childSnapshots: [DataSnapshot] {
return children.allObjects as? [DataSnapshot] ?? []
}
func child(_ path: String) -> DataSnapshot {
return childSnapshot(forPath: path)
}
}
These two extensions take care of the snapshot operations needed to initialize the objects.
extension MessageComments {
convenience init(snapshot: DataSnapshot) {
self.comments = snapshot.childSnapshots.map { $0.string }
}
}
extension FeedMessages {
convenience init(snapshot: DataSnapshot) {
self.color = snapshot.child("color").string ?? "bop",
self.comments = MessageComments(snapshot: snapshot.child("comments"))
self.content = snapshot.child("content").string ?? "Joe Flacco is an elite QB",
self.icon = snapshot.child("icon").string ?? "none",
}
}
Just map the children snapshots to initialize each of them as a FeedMessages object.
func getFeedMessages(handler: #escaping (_ feedMessages: [FeedMessages]) -> ()) {
REF_FEEDMESSAGES.observeSingleEvent(of: .value) {
handler($0.childSnapshots.map { FeedMessages(snapshot: $0) })
}
}

Related

Saving an custom object array that is appended constantly

I'm relatively new to Swift and coding in general. I'm trying to hone my skills at the moment but putting together a simple reminder app. I'm trying to get the back end working before I put together the story board but I have the essential story board elements to test if my system will work.
Basically I'm trying to save a array that contains a custom object, but this array is appended to each reminder addition done by the user. This is so that every time the app opens, the array will contain the reminders from last time.
Here is the code I have so far to create and append the list;
func createReminder() {
let reminderAdd = Reminder(chosenReminderDescription: textRetrieve.text!, chosenReminderLength: 1)
reminderList.append(reminderAdd)
dump(reminderList)
}
Here is the object code;
class Reminder {
var reminderDescription = "Require initalisation."
var reminderLength = 1 // in days
init (chosenReminderDescription: String, chosenReminderLength: Int) {
reminderDescription = chosenReminderDescription
reminderLength = chosenReminderLength
}
}
How would I go about saving the array?
EDIT:
This is what i've added so far.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let reminderAdd = Reminder(chosenReminderDescription: "Placeholder test", chosenReminderLength: 1)
reminderList.append(reminderAdd)
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "Tasks", in: context)
let newTask = NSManagedObject(entity: entity!, insertInto: context)
newTask.setValue(reminderList, forKey: "taskName")
do {
try context.save()
} catch {
print("Failed saving")
}
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Tasks")
//request.predicate = NSPredicate(format: "age = %#", "12")
request.returnsObjectsAsFaults = false
do {
let result = try context.fetch(request)
for data in result as! [NSManagedObject] {
print(data.value(forKey: "taskName"))
}
} catch {
print("Failed")
}
I'm getting crashes and I can't seem to debug it as of yet. I believe this line is causing the crash as when I remove it the app launches fine.
let reminderAdd = Reminder(chosenReminderDescription: "Placeholder test", chosenReminderLength: 1)
reminderList.append(reminderAdd)
Any ideas?
EDIT 2:
datamodel
That is the data model, I'm not entirely sure what you mean to make the object into a codable. Thanks again.
EDIT 3:
ViewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "Tasks", in: context)
let newTask = Tasks(entity: entity!, insertInto: context)
newTask.setValue(reminderList, forKey: "taskName")
do {
try context.save()
} catch {
print("Failed saving")
}
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Tasks")
//request.predicate = NSPredicate(format: "age = %#", "12")
request.returnsObjectsAsFaults = false
do {
let result = try context.fetch(request)
for data in result as! [Tasks] {
print(data.value(forKey: "taskName"))
}
} catch {
print("Failed")
}
dump(reminderList)
}
you could create an instance using CoreData and store it like an internal database.
These are some good tutorial to start with that:
https://medium.com/xcblog/core-data-with-swift-4-for-beginners-1fc067cca707
https://www.raywenderlich.com/7569-getting-started-with-core-data-tutorial
EDIT 2
As you can see in this image,
https://ibb.co/f1axcA
my list in coreData is of type [Notifica], so is an array of object Notifica, to implement codable you should do something like this
public class Notifica: NSObject, NSCoding {
public required init?(coder aDecoder: NSCoder) {
self.id = aDecoder.decodeObject(forKey: "id") as? Double
self.type = aDecoder.decodeObject(forKey: "type") as? String
self.idEvent = aDecoder.decodeObject(forKey: "idEvent") as? Int
self.contactPerson = aDecoder.decodeObject(forKey: "contactPerson") as? People
self.title = aDecoder.decodeObject(forKey: "title") as? String
self.date = aDecoder.decodeObject(forKey: "date") as? String
}
public func encode(with aCoder: NSCoder) {
aCoder.encode(id, forKey: "id")
aCoder.encode(type, forKey: "type")
aCoder.encode(idEvent, forKey: "idEvent")
aCoder.encode(contactPerson, forKey: "contactPerson")
aCoder.encode(title, forKey: "title")
aCoder.encode(date, forKey: "date")
}
ecc..
Another thing is to not call NSManagedObject and pass the entity, but you should name that Tasks as you called in dataModel, if you type Tasks on xcode it will fin for you the NSManagedObject created and then you can set the value for taskName
EDIT 3
"<Simple_Reminders.Reminder: 0x60400046da40>" means that a Reminder object exist! So you saved it! Reminder has two variable:
-reminderDescription and
-reminderLength, so change your code
do {
let result = try context.fetch(request)
for data in result as! [Tasks] {
print(data.value(forKey: "taskName"))
}
} catch {
print("Failed")
}
with this
do {
let result = try context.fetch(request)
for data in result as! [Tasks] {
print(data.value(forKey: "taskName"))
if let reminders = data.value(forKey: "taskName") as? [Reminder] {
for reminder in reminders {
// Now you have your single object Reminder and you can print his variables
print("Your reminder description is \(reminder. reminderDescription), and his length is \(reminder. reminderLength))"
}
}
}
} catch {
print("Failed")
}

Json data - white console - Xcode 9

I am trying a simple app in which I want to convert some values. It worked until I tried to convert the data in a dictionary, and when I hit run, it builds successfully, but the console does not print anything. Here is the code:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "http://gnb.dev.airtouchmedia.com/rates.json")
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil
{
print("ERROR")
}
else {
if let content = data {
do {
//Array
let myJson = try JSONSerialization.jsonObject(with:content, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject
//print(myJson)
if let rate = myJson["rate"] as? NSDictionary {
if let currency = rate["AUD"] {
print(currency)
}
}
}
catch {
}
}
}
}
task.resume()
}
because you are parsing JSON wrongly
try this
let myJson = try JSONSerialization.jsonObject(with:content, options: JSONSerialization.ReadingOptions.mutableContainers) as? [[String: AnyObject]] else { return }
for rate in myJson
guard let cur = user["from"] as? String,
let curRate = user["rate"] as? Double else { break }
if let cur = "AUD" {
print(curRate)
}
Update:
You are receiving Array of Objects in response,
so first you have to treat it as Array of object,
Then you have to loop through this objects and then inside that loop you have to extract the data you were looking for and play with it.

Firebase array became nil after loop in Swift

I'm trying to append element to array but it returns empty array.
What I'm trying to do is put elements that match up with tags user selected.
To achieve that, first I created model.
//Model
import UIKit
import Firebase
struct Team {
var key: String
var teamName: String
var league: String
var lat: Double
var lng: Double
init(snapshot: DataSnapshot) {
self.key = snapshot.key
self.teamName = (snapshot.value as! NSDictionary)["teamName"] as? String
self.league = (snapshot.value as! NSDictionary)["league"] as? String ?? ""
self.lat = (snapshot.value as! NSDictionary)["lat"] as? Double ?? 0
self.lng = (snapshot.value as! NSDictionary)["lng"] as? Double ?? 0
}
}
firebase structure looks like this.
{
"tags": {
"NewYork": {
uid1: 1
uid2: 1
uid3: 1
},
"baseball": {
uid1: 1
}
}
}
First, I fetched uids that match up with the tags. Then retrieve team's info based on uids that matched up with. If the same uid comes in like uid1 above, I want Xcode to print something. To do that I implemented like so(is there a better way?) but it's never been executed because results.count is always 0 even if after appended. However right after append method(I commented //here) it returns team's info properly.
func filterTeam(filterTag: FilteredTags) {
let tag = filterTag.selectedTag.components(separatedBy: "/") //this returns "NewYork" and "baseball"
var results = [Team]()
tag.forEach { (key) in
ref.child("tags").child(key).observe(.value, with: { (snapshot) in
guard let dictionary = snapshot.value as? [String:Any] else { return }
dictionary.forEach({ (uid,_) in
if results.count == 0 {
ref.child("teams").child(uid).observe(.value, with: { (snapshots) in
let team = Team(snapshot: snapshots)
results.append(team)
//here
print(results)
}, withCancel: nil)
} else {
for result in results {
if result.key == uid {
print("same uid!!")
} else {
ref.child("teams").child(uid).observe(.value, with: { (snapshots) in
let team = Team(snapshot: snapshots)
results.append(team)
}, withCancel: nil)
}
}
}
})
}, withCancel: nil)
}
}//func
How can I append properly and achieve what I want to do.
Thank you in advance!

Swift - read plist file to an array?

I have created a mini translation from English words to Spanish words. I would like to use the englishArray.plist instead of my englishArray = ["the cat"] How can I create this?
I have also used a localizable.strings to retrieve the value "the cat" for "el gato" but I would like to retrieve this from englishArray.plist
I started off with this but not sure if I'm on the right path
let path = NSBundle.mainBundle().pathForResource("englishArray", ofType: "plist")
let plistEnglishArray = NSArray(contentsOfFile: path!)
Here is the rest of my code:
var englishArray: [String] = ["rainbow", "boots", "heart", "leaf", "umbrella", "tired", "angry", "cry", "the cat" ]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.translateTextField.delegate = self
picker.delegate = self
picker.dataSource = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func translateButtonTapped(sender: UIButton) {
let emptyString = self.translateTextField.text
if (emptyString!.isEmpty) {
print("please enter a word")
}
for transIndex in englishArray.indices {
if englishArray[transIndex] == emptyString!.lowercaseString {
//englishArray
//translateLabel.text = "\(spanishArray[transIndex])"
translateLabel.text = NSLocalizedString(emptyString!.lowercaseString, comment:"")
print(emptyString)
return
}
}
}
Swift 4
The absolute simplest way to do this is
let url = Bundle.main.url(forResource: "Sounds", withExtension: "plist")!
let soundsData = try! Data(contentsOf: url)
let myPlist = try! PropertyListSerialization.propertyList(from: soundsData, options: [], format: nil)
The object myPlist is an Array or Dictionary, whichever you used as the base of your plist.
Change your root object to Array, then
var myEnglishArray: [String] = []
if let URL = NSBundle.mainBundle().URLForResource("englishArray", withExtension: "plist") {
if let englishFromPlist = NSArray(contentsOfURL: URL) as? [String] {
myEnglishArray = englishFromPlist
}
}
Swift 4
You can use Codable which is pure swift type.
Firstly load Plist file from bundle then use PropertyListDecoder
Complete code -
func setData() {
// location of plist file
if let settingsURL = Bundle.main.path(forResource: "JsonPlist", ofType: "plist") {
do {
var settings: MySettings?
let data = try Data(contentsOf: URL(fileURLWithPath: settingsURL))
let decoder = PropertyListDecoder()
settings = try decoder.decode(MySettings.self, from: data)
print("array is \(settings?.englishArray ?? [""])")//prints array is ["Good morning", "Good afternoon"]
} catch {
print(error)
}
}
}
}
struct MySettings: Codable {
var englishArray: [String]?
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
englishArray = try values.decodeIfPresent([String].self, forKey: .englishArray)
}
}
This will read a resource in your bundle with the name "englishArray.plist" and store it in the immutable variable english. It will be an Optional that you should test before using.
It uses a closure to read the file and return the array, this lets you use a immutable value rather than a mutable variable that can be changed. It's a good idea to use immutables wherever you can - they promote stability.
import Foundation
let english:[String]? = {
guard let URL = NSBundle
.mainBundle()
.URLForResource("englishArray", withExtension: "plist") else {
return nil
}
return NSArray(contentsOfURL: URL) as? [String]
}()
Here is the solution for swift 3. For this solution you do not need to change types in your plist structure (keep Dictionary, Array, as is). Also note that since your array's name in plist is also englishArray so the (value for key) argument in the second if statement is also englishArray.
var myEnglishArray: [String] = []
if let URL = Bundle.main.url(forResource: "englishArray", withExtension: "plist") {
guard let englishFromPlist = NSDictionary(contentsOf: URL) else { return [] }
if let englishArray = englishFromPlist.value(forKey: "englishArray") as? [String] {
for myEnglish in englishArray {
myEnglishArray.append(myEnglish)
}
}
}

struggling with JSON parsing in swift

I am trying to load data in JSON format from my server into IOS application.
Here is my JSON:
[
{
"BankName": "bank1",
"CurrencyName": "cur1",
"SellRate": "0.65",
"BuyRate": "0.55",
"OfficialRate": "0.6"
},
{
"BankName": "bank1",
"CurrencyName": "cur2",
"SellRate": "1.65",
"BuyRate": "1.55",
"OfficialRate": "1.6"
}
]
There are 2 files in my project:
1:
import Foundation
class Shot {
var bankName: String!
var currencyName: String!
var sellRate: String!
var buyRate: String!
var offRate: String!
init (data: NSDictionary) {
self.bankName = getStringFromJSON(data, key:"BankName")
self.currencyName = getStringFromJSON(data, key:"CurrencyName")
self.sellRate = getStringFromJSON(data, key:"SellRate")
self.buyRate = getStringFromJSON(data, key:"BuyRate")
self.offRate = getStringFromJSON(data, key: "OfficialRate")
}
func getStringFromJSON(data: NSDictionary, key: String) -> String {
if let info = data[key] as? String{
return info
}
return ""
}
}
2:
import Foundation
class JsonTest {
func loadJson(completion: ((AnyObject) -> Void)!) {
var urlString = "http://a.com/g.php"
let session = NSURLSession.sharedSession()
let sourceUrl = NSURL(string: urlString)
var task = session.dataTaskWithURL(sourceUrl!){
(data, response, error) -> Void in
if error != nil {
println(error.localizedDescription)
} else {
var error: NSError?
var jsonData = NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers, error: &error) as NSArray
var shots = [Shot]()
println(jsonData)
for shot in jsonData{
let shot = Shot(data: shot as NSDictionary)
shots.append(shot)
}
println(shots) //[jsontest.Shot, jsontest.Shot]
}
}
task.resume()
}
}
I am trying to populate array automatically when my app starts. To do it I have a code in my mainViewController class.
override func viewDidLoad() {
super.viewDidLoad()
let api = JsonTest()
api.loadJson(nil)
}
The problem occurs when I try to print shots variable in the second file.
it returns [jsontest.Shot, jsontest.Shot] when I was expecting the array of dictionaries.
println(jsonData) works fine and shows JSON data from URL.
Can anybody advise what is wrong in my program?
"shots" is an array of instances of Shot, not a dictionary:
class Shot {
var bankName: String!
var currencyName: String!
var sellRate: String!
var buyRate: String!
var offRate: String!
init (data: NSDictionary) {
self.bankName = getStringFromJSON(data, key:"BankName")
self.currencyName = getStringFromJSON(data, key:"CurrencyName")
self.sellRate = getStringFromJSON(data, key:"SellRate")
self.buyRate = getStringFromJSON(data, key:"BuyRate")
self.offRate = getStringFromJSON(data, key: "OfficialRate")
}
func getStringFromJSON(data: NSDictionary, key: String) -> String {
if let info = data[key] as? String{
return info
}
return ""
}
}
var shots = [Shot]()
let urlString = "http://almazini.lt/getrates.php"
let sourceUrl = NSURL(string: urlString)
// Using NSData instead of NSURLSession for experimenting in Playground
let data = NSData(contentsOfURL: sourceUrl!)
var error: NSError?
// As I'm using Swift 1.2 I had to change "as" with "as!"
let jsonData = NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers, error: &error) as! NSArray
for shot in jsonData{
let shot = Shot(data: shot as! NSDictionary)
shots.append(shot)
}
println(shots[0].bankName)
Update for Swift 2
var shots = [Shot]()
let urlString = "http://almazini.lt/getrates.php"
// Using NSData instead of NSURLSession for experimenting in Playground
if let sourceUrl = NSURL(string: urlString) {
NSURLSession.sharedSession().dataTaskWithURL(sourceUrl, completionHandler: { (data, response, error) in
if error == nil {
if let data = data, jsonData = try? NSJSONSerialization.JSONObjectWithData(data, options: []), jsonArray = jsonData as? [NSDictionary] {
for item in jsonArray {
let shot = Shot(data: item)
shots.append(shot)
}
print(shots[0].bankName)
} else {
print("no JSON data")
}
} else {
print(error!.localizedDescription)
}
}).resume()
}
Seems like there are two problems:
You're trying to use println to debug instead of setting a breakpoint and checking your objects values.
You have not created a description or debugDescription property for your object, so println on your object is just using some default implementation.
shots is an array of your custom object, so when you call println, it's using the description for Array, which prints out the objects in the array, comma separated, and within square brackets.
The default description property for classes in Swift just prints the class name.
Ideally, you should just use a break point to check the values of your object to be certain it initialized correctly, but if it's actually important to get them to print right, it's only a matter of implementing the description property:
override var description: String {
get {
// build and return some string that represents your Shot object
}
}

Resources