segue to specific Firebase data - ios

I have a tableView and it has about 5 cells and each cell has a different subject. Upon clicking on the cell, I will like to segue to a view controller that is populated with Firebase data only containing posts that has the specific subject clicked.
override func viewDidLoad() {
super.viewDidLoad()
FIRDatabase.database().reference().child("books").observeSingleEvent(of: .value, with: { (snapshot:FIRDataSnapshot) in
if let postsDictionary = snapshot .value as? [String: AnyObject] {
self.Category = postsDictionary["Category"] as? String ?? ""
}})
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "segue" {
let showBooks = segue.destination as! SearchTableViewController
showBooks.categorySegue = self.Category
}
}
In my Firebase database, I have a bunch of posts made by different users and in every post, they set the category to which the post belongs to. For example, if I click on the tableView cell that has the subject: Economics, I would like to segue to a view controller that only has the Economics category.
This is how I retrieve the post attributed to certain subjects:
databaseRef.child("books").queryOrdered(byChild: "category").queryEqual(toValue: categorySegue).observeSingleEvent(of: .value, with: { (snapshot) in
let key = snapshot.key
let snapshot = snapshot.value as? NSDictionary
snapshot?.setValue(key, forKey: "uid")
if(key == self.loggedUser?.uid)
{
print("same as logged in user")
}
else
{
self.usersArray.append(snapshot)
self.followUsersTableView.insertRows(at: [IndexPath(row:self.usersArray.count-1,section:0)], with: UITableViewRowAnimation.automatic)
}
}) { (error) in
print(error.localizedDescription)
}
tableView.tableFooterView = UIView()

If the category is directly below the posts node under a uniqueID, then try this
Example :
posts
-> post_id1
->category : "books"
-> other keys
-> post_id2
->category : "music"
-> other keys
The query to fetch the posts by category will be like this,
let strCategory = "music"
baseRef.child("posts").queryOrdered(byChild: "category").queryEqualToValue(strCategory).observeSingleEvent(of: .value, with: { (snapshot) in
print(snapshot)
})
To get the cell index on selection,
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let indexPath = tableView.indexPathForSelectedRow{
let selectedRow = indexPath.row
let categorySelected = Subjects[selectedRow]
}
}

Related

How to refresh DetailViewController when MasterViewController is updated

I have an application where the users can update and add tasks. But when the users update the task I need to update the details in the DetailViewController
When the users updates the task and saves the data in MasterViewController saves but the data in DetailViewController stays the same
How can I fix it so that the details get updated as well
The tasks are added and edited using the AddProjectController
I tried the following methods but none if them worked, all caused the the application to crash
let vc = DetailViewController()
let row = vc.view.layoutIfNeeded();
//Second method
let vc = MasterViewController()
vc.performSegue(withIdentifier: "showDetail", sender: vc.self)
The data is set in the DetailViewController using
override func viewDidLoad() {
super.viewDidLoad()
table.delegate = self
table.dataSource = self
lblName.text = detail.name ?? ""
lblCode.text = detail.code ?? ""
}
MasterViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetail" {
if let indexPath = tableView.indexPathForSelectedRow {
let object = fetchedResultsController.object(at: indexPath)
let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
controller.details = object;
controller.navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem
controller.navigationItem.leftItemsSupplementBackButton = true
detailViewController = controller
}
}
}
EditViewController
func editEvents(_ sender: Any){
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "Proj")
do {
let test = try context.fetch(fetchRequest)
var objectUpdate = test[0] as! Assesment;
objectUpdate.name = txtname.text ?? ""
objectUpdate.code = txtCode.text ?? "";
do {
try context.save()
}
catch {
print(error)
}
}
catch {
print(error)
}
}
I want to update the details when the editEvent function is finished executing
Is there anyway I can get this to work?
If You are using coreData .. You can implement FRC in details .. so you get refreshed data every time in callback ...
In your MasterViewController define a variable
Here is another workaround
private var controller : DetailViewController? = nil
Then in your Segue method use this controller
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetail" {
if let indexPath = tableView.indexPathForSelectedRow {
let object = fetchedResultsController.object(at: indexPath)
self.controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController // here you set your instence variable controller
controller.details = object;
controller.navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem
controller.navigationItem.leftItemsSupplementBackButton = true
detailViewController = controller
}
}
}
func editEvents(_ sender: Any){
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "Proj")
do {
let test = try context.fetch(fetchRequest)
var objectUpdate = test[0] as! Assesment;
objectUpdate.name = txtname.text ?? ""
objectUpdate.code = txtCode.text ?? "";
do {
try context.save()
if let detailsCotrollerExists = controller {
detailsCotrollerExists.updateDataWithObject(objectUpdate) // function you will write in detail class which takes object and refresh data there .. Either outlets or Table/Collection View reload
}
}
catch {
print(error)
}
}
catch {
print(error)
}
}
Then you write a function in DetailViewController func updateDataWithObject(_ obj: Assesment) function you will write in detail class which takes object and refresh data there .. Either outlets or Table/Collection View reload
Write function in DetailViewController
func updateDataWithObject(_ obj: Assesment) {
// Refresh data
}

