PickerInlineRow, PickerRow - Best Practices for populating options - ios

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()
}
}

Related

Swift display firebase data on tableview not shown

im making application with firebase as a database, and i seems cant to show my data to my tableview. i check on my firebase all my data is good even when i add new data the data is immediately shown in my firebase. but seems like thers some miss logic i have here....can someone help me?
*Edit theres 1 line where the code wont work
this is my main controller:
class MainController: UITableViewController, AddPatientControllerr {
private var patientLists = [PatientList]()
var Segue : String = "PatientName"
var Segue2 : String = "PatientNotes"
let user : User = Auth.auth().currentUser!
private var rootRef : DatabaseReference!// 1. buat nyambung ke root db
override func viewDidLoad() {
super.viewDidLoad()
self.rootRef = Database.database().reference()
populateList()
tableView.delegate = self
tableView.dataSource = self
}
// MARK : Firebase Function
private func populateList() {
self.rootRef.child(self.user.emailWithoutSpecialChar).observe(.value) { (snapshot) in
self.patientLists.removeAll()
let pasienListDict = snapshot.value as? [String:Any] ?? [:]
for (key,_) in pasienListDict {
if let pasienlistdict = pasienListDict[key] as? [String:Any]{
if let pasienlist = PatientList(pasienlistdict) { // this line of code is not working
self.patientLists.append(pasienlist)
}else {
print("your condition not working")
}
}
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
// MARK : Func delegate
func addPatientData(controller: UIViewController, nama: String, tglLahir: String, Telp: String, berat: String, Tinggi: String, golDarah: String) {
let patientList = PatientList(name: nama, tglLahir: tglLahir, Telp: Telp, berat: berat, Tinggi: Tinggi, golDarah: golDarah)
self.patientLists.append(patientList)
let userRef = self.rootRef.child(self.user.emailWithoutSpecialChar)
let patientListRef = userRef.child(patientList.name)
patientListRef.setValue(patientList.toDictionary())
controller.dismiss(animated: true, completion: nil)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
// MARK : Segue
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == Segue {
let nc = segue.destination as! UINavigationController
let addPatientName = nc.viewControllers.first as! ProfileController
addPatientName.delegate = self
}
else if segue.identifier == Segue2 {
guard let indexPath = self.tableView.indexPathForSelectedRow else {return}
let nc = segue.destination as! PasienProfileController
nc.pasien = self.patientLists[indexPath.row]
}
}
//MARK : TableView
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let pasienList = self.patientLists[indexPath.row]
let pasienListRef = self.rootRef.child(pasienList.name)
pasienListRef.removeValue()
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.patientLists.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as? MainCell else {return UITableViewCell()}
let patientListt = self.patientLists[indexPath.row]
cell.NameLbl.text = patientListt.name
return cell
}
}
and this is the model where i keep the data and dictionary :
import Foundation
typealias JSONDictionary = [String:Any]
class PatientList {
var name : String!
var tglLahir : String!
var Telp : String!
var berat : String!
var Tinggi : String!
var golDarah : String!
var patientNote :[PatientNote] = [PatientNote]() //ini buat nyimpen notes2 dari tiap2 pasien
init(name : String, tglLahir : String, Telp : String, berat : String, Tinggi : String, golDarah : String) {
self.name = name
self.tglLahir = tglLahir
self.berat = berat
self.Tinggi = Tinggi
self.golDarah = golDarah
self.Telp = Telp
}
init?(_ dictionary :[String:Any]){
guard let name = dictionary["Name"] as? String else {
return nil
}
guard let berat = dictionary["BeratBadan"] as? String else {
return nil
}
guard let tglLahir = dictionary["TanggalLahir"] as? String else {
return nil
}
guard let Tinggi = dictionary["TinggiBadan"] as? String else {
return nil
}
guard let golDarah = dictionary["GolonganDarah"] as? String else {
return nil
}
guard let Telp = dictionary["Telefon"] as? String else {
return nil
}
self.name = name
self.berat = berat
self.tglLahir = tglLahir
self.Tinggi = Tinggi
self.golDarah = golDarah
self.Telp = Telp
let pasienListDictionary = dictionary["patientNote"] as? [JSONDictionary]
if let dictionaries = pasienListDictionary {
self.patientNote = dictionaries.compactMap(PatientNote.init)
}
}
func toDictionary() -> [String:Any] { // ini buat dictionary buat convery object jd string:any, jd biar ga ngubah satu2 kl ada yg salah gituu
return ["Name":self.name, "BeratBadan":self.berat, "TanggalLahir":self.tglLahir, "TinggiBadan":self.Tinggi, "golDarah":self.golDarah, "Telefon":self.Telp, "patientNote":self.patientNote.map{ patientNote in
return patientNote.toDictionary()
}]
}
}
and this is my firebase :
[![Firebase][1]][1]
for the starters i just need to show my name into my tableview which in this case i cant even show the name data in my tableview
i cant seems to find out why the data wont show in my tableview....xcode not showing error
anyone can help me? thanks
firebase Json
"afipermanalivecom" : {
"Apiyyy" : {
"BeratBadan" : "",
"Name" : "Apiyyy",
"TanggalLahir" : "20-05-2020",
"Telefon" : "",
"TinggiBadan" : "",
"golDarah" : "A+"
},
"CocaCola" : {
"BeratBadan" : "80",
"Name" : "CocaCola",
"TanggalLahir" : "20-06-2020",
"Telefon" : "0878099996049",
"TinggiBadan" : "190",
"golDarah" : "A-"
},
"Jamsey" : {
"BeratBadan" : "",
"Name" : "Jamsey",
"TanggalLahir" : "19-06-2020",
"Telefon" : "",
"TinggiBadan" : "",
"golDarah" : "A-"
}
},
"puffygmailcom" : {
"Batman" : {
"Name" : "Batman"
},
"Stitchh" : {
"Name" : "Stitchh"
}
}
From the code and structure in the question it appears there's a list of doctors and patients with the patients being child nodes of the doctor. I'll post a solution and then some important recommendations about changes.
Here's the existing Firebase structure. Note that we are NOT using email addresses as node keys - it's a lot of extra work and if the email address changes, the entire database will have be scanned, nodes read, deleted and re-written. Dynamic node keys (ones that could change, like an email) should instead be generated with .childByAutoId - I am using doctor_0, doctor_1 etc for readability.
{
"doctor_0" : {
"name" : "Dr. Doolittle",
"patient_list" : {
"patient_0" : {
"blood_type" : "O-",
"name" : "Henry"
},
"patient_1" : {
"blood_type" : "AB-",
"name" : "Leroy"
}
}
},
"doctor_1" : {
"name" : "Dr. McCoy",
"patient_list" : {
"patient_2" : {
"blood_type" : "O+",
"name" : "Steve"
}
}
}
}
I have two classes to hold this data, a DoctorClass and PatientClass with the Patient class being an array within the DoctorClass. Note that a Doctor may not have any Patients so I am treating that as an optional.
class DoctorClass {
var doc_id = ""
var doc_name = ""
var patients = [PatientClass]()
convenience init(withId: String, andName: String, maybePatientList: DataSnapshot?) {
self.init()
self.doc_id = withId
self.doc_name = andName
if let patientList = maybePatientList {
let allPatientsSnap = patientList.children.allObjects as! [DataSnapshot]
for patientSnap in allPatientsSnap {
let patient = PatientClass(patientSnap: patientSnap)
self.patients.append(patient)
}
}
}
}
class PatientClass {
var patient_id = ""
var patient_name = ""
var blood_type = ""
convenience init(patientSnap: DataSnapshot) {
self.init()
self.patient_id = patientSnap.key
self.patient_name = patientSnap.childSnapshot(forPath: "name").value as? String ?? "No Name"
self.blood_type = patientSnap.childSnapshot(forPath: "blood_type").value as? String ?? "No blood type"
}
}
and finally the code to read in all of the doctors and populate their patient array with their patients.
var docAndPatientList = [DoctorClass]()
func loadDoctorsAndPatients() {
let doctorsRef = self.ref.child("doctors") //self.ref points to *my* firebase
doctorsRef.observeSingleEvent(of: .value, with: { snapshot in
let allDoctorsSnapshot = snapshot.children.allObjects as! [DataSnapshot]
for docSnap in allDoctorsSnapshot {
let docId = docSnap.key
let docName = docSnap.childSnapshot(forPath: "name").value as? String ?? ""
let patientSnap = docSnap.childSnapshot(forPath: "patient_list")
let doc = DoctorClass(withId: docId, andName: docName, maybePatientList: patientSnap)
self.docAndPatientList.append(doc)
}
})
}
and then let's print them out
func printDoctorsAndPatients() {
self.docAndPatientList.forEach { doctor in
print("Dr: \(doctor.doc_name)")
for patient in doctor.patients {
print(" patient: \(patient.patient_name) bloodtype: \(patient.blood_type)")
}
}
}
and the output
Dr: Dr. Doolittle
patient: Henry bloodtype: O-
patient: Leroy bloodtype: AB-
Dr: Dr. McCoy
patient: Steve bloodtype: O+
That will work with the existing structure but what if, for example, a patient has two doctors? Or what if we want to query Firebase for all patients that have blood type O-? It's not going to work (easily) with that structure.
Here's a better plan
root_ref
doctors
doctor_0
name : "Dr. Doolittle"
patients
patient_0 : true
patient_1 : true
doctor_1
name : "Dr. McCoy"
patients
patient_2 : true
and patients
root_ref
patients
patient_0
blood_type : "O-"
doctors:
doctor_0: true
name : "Henry"
patient_1
blood_type : "AB-"
doctors:
doctor_0: true
name : "Leroy"
patient_2
blood_type : "O+"
doctors:
doctor_1: true
name : "Steve"
This structure provides way more query flexibility and scaleability.
Just some tips and perhaps a solution,
I would recommend using firebase Cloud instead of firebase real time firebase, its much fast and more reliable especially if your trying to query data from arrays or dictionary, I can see you are trying to retrieve data from a dictionary, one thing you want to note is that firebase stores your swift dictionary as an objective C dictionary, so thats one thing you want to note! Try to check if you used the correct reuse identifiers.
Let me know if you still can't get it!
If you would like to return a list of data, you would have to use .childAdded instead of .value
So, your code would be something like this:
self.rootRef.child(self.user.emailWithoutSpecialChar).observe(.childAdded) { (snapshot) in
// do your stuff here
}
*salam sesama orang Indonesia :)

error: Index out of range in segment control

While using segment control to populate the respective list under different segments, issues I am facing are as below
When the segment control is loaded in the tableview , the first segment is working fine and when I switch second segment it do populate the list but it gives error of Index Out Of Range when the list scrolled
The second segment control should load the different list as per the filter, but it loads the same list as the first segment control
I am using Swift IOS and Firestore Database for this
Below is the code I am sharing
class FirstSegementViewController: UIViewController {
#IBOutlet var segmentControl:UISegmentedControl!
#IBOutlet var tableView: UITableView!
var firstDetails:[FirstDetails] = []
var secondDetails:[SecondDetails] = []
var postKey:String = ""
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
// Do any additional setup after loading the view.
retrieveAllPosts()
}
func retrieveAllPosts(){
let postsRef = Firestore.firestore().collection("posts").whereField("post_author_id", isEqualTo: Auth.auth().currentUser!.uid
).whereField("status", isEqualTo: false).limit(to: 50)
postsRef.getDocuments { (snapshot, error) in
if let error = error {
print(error.localizedDescription)
} else {
if let snapshot = snapshot {
for document in snapshot.documents {
let data = document.data()
//self.postKey = document.documentID
let username = data["post_author_username"] as? String ?? ""
let postTitle = data["postTitle"] as? String ?? ""
let postcategory = data["postcategory"] as? String ?? ""
let postContent = data["postContent"] as? String ?? ""
let postAuthorProfilePicUrl = data["post_user_profile_pic_url"] as? String ?? ""
let postAuthorSpinnerC = data["post_author_spinnerC"] as? String
let newSourse = FirstDetails(_documentId: document.documentID, _username: username, _postTitle: postTitle, _postcategory: postcategory, _postContent: postContent, _postuserprofileImagUrl: postAuthorProfilePicUrl, _postAuthorSpinncerC: postAuthorSpinnerC)
self.firstDetails.append(newSourse)
// print(self.postKey)
}
self.tableView.reloadData()
}
}
}
}
func retrieveAllPosts2(){
let postsRef = Firestore.firestore().collection("posts").whereField("post_author_id", isEqualTo: Auth.auth().currentUser!.uid).whereField("status", isEqualTo: true).limit(to: 50)
postsRef.getDocuments { (snapshot, error) in
if let error = error {
print(error.localizedDescription)
} else {
if let snapshot = snapshot {
for document in snapshot.documents {
let data = document.data()
//self.postKey = document.documentID
let username = data["post_author_username"] as? String ?? ""
let postTitle = data["postTitle"] as? String ?? ""
let postcategory = data["postcategory"] as? String ?? ""
let postContent = data["postContent"] as? String ?? ""
let postAuthorProfilePicUrl = data["post_user_profile_pic_url"] as? String ?? ""
let postAuthorSpinnerC = data["post_author_spinnerC"] as? String
let newSourse1 = SecondDetails(_documentId: document.documentID, _username: username, _postTitle: postTitle, _postcategory: postcategory, _postContent: postContent, _postuserprofileImagUrl: postAuthorProfilePicUrl, _postAuthorSpinncerC: postAuthorSpinnerC)
self.secondDetails.append(newSourse1)
}
self.tableView.reloadData()
}
}
}
}
#IBAction func indexChanged(_ sender: UISegmentedControl) {
switch segmentControl.selectedSegmentIndex
{
case 0:
retrieveAllPosts()
// label1.text = "First Segment Selected"
case 1:
retrieveAllPosts2()
// label1.text = "Second Segment Selected"
default:
break
}
//self.tableView.reloadData()
}
#objc func toComments(_ sender: AnyObject) {
let commentbutton = sender as! UIButton
let post = firstDetails[commentbutton.tag]
postKey = post._documentId // or what key value it is
print("hello")
performSegue(withIdentifier: "toCommentsListforMWs", sender: self)
}
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
var vc = segue.destination as! CommentListViewController
vc.postId = postKey
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}
extension FirstSegementViewController: UITableViewDelegate, UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var value = 0
switch segmentControl.selectedSegmentIndex{
case 0:
value = firstDetails.count
break
case 1:
value = secondDetails.count
break
default:
break
}
return value
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath) as! MyWsPostCell
switch segmentControl.selectedSegmentIndex{
case 0:
cell.firstdetails1 = firstDetails[indexPath.row]
cell.commentbutton.tag = indexPath.row
cell.commentbutton.addTarget(self, action: #selector(toComments(_:)), for: .touchUpInside)
break
case 1:
cell.completed1 = secondDetails[indexPath.row]
cell.commentbutton.tag = indexPath.row
cell.commentbutton.addTarget(self, action: #selector(toComments(_:)), for: .touchUpInside)
break
default:
break
}
return cell
}
}
Change your indexChanged(_ sender: UISegmentedControl) function like this:
#IBAction func indexChanged(_ sender: UISegmentedControl) {
switch segmentControl.selectedSegmentIndex
{
case 0:
self.firstDetails.removeAll()
retrieveAllPosts()
// label1.text = "First Segment Selected"
case 1:
self.secondDetails.removeAll()
retrieveAllPosts2()
// label1.text = "Second Segment Selected"
default:
break
}
//self.tableView.reloadData()
}
In your retrieveAllPosts() and retrieveAllPosts2() you need to empty the data you have already stored in the firstDetails and secondDetails array. Currently you are just appending the data everytime user changes the segment index.
func retrieveAllPosts() {
self.firstDetails = []
//all of your code here
}
func retrieveAllPosts2() {
self.secondDetails = []
//all of your code here
}
Before your Api hit, or before adding data to array remove the previous data in the array.
#IBAction func indexChanged(_ sender: UISegmentedControl) {
switch segmentControl.selectedSegmentIndex
{
case 0:
self.firstDetails.removeAll()// Remove data in array before u call Api
retrieveAllPosts()
case 1:
self.secondDetails.removeAll()// Remove data in array before u call Api
retrieveAllPosts2()
default:
break
}
}

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)
}
})
}
}

