[swift]Enter different VIEWs according to different values - ios

This is my firebase database path ->ID/uid/Profile/Safety-Check
The way I want to achieve is this
key: Safety-Check,
value: "admin" , go to ViewController "adminVC",
Value: "ON" , go to ViewController "MainTabBarController",
Without this key: Safety-Check , go to ViewController "SignUpViewControllerID"
Below is my current code but it doesn't work,No KEY "Safety-Check" program crashes, my KEY "Safety-Check" is generated in next ViewController "SignUpViewControllerID", hope someone can give me some advice, thanks
Database.database().reference().child("ID/\(self.uid)/Profile/Safety-Check").observeSingleEvent(of: .value, with: {
(snapshot) in
if error != nil {
let viewController = self.storyboard?.instantiateViewController(withIdentifier: "SignUpViewControllerID")
GetWindow()?.rootViewController = viewController
GetWindow()?.makeKeyAndVisible()
}else{
switch snapshot.value as! String {
// If our user is admin...
case "admin":
// ...redirect to the admin page
let viewController = self.storyboard?.instantiateViewController(withIdentifier: "admin")
GetWindow()?.rootViewController = viewController
GetWindow()?.makeKeyAndVisible()
// If out user is a regular user...
case "ON":
// ...redirect to the user page
let viewController = self.storyboard?.instantiateViewController(withIdentifier: "MainTabBarController")
GetWindow()?.rootViewController = viewController
GetWindow()?.makeKeyAndVisible()
// If the type wasn't found...
default:
print("Error: Couldn't find type for user ")

Related

Firestore into different ViewController

I'm building a student-tutor app using swift + firebase (auth and firestore).
I have a wrapper widget that checks if a user is logged in. If they're not, I direct them to an authentication screen (login/registration). If they are logged in, I then want to check if they are a student or a tutor.
In other words, in my wrapper, I need a way to retrieve user data from firestore and check their role and then direct them to the appropriate screen. I can't figure out how to do it. Please help. This is my wrapper class
let UID = Auth.auth().currentUser?.uid
let db = Firestore.firestore()
db.collection("Users").document(UID!).getDocument { snapshot, error in
if error == nil {
let viewController = self.storyboard?.instantiateViewController(withIdentifier: "SignUpViewControllerID")
GetWindow()?.rootViewController = viewController
GetWindow()?.makeKeyAndVisible()
}else{
if let viewController = self.storyboard?.instantiateViewController(withIdentifier: "MainTabBarController") {
GetWindow()?.rootViewController = viewController
GetWindow()?.makeKeyAndVisible()
}
let db = Firestore.firestore()
db.collection("Users").document(UID!).getDocument { snapshot, error in
if error != nil {
for document in snapshot.documents {
let UID = document.documentID
}
}
}
You can access the uid in different viewController via two ways.
i) Make a function and pass the uid, call the function in another viewController.
ii) let userId = Auth.auth().currentUser?.uid , by this term( You have to import the FireBaseAuth).

Getting the value of a constant in another controller is always returning nil using Swift

In an attempt to get the value of historyRef in another VC, it is returning nil. I have tried different solutions (including this one I am using) and I can't seem to get the actual value of the historyRef variable as declared in viewDidLoad().
The Firebase database has a node "history", which has a key (childByAutoId()) in MainVC. I am trying to access that key in SecondVC.
In the MainVC is a constant:
var historyRef : FIRDatabaseReference!
var ref : FIRDatabaseReference!
Also instance is declared:
private static let _instance = MainVC()
static var instance: MainVC {
return _instance
}
viewDidLoad() :
historyRef = ref.child("history").childByAutoId()
SecondVC
class SecondVC: UIViewController {
var mainVC : MainVC? = nil // hold reference
override func viewDidLoad() {
super.viewDidLoad()
mainVC = MainVC() // create MainVC instance
getUserHistoryIds()
}
func getUserHistoryIds() {
let historyKey = mainVC?.historyRef
print("HistoryKey: \(String(describing: historyKey))")
}
}
printout:
HistoryKey: nil
My database:
my-app
- history
+ LSciQTJwR0VqwaAfKVz
Edit
Rather than get from another controller, I got from Firebase.
I was able to get the value of the childAutoById but it lists all of them and not just the current one:
let historyRef = ref.child("history")
historyRef.observe(.value) { (snapshot) in
if snapshot.exists() {
for history in snapshot.children {
let snap = history as! FIRDataSnapshot
let _ = snap.value as! [String: Any] // dict
let historyKey = snap.key
print("History Key: \(historyKey)")
}
} else {
print("There are none")
}
}
You are initialising MainVC::historyRef in the viewDidLoad method, but just instantiating MainVC from SecondVC will not cause the MainVC to be loaded or displayed.
You can use mainVC = MainVC.instance, but you are dependent on the MainVC instance having been previously loaded and not otherwise discarded.
I'd be looking to extract any model usages away from being related to a VC, and passed to the VC as part of a segue when they are required.
Move the history ref from the viewDidLoad to init() when you initialize the MainVC. This way the method will run as soon as you instantiate the view controller. Also make sure that this is not an async call, or add a completion callback to know when it is done, as you mention you are using firebase.
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
historyRef = ref.child("history").childByAutoId()
}
As you instantiate the MainVC but never load it so that historyRef will never run.
Also having a view controller as a static is not recommended. I would rather pass the variable down through initializers or through view models.
As you can see from my edit, I wasn't able to get what I needed from passing between controllers, so I just got it from firebase database:
let historyRef = ref.child("history")
historyRef.observe(.value) { (snapshot) in
if snapshot.exists() {
for history in snapshot.children {
let snap = history as! FIRDataSnapshot
let _ = snap.value as! [String: Any] // dict
let historyKey = snap.key
print("History Key: \(historyKey)")
}
} else {
print("There are none")
}
}
This gets all the random keys rather than the current one, this I can work with.

Sync data between two viewcontrollers to avoid creating same observer again

In my main VC I look for changes in my FB database like this:
ref.child("posts").observe(.childChanged, with: { (snapshot) in
.......
})
From this VC I can enter VC2 which is set to be "present modally" in my segue.
Now I wonder if I can pass live FB data from VC1 to VC2? I know that I can use a segue.identifier and pass data when I segue to the next VC but this is one time send only. Or should I setup a delegate to fetch data from vc1 to vc2?
So is there any way I can send data from VC1 to VC2 once a node has been updated or must I setup a new .observe() function in VC2?
First I would like to remind you about the singleton design patter :
In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system.
So the first thing you need to do is to create a call that contains as a parameters the data you get from firebase, I have did the following to get the following in order to get the user data when he logged in into my app and then use these data in every part of my application (I don't have any intention to pass the data between VC this is absolutely the wrong approach )
my user class is like this :
import Foundation
class User {
static let sharedInstance = User()
var uid: String!
var username: String!
var firstname: String!
var lastname: String!
var profilePictureData: Data!
var email: String!
}
after that I have created another class FirebaseUserManager (you can do this in your view controller but it's always an appreciated idea to separate your view your controller and your model in order to make any future update easy for you or for other developer )
So my firebaseUserManager class contains something like this
import Foundation
import Firebase
import FirebaseStorage
protocol FirebaseSignInUserManagerDelegate: class {
func signInSuccessForUser(_ user: FIRUser)
func signInUserFailedWithError(_ description: String)
}
class FirebaseUserManager {
weak var firebaseSignInUserManagerDelegate: FirebaseSignInUserManagerDelegate!
func signInWith(_ mail: String, password: String) {
FIRAuth.auth()?.signIn(withEmail: mail, password: password) { (user, error) in
if let error = error {
self.firebaseSignInUserManagerDelegate.signInUserFailedWithError(error.localizedDescription)
return
}
self.fechProfileInformation(user!)
}
}
func fechProfileInformation(_ user: FIRUser) {
var ref: FIRDatabaseReference!
ref = FIRDatabase.database().reference()
let currentUid = user.uid
ref.child("users").queryOrderedByKey().queryEqual(toValue: currentUid).observeSingleEvent(of: .value, with: { snapshot in
if snapshot.exists() {
let dict = snapshot.value! as! NSDictionary
let currentUserData = dict[currentUid] as! NSDictionary
let singletonUser = User.sharedInstance
singletonUser.uid = currentUid
singletonUser.email = currentUserData["email"] as! String
singletonUser.firstname = currentUserData["firstname"] as! String
singletonUser.lastname = currentUserData["lastname"] as! String
singletonUser.username = currentUserData["username"] as! String
let storage = FIRStorage.storage()
let storageref = storage.reference(forURL: "gs://versus-a107c.appspot.com")
let imageref = storageref.child("images")
let userid : String = (user.uid)
let spaceref = imageref.child("\(userid).jpg")
spaceref.data(withMaxSize: 1 * 1024 * 1024) { data, error in
if let error = error {
// Uh-oh, an error occurred!
print(error.localizedDescription)
} else {
singletonUser.profilePictureData = data!
print(user)
self.firebaseSignInUserManagerDelegate.signInSuccessForUser(user)
}
}
}
})
}
}
so basically this class contains some protocols that we would implements and two functions that manager the firebase signIn and fechProfileInformation , that will get the user information
than in my login View controller I did the following :
1 implement the protocol
class LoginViewController: UIViewController, FirebaseSignInUserManagerDelegate
2 in the login button I did the following
#IBAction func loginAction(_ sender: UIButton) {
guard let email = emailTextField.text, let password = passwordTextField.text else { return }
let firebaseUserManager = FirebaseUserManager()
firebaseUserManager.firebaseSignInUserManagerDelegate = self
firebaseUserManager.signInWith(email, password: password)
}
3 implement the protocol method :
func signInSuccessForUser(_ user: FIRUser) {
// Do something example navigate to the Main Menu
}
func signInUserFailedWithError(_ description: String) {
// Do something : example alert the user
}
So right now when the user click on the sign in button there is an object created which contains the user data save on firebase database
now comes the funny part (the answer of your question : how to get the user data in every where in the app)
in every part of my app I could make
print(User.sharedInstance.uid) or print(User.sharedInstance. username)
and I get the value that I want to.
PS : In order to use the singleton appropriately you need to make sure that you call an object when it's instantiated.

After retrieving data from Firebase, how to send retrieved data from one view controller to another view controller?

This is my table created in Firebase here. I have a search button. The button action will be at first it will fetch data from firebase and then it will send it to another view controller and will show it in a table view. but main problem is that before fetching all data from Firebase
self.performSegueWithIdentifier("SearchResultPage", sender: self )
triggers and my next view controller shows a empty table view. Here was my effort here.
From this post here I think that my code is not placed well.
Write this line of code in your dataTransfer method in if block after complete for loop
self.performSegueWithIdentifier("SearchResultPage", sender: self )
Try sending the search string as the sender when you are performing the segue, for example:
self.performSegueWithIdentifier("SearchResultPage", sender: "searchString")
Then, in your prepare for segue get the destination view controller and send over the string to the new view controller. something like:
destinationViewController.searchString = sender as! String
When the segue is completed and you have come to the new view controller, you should now have a searchString value that is set, use this value to perform your query and get the relevant data to show in the new table view.
Please note that this is just one solution of many, there are other ways to achieve this.
The reason why you are not able to send data is because, you are trying to send data even before the data could actually be fetched.
Try the following where you are pushing to next view controller only after getting the data
func dataTransfer() {
let BASE_URL_HotelLocation = "https://**************.firebaseio.com/Niloy"
let ref = FIRDatabase.database().referenceFromURL(BASE_URL_HotelLocation)
ref.queryOrderedByChild("location").queryStartingAtValue("uttara").observeEventType(.Value, withBlock: { snapshot in
if let result = snapshot.children.allObjects as? [FIRDataSnapshot] {
for child in result {
let downloadURL = child.value!["image"] as! String;
self.storage.referenceForURL(downloadURL).dataWithMaxSize(25 * 1024 * 1024, completion: { (data, error) -> Void in
let downloadImage = UIImage(data: data!)
let h = Hotel()
h.name = child.value!["name"] as! String
print("Object \(count) : ",h.name)
h.deal = child.value!["deal"] as! String
h.description = child.value!["description"] as! String
h.distance = child.value!["distance"] as! String
h.latestBooking = child.value!["latestBooking"] as! String
h.location = child.value!["location"] as! String
h.image = downloadImage!
self.HotelObjectArray.append(h)
})
}
let storyboard = UIStoryboard(name:"yourStoryboardName", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("SearchResultPageIdentifier") as! SearchResultPage
self.navigationController.pushViewController(vc, animated: true)
}
else {
print("no results")
}
})
}

How can I prevent TableView cells from duplicating? Swift

So I am building a notes app and have tried everything but can not figure this issue out. I have 3 UIViewController's. When you click a button on the first UIViewController, it shoots you over to the third one which consist of a UITextView, UITextField and a Static UILabel which gets updated with some information. When you fill out these fields and tap the back button it brings you to the second view controller which is a table that gets updated with this information.
The issue is: when I tap the UITableViewCell it loads the information back to the third view controller so the user can edit his notes but when I come back to the UITableView it creates a brand new cell instead of just updating the old one.
If I could just update my array with the same object I sent back to be edited by the user I think this issue would be solved but I have no idea how to do this. Thanks for the help!
VC2 - this is how I am sending my data from the tableView to the textView Back
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let nextView = segue.destinationViewController as! TextViewController
guard let indexPath = self.tableView.indexPathForSelectedRow else {
print("Didnt work")
return
}
let dataToSendBackToBeEdited = textViewObjectData[indexPath.row]
print(dataToSendBackToBeEdited.dreamText)
print(dataToSendBackToBeEdited.dreamTitle)
print(dataToSendBackToBeEdited.dreamType)
print(dataToSendBackToBeEdited.description)
print(dataToSendBackToBeEdited)
nextView.objectSentFromTableViewCell = dataToSendBackToBeEdited
}
This is how I am saving the information the the user taps back to go to the tableView
func determineSave() {
guard var savedDreamArray = NSKeyedUnarchiver.unarchiveObjectWithFile(TextViewController.pathToArchieve.ArchiveURL.path!) as? [Dream] else {
//First dream object saved
let dreamObject = Dream(dreamTitle: titleForDream.text!, dreamType: typeOfDreamLabel.text!, dreamText: textView.text, currentDate: NSDate())
dreamArray.append(dreamObject)
NSKeyedArchiver.archiveRootObject(dreamArray, toFile: pathToArchieve.ArchiveURL.path!)
return
}
//print(savedDreamArray.count)
//print(savedDreamArray.first!.dreamTitle)
let dreamObject = Dream(dreamTitle: titleForDream.text!, dreamType: typeOfDreamLabel.text!, dreamText: textView.text!, currentDate: NSDate())
savedDreamArray.append(dreamObject)
NSKeyedArchiver.archiveRootObject(savedDreamArray, toFile: pathToArchieve.ArchiveURL.path!)
}
I was having this issue as well. Came in here, and got the answer. The array!
I was appending the array as well, which apparently was causing duplicate cells to appear.
I just reset my array back to empty before I retrieved the data again and reloaded the table.
I'm using Firebase so my code looks like this:
DataService.instance.dateProfileRef.observeEventType(FIRDataEventType.Value) { (snapshot: FIRDataSnapshot)
in
//have to clear the array here first before reloading the data. otherwise you get duplicates
self.posts = [] //added this line
if let snapshot = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshot {
let snapUserID = (snap.childSnapshotForPath("userID").value!)
if snapUserID as? String == USER_ID {
if let profileDict = snap.value as? Dictionary<String, AnyObject> {
let key = snap.key
let post = Post(postKey: key, postData: profileDict)
self.posts.append(post)
You do a .append() on your array, this will add a new cell.
You must find the index of the object you want to update and then assign it:
array[index] = newObject

Resources