Swift Firebase TableView Data - DataEventType.value

I am doing a call into a child "following" and am seeing if the logged-in user's UID is there and has a child of another user which the logged-in user is following.
I am printing who the logged-in user is following into a tableview. The first problem is my code, because I know it is bad practice to have two firebase calls within each other so I need someone to teach me a better method. Because of the poor code, when I go unfollow the other user and come back to the tab where the logged-in users list of who they are following is displayed it shows this (image below). When the logged-in user is following nobody it should just display the "Sorry!" text, yet still keeps who the user was following. Need someone to teach me a better method for doing this type of firebase call. Code and a firebase JSON stack image are below... In the firebase JSON stack image, the expanded UID is the logged-in user and the child in is the other user the logged-in user is following. I need a better way to call and extract this information, I am just ignorant of how-to.
func getFollowingData() {
Database.database().reference().child("following").child(uid!).observe(DataEventType.value, with: { (snapshot) in
if snapshot.exists() {
print("Got Snapshot")
Database.database().reference().child("following").child(self.uid!).observe(.childAdded, with: { (snapshot) in
if snapshot.exists() {
print(snapshot)
let snapshot = snapshot.value as? NSDictionary
self.listFollowing.append(snapshot)
self.followingTableView.insertRows(at: [IndexPath(row:self.listFollowing.count-1,section:0)], with: UITableViewRowAnimation.automatic)
self.followingTableView.backgroundView = nil
}
})
} else {
print("No Snapshot")
self.followingTableView.backgroundView = self.noDataView
}
})
}
Figured it out, just needed to do it how I did it before on other feeds.
import UIKit
import Firebase
class BusinessFollowing: UITableViewController {
#IBOutlet var noDataView: UIView!
#IBOutlet var followingTableView: UITableView!
var yourFollowing = [Information]()
var listFollowing = [NSDictionary?]()
var databaseRef = Database.database().reference()
let uid = Auth.auth().currentUser?.uid
var loggedInUser = Auth.auth().currentUser
var loggedInUserData:NSDictionary?
var following = [String]()
override func viewDidLoad() {
super.viewDidLoad()
self.followingTableView.backgroundView = nil
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.followingTableView.reloadData()
self.yourFollowing.removeAll()
self.following.removeAll()
getFollowingData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
if segue.identifier == "following" {
// gotta check if we're currently searching
if let indexPath = followingTableView.indexPathForSelectedRow {
let user = listFollowing[indexPath.row]
let controller = segue.destination as? ExploreBusinessProfileSwitchView
controller?.otherUser = user
}
}
}
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return self.yourFollowing.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! BusinessFollowingCell
let following = yourFollowing[indexPath.row]
let businessName = following.businessName
let businessStreet = following.businessStreet
let businessCity = following.businessCity
let businessState = following.businessState
cell.businessName.text = businessName
cell.businessStreet.text = businessStreet
cell.businessCity.text = businessCity
cell.businessState.text = businessState
// cell.businessName?.text = self.listFollowing[indexPath.row]?["businessName"] as? String
// cell.businessStreet?.text = self.listFollowing[indexPath.row]?["businessStreet"] as? String
// cell.businessCity?.text = self.listFollowing[indexPath.row]?["businessCity"] as? String
// cell.businessState?.text = self.listFollowing[indexPath.row]?["businessState"] as? String
return cell
}
func getFollowingData() {
self.yourFollowing.removeAll()
self.following.removeAll()
self.followingTableView.reloadData()
Database.database().reference().child("Businesses").child((loggedInUser?.uid)!).child("following").observe(.value, with: { snapshot in
if snapshot.exists() {
MBProgressHUD.showAdded(to: self.view, animated: true)
let databaseRef = Database.database().reference()
databaseRef.child("Businesses").queryOrderedByKey().observeSingleEvent(of: .value, with: { (usersSnapshot) in
let users = usersSnapshot.value as! [String: AnyObject]
for (_, value) in users {
if let userID = value["uid"] as? String {
if userID == Auth.auth().currentUser?.uid {
print(value)
if let followingUsers = value["following"] as? [String : String] {
for (_,user) in followingUsers {
self.following.append(user)
}
}
databaseRef.child("following").queryOrderedByKey().observeSingleEvent(of: .value, with: { (postsSnapshot) in
let posts = postsSnapshot.value as! [String: AnyObject]
for (_, post) in posts {
for (_, postInfo) in post as! [String: AnyObject] {
if let followingID = postInfo["uid"] as? String {
for each in self.following {
if each == followingID {
guard let uid = postInfo["uid"] as! String? else {return}
guard let name = postInfo["businessName"] as! String? else {return}
guard let address = postInfo["businessStreet"] as! String? else {return}
guard let state = postInfo["businessState"] as! String? else {return}
guard let city = postInfo["businessCity"] as! String? else {return}
self.yourFollowing.append(Information(uid: uid, businessName: name, businessStreet: address, businessCity: city, businessState: state))
}
self.followingTableView.backgroundView = nil
self.followingTableView.reloadData()
}
}
}
}
MBProgressHUD.hide(for: self.view, animated: true)
}) { (error) in
print(error.localizedDescription)
}
}
}
}
})
} else {
print("Not following anyone")
self.followingTableView.backgroundView = self.noDataView
MBProgressHUD.hide(for: self.view, animated: true)
}
})
}
}