Passing data from a class to same instance of a viewcontroller

When a specific event happens(in my case when a tab bar is changed) I want to create a new link from an Array. I have gotten this to work but the problem I am not facing is when i try to pass the generated link to the same viewcontroller i get an error
fatal error: unexpectedly found nil while unwrapping an Optional value
This happens when I try to change the UILabel movietitle and imageview. I think this is because every time it sends the link it creates a new ViewController instead of using the existing one. Might also be that i have missed an unwrapped value somewhere. Hope someone here can help me!
StringBuilder:
import UIKit
class StringBuilder: NSObject {
let urlString = "https://api.themoviedb.org/3/discover/movie?api_key=935f539acb9e5534ddeed3fb57e&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=1&with_genres=12"
let urlStringMultipleGenres = "https://api.themoviedb.org/3/discover/movie?api_key=935f539acbf5534ddeed3fb57e&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=1&with_genres=28,12,10749"
var currentGenreArray: Array<Int> = []
//This will be run after the user has selected or deselected genres in the genreControllerView
func updateGenres(genreArrayIn: Array<Int>){
print("update genres input: ")
print(genreArrayIn)
//If new array input is the same as old arrayinput, do nothing
if genreArrayIn == currentGenreArray{
return
}
else{
let returnedLink = generateString(genreID: genreArrayIn)
print("Returned link after generate string" + returnedLink)
sendLink(link: returnedLink)
}
}
//After the updated genres have been put into an Array, this function will generate the whole string which
//will be the main String the getMovieRequest follows
func generateString(genreID: Array<Int>) -> String{
let filteredGenreArray = filterZeroes(unfilteredArray: genreID)
currentGenreArray = genreID
print("current genre array: ")
print(currentGenreArray)
let baseString = "https://api.themoviedb.org/3/discover/movie?api_key=935f539acbfed4ddeed3fb57e&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=1&with_genres="
var generatedString = baseString
for id in filteredGenreArray{
let k = String(id)
generatedString += k + ","
}
print("Generated Link from Strinbuilder: ")
print(generatedString)
return generatedString
}
func filterZeroes(unfilteredArray: Array<Int>) -> Array<Int>{
let filteredGenreArray = unfilteredArray.filter {$0 > 0}
print("filtered array: ")
print(filteredGenreArray)
return filteredGenreArray
}
func sendLink(link: String){
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let movieVC = storyBoard.instantiateViewController(withIdentifier: "movieView") as! ViewController
movieVC.getMovieData(activeGenreLink: link)
print("new link sent from sendlink()")
}
}
ViewController:
import UIKit
import Alamofire
import AlamofireImage
class ViewController: UIViewController{
static let sharedInstance = ViewController()
var movieIndex = 0
var movieArray:[Movie] = []
var downloadGrp = DispatchGroup()
#IBOutlet var uiMovieTitle: UILabel!
#IBOutlet var uiMoviePoster: UIImageView!
#IBOutlet var posterLoading: UIActivityIndicatorView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let firstTimeLink = "https://api.themoviedb.org/3/discover/movie?api_key=935f539acb9e5534ddeed3fb57e&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=1&with_genres=35,18"
getMovieData(activeGenreLink: firstTimeLink)
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(imageTapped(tapGestureRecognizer:)))
uiMoviePoster.isUserInteractionEnabled = true
uiMoviePoster.addGestureRecognizer(tapGestureRecognizer)
print("settings Sucessful")
}
func imageTapped(tapGestureRecognizer: UITapGestureRecognizer){
performSegue(withIdentifier: "detailsSegue", sender: self)
}
#IBAction func yesBtn(_ sender: UIButton) {
movieIndex += 1
updateUI()
}
#IBAction func seenBtn(_ sender: UIButton) {
movieIndex += 1
}
#IBAction func noBtn(_ sender: UIButton) {
movieIndex += 1
}
//Get movie data
func getMovieData(activeGenreLink: String){
//self.posterLoading.startAnimating()
movieIndex = 0
self.downloadGrp.enter()
Alamofire.request(activeGenreLink).responseJSON { response in
//print(response.request) // original URL request
//print(response.response) // HTTP URL response
//print(response.data) // server data
//print(response.result) // result of response serialization
self.movieArray = []
print(self.movieArray)
if let json = response.result.value as? Dictionary<String,AnyObject> {
if let movies = json["results"] as? [AnyObject]{
for movie in movies{
let movieObject: Movie = Movie()
let title = movie["title"] as! String
let releaseDate = movie["release_date"] as! String
let posterPath = movie["poster_path"] as! String
let overView = movie["overview"] as! String
let movieId = movie["id"] as! Int
let genre_ids = movie["genre_ids"] as! [AnyObject]
movieObject.title = title
movieObject.movieRelease = releaseDate
movieObject.posterPath = posterPath
movieObject.overView = overView
movieObject.movieId = movieId
for genre in genre_ids{//Genre ids, fix this
movieObject.movieGenre.append(genre as! Int)
}
Alamofire.request("http://image.tmdb.org/t/p/w1920" + posterPath).responseImage {
response in
//print(response.request)
//print(response.response)
//debugPrint(response.result)
if var image = response.result.value {
image = UIImage(data: response.data!)!
movieObject.poster = image
}
}
self.movieArray.append(movieObject)
}//End of for each movie
}
else{
print("error while making results anyobject")
}
}
else{
print("error while trying to make NSDictionary")}
self.downloadGrp.leave()
}//End of Json request
downloadGrp.notify( queue: .main){
print("all downloads finished")
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3)) {
print(self.movieArray[0].title!)
self.updateUI()
print("updatedUI")
}
}
}//End of getmoviedata
override func prepare(for segue: UIStoryboardSegue,sender: Any?){
// Create a variable that you want to send
let currentMovie = self.movieArray[movieIndex]
if let destinationVC = segue.destination as? DetailsViewController{
destinationVC.currentMovie = currentMovie
}
}
func updateUI(){
//self.posterLoading.stopAnimating()
if uiMoviePoster == nil{
print(uiMovieTitle.debugDescription)
}
else{
print("first time debugID: " + uiMovieTitle.debugDescription)
uiMovieTitle.text = self.movieArray[movieIndex].title
uiMoviePoster.image = self.movieArray[movieIndex].poster
}
}
}
You want to grab a sharedInstance not instantiate from a storyboard
let movieVC = ViewController.sharedInstance()
but I still do not understand why do you need to do it like this