Segue using tableViewCells

I am going to try to be as clear as I can be on this question but if you need more information please please ask. I have a tableviewcontroller that has a list of all the messages the logged in user has had with other users of the app. When clicked the logged in user clicks a cell, I would like for the user to segue to a view controller that allows them to chat with whatever user they like. This chat was acquired using JSQMessageController. However, when I set it the segue in the tableviewcontroller, show below:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let message = messages[indexPath.row]
if message.ReceiverId != self.loggedInUserUid {
var newVariable = message.ReceiverId
if self.userpicuid == newVariable {
let ref = FIRDatabase.database().reference().child("users").child(userpicuid!)
ref.observeSingleEvent(of: .value, with: { (snapshot)
in
if let dictionary = snapshot.value as? [String: AnyObject]{
for post in dictionary {
let messages = post.value as! [String: AnyObject]
for (id, value) in messages {
self.username = messages["username"] as? String
}}}})}} else if message.senderId != self.loggedInUserUid {
let newVariable = message.senderId
if self.userpicuid == newVariable {
let ref = FIRDatabase.database().reference().child("users").child(userpicuid!)
ref.observeSingleEvent(of: .value, with: { (snapshot)
in
if let dictionary = snapshot.value as? [String: AnyObject]{
for post in dictionary {
let messages = post.value as! [String: AnyObject]
for (id, value) in messages {
self.username = messages["username"] as? String
}}}})}
}
performSegue(withIdentifier: "MessageNow", sender: self.userpicuid)
}
override public func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard segue.identifier == "MessageNow", let chatVc = segue.destination as? SendMessageViewController else {
return
}
chatVc.senderId = self.loggedInUser?.uid
chatVc.receiverData = sender as AnyObject
chatVc.senderDisplayName = self.userpicuid
chatVc.username = self.username
}
I get an error in the MessageViewController:
var receiverData: AnyObject?
override func viewDidLoad() {
super.viewDidLoad()
let receiverId = receiverData as! String
let receiverIdFive = String(receiverId.characters.prefix(5))
let senderIdFive = String(senderId.characters.prefix(5))
if (senderIdFive > receiverIdFive)
{
self.convoId = senderIdFive + receiverIdFive
}
else
{
self.convoId = receiverIdFive + senderIdFive
}}
I get the error on the let receiverId = receiverData as! String that:
Could not cast value of type 'Chat_App.MessageTableViewCell' (0x10eb2ef10) to 'NSString' (0x110ab1c60).
in a different view controller, I have:
#IBAction func sendMessage(_ sender: Any) {
performSegue(withIdentifier: "sendMessageToUser", sender: self.userpicuid)
}
override public func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard segue.identifier == "sendMessageToUser", let chatVc = segue.destination as? SendMessageViewController else {
return
}
chatVc.senderId = self.loggedInUser?.uid
chatVc.receiverData = sender as! String!
chatVc.senderDisplayName = self.userpicuid
chatVc.username = self.username
}
And it segues perfectly.
Sender is Any? and you're casting it to AnyObject. AnyObject refers to a class type, and it's asserting when you attempt to cast it to a Swift value type (String).
Try this instead:
chatVc.receiverData = NSString(string: sender as! String)
Your sender is the the UITableViewCell that initiated the segue. You are crashing when casting it to NSString. Remove this line
let receiverId = receiverData as! String
In prepareForSegue do this instead
chatVc.receiverData = self.userpicuid

PickerInlineRow, PickerRow - Best Practices for populating options

Fairly new to iOS. I am using Firebase for the backend data, and I'm wondering about the recommended approach for populating option lists for things such as a PickerInlineRow. What I have typically done is the following;
Create variables to hold the data used in my form
Call Firebase to retrieve the data
Load the values from Firebase into my local variables
In the closure for the Firebase call, load the form
In the form, populate the values by using my variables
Update the variables using .onchange events
When the user saves, the variables are used to update the database. This all works, but the problem comes about when trying to populate dropdowns within the form. I know how to set options for the picker, but unclear as to how to structure the sequence so that the array I use for options is populated prior to use. If I set the options to an array, but the array hasn't finished populating, the picker has no values.
What's the recommended way to coordinate these events? I've pasted an example Eureka form below.
import UIKit
import Firebase
import GeoFire
import Eureka
class QuestDetailsViewController: FormViewController {
let ref: FIRDatabaseReference = FIRDatabase.database().reference()
var key = String()
var isNew = Bool()
var locationKeys = [String]()
var locationNames = [String]()
var locationKey: String?
var locationName: String?
var startDate: Date?
var endDate: Date?
override func viewDidLoad() {
super.viewDidLoad()
loadData()
}
func loadData(){
// load lookup values
if isNew == false {
ref.child("Quests").child(key).observeSingleEvent(of: .value, with: {(snapshot) in
if let item = snapshot.value as? [String:AnyObject]{
self.locationName = item["LocationName"] as? String
self.locationKey = item["LocationKey"] as? String
self.startDate = DateFromInterval(interval: (item["StartDate"] as? Double)!)
self.endDate = DateFromInterval(interval: (item["EndDate"] as? Double)!)
}
self.loadDropdowns()
} , withCancel: {error in
print("Error : \(error.localizedDescription)")
})
}
else {
self.loadDropdowns()
}
}
func loadDropdowns() {
ref.child("Places").queryOrdered(byChild: "PlaceName").observeSingleEvent(of: .value, with: {(snapshot) in
for item in (snapshot.children.allObjects as? [FIRDataSnapshot])! {
let thisPlace = item.value as! [String: AnyObject]
self.locationKeys.append(item.key)
self.locationNames.append(thisPlace["PlaceName"] as! String)
}
self.loadForm()
}, withCancel: {error in
})
}
func loadForm() {
form +++ PickerInlineRow<String>() {
$0.tag = "locationPicker"
$0.title = "Location"
$0.options = locationNames
$0.value = self.locationName
}.onChange({ (row) in
self.locationName = row.value
let itemIndex = self.locationNames.index(of: self.locationName!)
self.locationKey = self.locationKeys[itemIndex!]
})
<<< DateTimeRow() {
$0.tag = "startDate"
$0.title = "From"
$0.value = self.startDate
}.onChange({ (row) in
self.startDate = row.value
})
<<< DateTimeRow() {
$0.tag = "endDate"
$0.title = "To"
$0.value = self.endDate
}.onChange({ (row) in
self.endDate = row.value
})
+++ ButtonRow() {
$0.title = "Challenges"
$0.presentationMode = PresentationMode.segueName(segueName: "segueChallenges", onDismiss: nil)
}
+++ ButtonRow() {
$0.title = "Save Changes"
}.onCellSelection({ (cell, row) in
self.saveChanges()
})
}
func saveChanges() {
let childUpdates = ["LocationKey": locationKey!, "LocationName": locationName!, "StartDate": IntervalFromDate(date: startDate!), "EndDate": IntervalFromDate(date: endDate!)] as [String : Any]
if isNew == true {
key = ref.child("Quests").childByAutoId().key
}
ref.child("Quests").child(key).updateChildValues(childUpdates, withCompletionBlock: {(error, ref) in
self.navigationController?.popViewController(animated: true)
})
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "segueChallenges" {
let vc = segue.destination as? ChallengesTableViewController
vc?.questKey = key
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
You want to load the form without any values, then when the values come back from firebase, you can reload the rows using row tags
//Completion handler of Firebase {
if let row = self.form.rowBy(tag: "rowTag") as? PickerInlineRow<RowType> {
row.reload()
}
}

passing data with performSegueWithIdentifier nil

I have the segue setup as:
and the tableView row selection:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
// Ensure controller knows which dataset to pull from,
// so detail view is correct
var friendChat: Friend!
if searchController.active && searchController.searchBar.text != "" {
friendChat = filterMappedFriends[indexPath.row]
} else {
friendChat = mappedFriends[indexPath.row]
}
// Now set the conditional cases: if a friend then chat, if user then friend request if not user then can invite them:
if(friendChat.statusSort == 2) {
self.performSegueWithIdentifier("showIndividualChat", sender: friendChat)
} else if (friendChat.statusSort == 1) {
print("Can invite to be friend")
} else if (friendChat.statusSort == 0) {
print("Invite to Feast")
}
}
and the prepareForSegue:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let indexPath = tableView.indexPathForSelectedRow {
// Ensure controller knows which dataset to pull from,
// so detail view is correct
let friendChat: Friend
if searchController.active && searchController.searchBar.text != "" {
friendChat = filterMappedFriends[indexPath.row]
} else {
friendChat = mappedFriends[indexPath.row]
}
// Now set the conditional cases: if a friend then chat, if user then friend request if not user then can invite them:
if segue.identifier == "showIndividualChat" {
let controller = segue.destinationViewController as! IndividualChatController
controller.friendChat = friendChat
controller.senderId = Global.sharedInstance.userID
controller.senderDisplayName = Global.sharedInstance.userName
}
}
}
However, the object friendChat, seen in controller.friendChat, of the destination controller is always nil.
How can I pass the data:
controller.friendChat = friendChat
controller.senderId = Global.sharedInstance.userID
controller.senderDisplayName = Global.sharedInstance.userName
to the destination controller successfully?
The first thing you are doing in didSelectRowAtIndexPath is deselecting the row, so when you try and access the selected row in prepareForSegue you are going to get no row selected.
Since you are passing the Friend instance as your sender to performSegueWithIdentifier you can just say let friendChat = sender as? Friend in prepareForSegue;
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
if segue.identifier == "showIndividualChat" {
if let friendChat = sender as? Friend {
let controller = segue.destinationViewController as! IndividualChatController
controller.friendChat = friendChat
controller.senderId = Global.sharedInstance.userID
controller.senderDisplayName = Global.sharedInstance.userName
}
}
}
For Swift 3
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
if segue.identifier == "showIndividualChat" {
if let friendChat = sender as? Friend {
let controller = segue.destination as! IndividualChatController
controller.friendChat = friendChat
controller.senderId = Global.sharedInstance.userID
controller.senderDisplayName = Global.sharedInstance.userName
}
}
}

Resources