Reload view controller when firebase data is loaded swift

Ok, so my text label should display the name of the user, but the codes runs before the user data is fetched and the label doesn't change when the data is loaded.
First time i print the users name i get nil, but when i print within the firebase call i get the users name. how can i do this so i don't have to change the label with in the firebase call?
var ref: FIRDatabaseReference!
var user: User!
override func viewDidLoad() {
super.viewDidLoad()
ref = FIRDatabase.database().reference()
fetchUser()
self.titleLabel.text = user?.name
// Returns nil
print(user?.name)
}
func fetchUser() {
ref.child("users").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let name = value?["name"] as? String ?? ""
let birthdate = value?["birthdate"] as? String ?? ""
let gender = value?["gender"] as? String ?? ""
self.user = User(name: name, birthdate: birthdate, gender: gender)
// Now i get the users name
print(self.user.name)
}) { (error) in
print(error.localizedDescription)
}
}
If you do not want to access the label from fetchUser, you can use a simple callback.
override func viewDidLoad() {
//viewDidLoad code
fetchUser() {
self.titleLabel.text = user?.name
}
}
func fetchUser(_ completion: #escaping () -> Void) {
ref.child("users").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
//fetchUser code
// Now i get the users name
print(self.user.name)
completion()
}) { (error) in
print(error.localizedDescription)
}
}

Resources