I'm trying to display the users username that stored in firebase. I'm not sure if Im saving the username correctly or if that's even the problem. I can't seem to access the data or display it at all. Any tips? I have a screenshot of the Cloud Firestore attached.
SignupViewController
import UIKit
import FirebaseAuth
import Firebase
import AVKit
import FirebaseFirestore
class SignUpViewController: UIViewController {
var videoPlayer:AVPlayer?
var videoPlayerLayer:AVPlayerLayer?
#IBOutlet weak var firstNameTextField: UITextField!
#IBOutlet weak var lastNameTextField: UITextField!
#IBOutlet weak var emailTextField: UITextField!
#IBOutlet weak var passwordTextField: UITextField!
#IBOutlet weak var BackButton: UIButton!
#IBOutlet weak var UserNameTextField: UITextField!
#IBOutlet weak var signUpButton: UIButton!
#IBOutlet weak var errorLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: view, action: #selector(UIView.endEditing))
view.addGestureRecognizer(tap)
// Do any additional setup after loading the view.
setupElements()
}
func setupElements(){
//hides the error label
errorLabel.alpha = 0
//styles the text and buttons
Utilities.styleTextField(UserNameTextField)
Utilities.styleTextField(firstNameTextField)
Utilities.styleTextField(lastNameTextField)
Utilities.styleTextField(passwordTextField)
Utilities.styleTextField(emailTextField)
Utilities.styleFilledButton(signUpButton)
}
/*
// MARK: - 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.
}
*/
// this function makes sure all data is good
func validateFields() -> String?
{
//make sure fields are filled
if firstNameTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" || lastNameTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" || emailTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" || UserNameTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) == ""
{
return "Please fill all fields"
}
let cleanPassword = passwordTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
if Utilities.isPasswordValid(cleanPassword) == false{
return "Needs 8 charaters, number, and special charater"
}
return nil
}
#IBAction func signUpTapped(_ sender: Any) {
//validate fields
let error = validateFields()
if error != nil{
showError(error!)
}
else{
let firstName = firstNameTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let lastName = lastNameTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let email = emailTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let password = passwordTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let username = UserNameTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
//create user
Auth.auth().createUser(withEmail: email, password: password) { result, err in
if err != nil{
self.showError("Error creating user")
}
else
{
//User is stored sucessfully, store the first and last name
let database = Firestore.firestore()
database.collection("UserInfo").addDocument(data: ["firstname": firstName,"email": email, "lastname": lastName, "username": username, "uid": result!.user.uid]) {(error) in
if error != nil
{
self.showError("First name and last name couldnt be saved.")
}
}
self.transitionToHome()
}
}
}
}
func showError(_ message:String){
errorLabel.text = message
errorLabel.alpha = 1
}
func transitionToHome(){
let homeViewController = storyboard?.instantiateViewController(identifier: Constants.StoryBoard.homeViewController) as?
HomeViewController
view.window?.rootViewController = homeViewController
view.window?.makeKeyAndVisible()
}
override func viewWillAppear(_ animated: Bool) {
//Set up video in background
setUpVideo()
}
func setUpVideo(){
//Get path to resource bundle
let bundlePath = Bundle.main.path(forResource: "IMG_7211 2", ofType: "mov")
guard bundlePath != nil else{
return
}
//create the url from it
let url = URL(fileURLWithPath: bundlePath!)
//Create The video Player item
let item = AVPlayerItem(url: url)
//create the player
videoPlayer = AVPlayer(playerItem: item)
//create the layer
videoPlayerLayer = AVPlayerLayer(player: videoPlayer!)
//adjust the size and frame
videoPlayerLayer?.frame = CGRect(x: -self.view.frame.size.width*1.5, y:0, width: self.view.frame.size.width*4, height: self.view.frame.size.height)
view.layer.insertSublayer(videoPlayerLayer!, at: 0)
//add and play
videoPlayer?.playImmediately(atRate: 0.8)
}
}
CurrentUser.swift
import Foundation
struct CurrentUser{
let uid: String
let name: String
let email: String
let profilepictureURL:String
init(uid: String, dictionary: [String: Any]){
self.uid = uid
self.name = dictionary["username"] as? String ?? ""
self.email = dictionary["email"] as? String ?? ""
self.profilepictureURL = dictionary["profilepictureURL"] as? String ?? ""
}
}
ProfileViewController
import UIKit
import Firebase
import FirebaseAuth
import FirebaseInstallations
import FirebaseFirestore
import FirebaseStorage
import FirebaseDatabase
class ProfileViewController: UIViewController {
#IBOutlet weak var SignOut: UIButton!
#IBOutlet weak var UserName: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
UserNameLabel()
// Do any additional setup after loading the view.
}
func UserNameLabel()
{
if Auth.auth().currentUser != nil
{
guard let uid = Auth.auth().currentUser?.uid else {return}
Database.database().reference().child("users").child(uid).observeSingleEvent(of: .value) { (snapshot) in
guard let dict = snapshot.value as? [String: Any] else {return}
let user = CurrentUser(uid: uid, dictionary: dict)
self.UserName.text = user.username
}
}
}
#IBAction func SignOut(_ sender: Any) {
if Auth.auth().currentUser != nil {
do {
try Auth.auth().signOut()
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Vc")
vc.modalPresentationStyle = .fullScreen
present(vc, animated: true, completion: nil)
}
catch let error as NSError {
print(error.localizedDescription)
}
}
}
}
The problem is you are saving the data to Firebase Firestore, and then trying to access it from firebase real time database. Firebase Realtime Database and Firebase Firestore are 2 completely different backend servers and you are trying to use both at the same time. You need to pick only 1 and stick with it.
You are saving it to firebase firestore by using:
let database = Firestore.firestore()
database.collection("UserInfo").addDocument()
then after you save it to firestore you are trying to access it from real time database, not Firestore which is causing it to return nil:
Database.database().reference().child("users").child(uid).observeSingleEvent(of: .value) { (snapshot) in
So instead what you need to do is get it back from firestore by using this:
Firestore.firestore().collection("s").getDocuments { query, error in
for doc in query!.documents {
// Successfully retrieved data from firestore.
}
}
Note: You could also instead save it to the real time database instead of firestore, but it just depends which one will work better for your app. You need to pick either Firestore or the Database, you can't use both. Here is more info to help you decide which one is better. More Info
Related
I have a model class of all string variables which i need to display in home view controller when user is successfully logged in.
I have created a seperate function to fetch and assign stuffs from user profile api and assign to model class.
and it's called in login function.
but after the segue to next view controller,Model class variables are still blank in it's viewdidload.(consider it as any viewcontroller )
import UIKit
import Alamofire
import SwiftyJSON
class LoginViewController: UIViewController {
var countries = [String]()
var cities = [String]()
let manager = APIManager()
#IBOutlet var txtUsername: UITextField!
#IBOutlet var txtPassword: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func ForgotPasswordAction(_ sender: UIButton) {
}
#IBAction func LoginButtonAction(_ sender: UIButton) {
let username = self.txtUsername.text
let password = self.txtPassword.text
if (username?.isEmpty)! || (password?.isEmpty)!{
self.showAlertWith(title: "Empty Username / Password", message: "Please check if username or password is entered")
}
else {
if Connectivity.isConnectedToInternet{
let parameter:Parameters = ["userItsId":username!,"password":password!]
manager.parsing2(url: BaseURL.login, parameter: parameter) { (JSON, Status) in
if Status { //login success
self.fetchUserProfile()
if JSON["status"] == "true"{
self.performSegue(withIdentifier: "login", sender:self)
}
else{
print("login failed")
}
}
else {
print("error occured,try again later")
}
}
}
else {
self.showAlertWith(title: "No Internet", message: "Please make sure that your device is connected to internet")
}
}
}
#IBAction func NotMemberAction(_ sender: UIButton) {
performSegue(withIdentifier: "register", sender: self)
}
#IBAction func SkipButtonAction(_ sender: UIButton) {
if Connectivity.isConnectedToInternet {
user.isLoggedin = false
performSegue(withIdentifier: "skip", sender: self )
}
else {
self.showAlertWith(title: "Connectivity Error", message: "Please check your connection and make sure that you are connected to the internet")
}
}
func fetchUserProfile(){
let userid = txtUsername.text
let parameter:Parameters = ["userItsId":userid!]
manager.parsing2(url: BaseURL.profile, parameter: parameter) { (JSON, Status) in
if Status{
let dict = JSON.dictionaryObject!
let data = dict["data"] as! [String:String]
let username = data["userName"]
let email = data["email"]
let UserId = data["userId"]
let nationality = data["nationality"]
let country = data["country"]
let mCity = data["mCity"]
let phoneNo = data["phoneNo"]
let dawatTitle = data["dawatTitle"]
let itsId = data["itsId"]
signUser.name = username!
signUser.email = email!
signUser.userID = UserId!
signUser.nationality = nationality!
signUser.country = country!
signUser.mCity = mCity!
signUser.phone = phoneNo!
signUser.dawatName = dawatTitle!
signUser.ItsId = itsId!
}
else {
print("fetching failed")
}
}
}
}
I have created firebase all good with sign in and signup I can
store images as well but can't get images back that are stored in firebase and I want to create user profile to store name and age of user,
success with login, storing image with firebase and have created real time data base child such as image user name and age but cant use in my app, issue in creating user profile and issue with getting image back from firebase storage.
import UIKit
import FirebaseAuth
import Firebase
import FirebaseStorage
class ViewController: UIViewController,UIImagePickerControllerDelegate,UINavigationControllerDelegate {
var ref : DatabaseReference?
#IBOutlet weak var emailTextField: UITextField!
#IBOutlet weak var passwordTextField: UITextField!
#IBOutlet weak var myImage: UIImageView!
#IBOutlet weak var userName: UITextField!
#IBOutlet weak var ageTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// get image from firebase
// imagesFolder.
// let imageBack = Database.database().reference().child("images").child("user")
// self.myImage.image = UIImage(data: data!)
// creating dataBase
ref = Database.database().reference()
if Auth.auth().currentUser != nil {
ref?.child("images").child("username").setValue(["name":"first", "age":28])
}
}
#IBAction func loginButtonPressed(_ sender: UIButton) {
}
// signUp Button pressed
#IBAction func signUpButtonPressed(_ sender: UIButton) {
Auth.auth() .createUser(withEmail: emailTextField!.text!, password: passwordTextField!.text!) {(user,error)
in
if user != nil
{
self.performSegue(withIdentifier: "goToHome", sender: self)
print("SignUp Sucessfull")
}
else {
print("unSucessfull")
// uiAlert
let alert = UIAlertController(title:"wrong Information", message: nil, preferredStyle: .alert)
let okButton = UIAlertAction(title: "ok", style: .default, handler: nil)
alert.addAction(okButton)
self.present(alert,animated: true,completion: nil)
print("login Failed")
}
guard let userName = self.userName.text,!userName.isEmpty else {
print("Email is Empty");return
}
guard let userAge = self.ageTextField.text,!userAge.isEmpty else {
print("Age is required"); return
}
}
// upload image data to firebase
let imagesFolder = Storage.storage().reference().child("images")
if let image = myImage.image {
if let imageData = image.jpegData(compressionQuality: 0.75) {
imagesFolder.child("\(NSUUID().uuidString).jpg").putData(imageData, metadata: nil,completion: { (metadata,error) in
if let error = error {
// alert notification
}
// putData(_:metadata:completion:)
})
}
}
I have crazy comment please ignore that !
I have found the answer myself for future coders this can be a reference !
// user info stored
let userID = Auth.auth().currentUser?.uid
let userData = ["userName": userName,
"userAge ": userAge] as [String? : Any]
let ref = Database.database().reference()
ref.child("users/\(userID ?? "")").setValue(userData)
}
now to retrieve data create a struct and put the code in view did load
func fetchCurrentUserData() {
guard let currentUid = Auth.auth().currentUser?.uid else { return }
let userRef = Database.database().reference().child("users").child(currentUid)
print("userRef: \(userRef)")
userRef.observeSingleEvent(of: .value) { (snapshot) in
guard let dictionary = snapshot.value as? Dictionary<String, AnyObject> else { return }
let uid = snapshot.key
let user = User(uid: uid, dictionary: dictionary)
self.user = user
print(snapshot.key)
}
}
I have a search function in my app to search my firebase db based on a pub name entered. This is working correctly & returning the correct record in my console. However I'm trying to display the relating info to that search query as values on my textfields.
var drinkReference: DatabaseReference!
let databaseRef = Database.database().reference()
#IBOutlet weak var pintImage: UIImageView!
#IBOutlet weak var pubName: UITextField!
#IBOutlet weak var pubLocation: UITextField!
#IBOutlet weak var price: UITextField!
#IBOutlet weak var rating: UITextField!
#IBOutlet weak var comment: UITextField!
#IBOutlet weak var searchText: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(AddDrinkVC .dismissKeyboard))
view.addGestureRecognizer(tap)
drinkReference = Database.database().reference().child("Drinks");
NotificationCenter.default.addObserver(self, selector: #selector(SearchVC.keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(SearchVC.keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
#IBAction func searchIsClicked(_ sender: Any) {
searchT()
}
#IBAction func homeClicked(_ sender: Any) {
homeClicked()
}
func searchT() {
// You must cast pub variable as String.
guard let pub: String = searchText.text else { return }
let databaseRef = Database.database().reference().child("Drinks")
let query = databaseRef.queryOrdered(byChild: "pub").queryStarting(atValue: pub).queryEnding(atValue: "\(String(describing: pub))\\uf8ff")
query.observeSingleEvent(of: .value) { (snapshot) in
guard snapshot.exists() != false else {
let alert = UIAlertController(title: "", message: "This pub hasn't been visited yet. We'll get there.", preferredStyle: UIAlertControllerStyle.alert)
let search = UIAlertAction(title: "Want to add the drink yourself?", style: .default) { [weak self] _ in
self?.uploadClicked()
}
alert.addAction(search)
alert.addAction(UIAlertAction(title: "Close & Search again", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
return }
print(snapshot.value as Any)
DispatchQueue.main.async {
guard let dict = snapshot.value as? [String:Any] else {
print(snapshot)
return
}
let returnedPubName = dict["pub"] as? String
let returnedPubLocation = dict["location"] as? String
let returnedPintPrice = dict["price"] as? String
let returnedPintRating = dict["rating"] as? String
let returnedComment = dict["comment"] as? String
self.pubName.text?.append(returnedPubName!)
self.pubLocation.text?.append(returnedPubLocation!)
self.price.text?.append(returnedPintPrice!)
self.rating.text?.append(returnedPintRating!)
self.comment.text?.append(returnedComment!)
}
}
}
When I try to assign the value of 'pubName' to the textfield of the same name it's receiving nil. Is this due to my 'guard let dict....' code?
Does anyone know how & where I'm going wrong here?
Thanks, E
Create a struct like this:-
struct Names {
var pub: String = ""
var location: String = ""
var price: String = ""
var rating: String = ""
var comment: String = ""
init(data: [String:Any]) {
if let obj = data["pub"] as? String {
self.pub = obj
}
if let obj = data["location"] as? String {
self.location = obj
}
if let obj = data["price"] as? String {
self.price = obj
}
if let obj = data["rating"] as? String {
self.rating = obj
}
if let obj = data["comment"] as? String {
self.comment = obj
}
}
}
And in your controller class create a variable
var names: [Names] = [Names]()
Then in your searchT method do this
guard let dict = snapshot.value as? [String:Any] else {
return
}
self.names.append(Names(data: dict))
print(self.names)
And then use
self.pubName.text = names.first?.pub
And if it not works just try after
DispatchQueue.main.async {
guard let dict = snapshot.value as? [String:Any] else {
return
}
self.names.append(Names(data: dict))
print(self.names)
}
self.pubName.text = names.first?.pub
So through trial and error & with the help of a few folk this is the working code, as a note I'm not sure if this is the most efficient way or anything, but it works so c'est la vie.
func searchT() {
// You must cast pub variable as String.
guard let pub: String = searchText.text else { return }
let databaseRef = Database.database().reference().child("Drinks")
let query = databaseRef.queryOrdered(byChild: "pub").queryStarting(atValue: pub).queryEnding(atValue: "\(String(describing: pub))\\uf8ff")
query.observeSingleEvent(of: .value) { (snapshot) in
guard snapshot.exists() != false else {
let alert = UIAlertController(title: "", message: "This pub hasn't been visited yet. We'll get there.", preferredStyle: UIAlertControllerStyle.alert)
let search = UIAlertAction(title: "Want to add the drink yourself?", style: .default) { [weak self] _ in
self?.uploadClicked()
}
alert.addAction(search)
alert.addAction(UIAlertAction(title: "Close & Search again", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
return }
print(snapshot.value as Any)
DispatchQueue.main.async {
// DATA WAS FOUND
for user_child in (snapshot.children) {
let user_snap = user_child as! DataSnapshot
let dict = user_snap.value as! [String: String?]
// DEFINE VARIABLES FOR LABELS
let returnedPubName = dict["pub"] as? String
let returnedPubLocation = dict["location"] as? String
let returnedPintPrice = dict["price"] as? String
let returnedPintRating = dict["rating"] as? String
let returnedComment = dict["comment"] as? String
self.pubName.text?.append(returnedPubName!)
self.pubLocation.text?.append(returnedPubLocation!)
self.price.text?.append(returnedPintPrice!)
self.rating.text?.append(returnedPintRating!)
self.comment.text?.append(returnedComment!)
}
}
}
}
Pay particular attention to the code following the comment //DATA WAS FOUND, that's accessing the values of the children data records.
My app will show teacher shedule, in the first controller teacher need to choose the right pair (first, second, third) and then write nessesary data (like what the subject name, room, number of group) now in my code if i write data in first pair and save, it will show to me, but if i write data in second pair and choose save button, the previous data in first pair is lost, so i have a problem with losing data, this is code for controller where i choose pair:
class ChoosePair: UIViewController {
enum choosePair: Int{
case FirstPair = 21
case SecondPair = 22
case ThirdPair = 23
case FourPair = 24
}
#IBAction func PairButtons(_ sender: UIButton){
guard let day = choosePair.init(rawValue: sender.tag) else { return }
switch day {
case .FirstPair:
let FirstPair = self
UserDefaults.standard.set(21, forKey: "pairId")
performSegue(withIdentifier: "SheduleTeacher", sender: self)
break
case .SecondPair:
let SecondPair = self
UserDefaults.standard.set(22, forKey: "pairId")
performSegue(withIdentifier: "SheduleTeacher", sender: SecondPair)
break
case .ThirdPair:
let ThirdPair = self
UserDefaults.standard.set(23, forKey: "pairId")
performSegue(withIdentifier: "SheduleTeacher", sender: ThirdPair)
break
case .FourPair:
let FourPair = self
UserDefaults.standard.set(24, forKey: "pairId")
performSegue(withIdentifier: "SheduleTeacher", sender: FourPair)
break
}
}
}
And the code for second controller(where i write data and save it in CoreData)
class SheduleTeacher: UIViewController {
var pairId = UserDefaults.standard.integer(forKey: "pairId")
#IBOutlet weak var subjectLabel: UILabel!
#IBOutlet weak var roomLabel: UILabel!
#IBOutlet weak var timeLabel: UILabel!
#IBOutlet weak var emailLabel: UILabel!
#IBOutlet weak var groupLabel: UILabel!
#IBOutlet weak var captainLabel: UILabel!
#IBOutlet weak var subjectField: UITextField!
#IBOutlet weak var roomField: UITextField!
#IBOutlet weak var timeField: UITextField!
#IBOutlet weak var emailField: UITextField!
#IBOutlet weak var groupField: UITextField!
#IBOutlet weak var captainField: UITextField!
let appDelegate = UIApplication.shared.delegate as! AppDelegate
override func viewDidLoad() {
super.viewDidLoad()
let context = appDelegate.persistentContainer.viewContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Users")
request.returnsObjectsAsFaults = false
do {
let result = try context.fetch(request)
for data in result as! [NSManagedObject] {
subjectField.text = data.value(forKey: "subject\(pairId)") as? String
roomField.text = data.value(forKey: "room\(pairId)") as? String
timeField.text = data.value(forKey: "time\(pairId)") as? String
emailField.text = data.value(forKey: "emailGroup\(pairId)") as? String
groupField.text = data.value(forKey: "group\(pairId)") as? String
captainField.text = data.value(forKey: "captain\(pairId)") as? String
}
} catch {
print("Failed")
}
subjectLabel.layer.borderWidth = 1.0
roomLabel.layer.borderWidth = 1.0
timeLabel.layer.borderWidth = 1.0
emailLabel.layer.borderWidth = 1.0
groupLabel.layer.borderWidth = 1.0
captainLabel.layer.borderWidth = 1.0
}
#IBAction func saveButton(_ sender: UIButton) {
let context = appDelegate.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "Users", in: context)
let newUser = NSManagedObject(entity: entity!, insertInto: context)
newUser.setValue(self.subjectField!.text, forKey: "subject\(pairId)")
newUser.setValue(self.roomField!.text, forKey: "room\(pairId)")
newUser.setValue(self.timeField!.text, forKey: "time\(pairId)")
newUser.setValue(self.emailField!.text, forKey: "emailGroup\(pairId)")
newUser.setValue(self.groupField!.text, forKey: "group\(pairId)")
newUser.setValue(self.captainField!.text, forKey: "captain\(pairId)")
do {
try context.save()
} catch {
print("Failed saving")
}
}
}
And photo what I now have in CoreData
photo
Predicates are used to limit the fetch to only get certain entities.
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Users")
request.returnsObjectsAsFaults = false
Does not set a predicate so it always returns all the Users entities there are. The following has you iterate over every entity, that's why there is a for-loop.
for data in result as! [NSManagedObject] {
Here is an example how you can use a predicate to only get entities with entity.pairId == pairId.
override func viewDidLoad() {
super.viewDidLoad()
let context = AppDelegate.shared.persistentContainer.viewContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Users")
request.returnsObjectsAsFaults = false
request.predicate = NSPredicate(format: "pairId = %#", argumentArray: [pairId])
do {
let results = try context.fetch(request)
guard results.count == 1 else {
fatalError("Bad count: \(results.count)")
}
guard let data = results.first! as? NSManagedObject else {
fatalError("No NSManagedObject")
}
context.performAndWait {
subjectField.text = data.value(forKey: "subject\(pairId)") as? String
roomField.text = data.value(forKey: "room\(pairId)") as? String
timeField.text = data.value(forKey: "time\(pairId)") as? String
emailField.text = data.value(forKey: "emailGroup\(pairId)") as? String
groupField.text = data.value(forKey: "group\(pairId)") as? String
captainField.text = data.value(forKey: "captain\(pairId)") as? String
}
} catch {
print("Failed")
}
// ...
}
The entity must of course have said attribute pairId for the predicate to work.
You would do something like this in the save() method too. There you could update the retrieved Users entity/create a new one if one does not exist.
One alternative is to delete the old one if one so exists and always create a new one on save.
The other alternative to "create a new one if one does not exist" is to pre-create for all four buttons and then fetch the entities on save and update them there. You would however have to check on startup too if they exist already else you would create on every startup four.
In your saveButton func you create a new Users instance every time which mean data from pair two will not be written together with data for pair one but as a separate record. You need to update the existing object, the one you fetched in viewDidLoad, instead. (I very much doubt any data is lost)
That being said I think you need to rethink your design, having duplicate (or more) fields in the same entity looks like an awkward design that will be hard to maintain. A better solution would be to only have one group attribute, one subject attribute and so on and complement it with an attribute that holds the pairId.
Also, why is the entity name Users, it seems to hold no kind of user id?
So i have a firebase structure like the pic below
Now i want to retrieve that image file that i've uploaded. to decode the base64String and show it. Every user can make a post and the information that will be sended to firebase has a description etc. and also have an image. now i tried to retrieve it whit this codes but nothing did work.
var REF_LIST = Firebase(url: "\(URL_BASE)/listItems")
REF_LIST.observeEventType(FEventType.Value, withBlock: { snapshot in
let image = snapshot.value.objectForKey("images") as! String
but this already gave me a nil error on that line, so i couldn't even decode. i think i understand why it's giving me a nil error, there is no images in listItems on firebase, you first have the unique ID and then the specs with images in. now i don't now how i can retrieve that information from that unique ID?
UPDATE:
the tableViewController what will receive the data from firebase:
import UIKit
import FBSDKLoginKit
import Alamofire
import Firebase
class ListVC: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
var lists = [List]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
dispatch_async(backgroundQueue, {
self.initObservers()
})
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.tableView.reloadData()
}
func initObservers() {
LoadingOverlay.shared.showOverlay(self.view)
DataService.ds.REF_LISTS.observeEventType(.Value, withBlock: { snapshot in
print(snapshot.value)
self.lists = []
if let snapshots = snapshot.children.allObjects as? [FDataSnapshot] {
for snap in snapshots {
print("SNAP: \(snap)")
if let listDict = snap.value as? Dictionary<String, AnyObject> {
let key = snap.key
let list = List(listKey: key, dictionary: listDict)
self.lists.insert(list, atIndex:0)
}
}
}
self.tableView.reloadData()
LoadingOverlay.shared.hideOverlayView()
})
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return lists.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCellWithIdentifier("ListCell") as? ListCell {
let list = self.lists[indexPath.row]
cell.request?.cancel()
cell.configureCell(list)
return cell
} else {
return ListCell()
}
}
}
the addController which post the data to firebase:
import UIKit
import Firebase
import Alamofire
import FBSDKCoreKit
class AddVC: UIViewController, UITextFieldDelegate, UITextViewDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
#IBOutlet weak var addTitle: UITextField!
#IBOutlet weak var addDescription: UITextView!
#IBOutlet weak var addLocation: UITextField!
#IBOutlet weak var placeholderLbl: UILabel!
#IBOutlet weak var freeSwitch: UISwitch!
#IBOutlet weak var tradeSwitch: UISwitch!
#IBOutlet weak var imageSelectorImg: UIImageView!
#IBOutlet weak var overlayView: UIView!
var currentUsername = ""
var imageSelected = false
var imagePicker: UIImagePickerController!
var base64String: NSString = ""
override func viewDidLoad() {
super.viewDidLoad()
addTitle.delegate = self
addDescription.delegate = self
addLocation.delegate = self
imagePicker = UIImagePickerController()
imagePicker.delegate = self
getCurrentUser()
hideKeyboardWhenTappedAround()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
addTitle.text = ""
addDescription.text = ""
addLocation.text = ""
freeSwitch.setOn(false, animated: false)
tradeSwitch.setOn(false, animated: false)
placeholderLbl.hidden = false
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func getCurrentUser() {
DataService.ds.REF_USER_CURRENT.observeEventType(FEventType.Value, withBlock: { snapshot in
let currentUser = snapshot.value.objectForKey("username") as! String
print("Username: \(currentUser)")
self.currentUsername = currentUser }, withCancelBlock: { error in
print(error.description)
})
}
func imagePickerController(picker: UIImagePickerController, didFinishPickingImage image: UIImage, editingInfo: [String : AnyObject]?) {
imagePicker.dismissViewControllerAnimated(true, completion: nil)
imageSelectorImg.image = image
dispatch_async(backgroundQueue, {
let uploadImage = image
let imageData = UIImageJPEGRepresentation(uploadImage, 0.5)
self.base64String = imageData!.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.Encoding64CharacterLineLength)
})
imageSelected = true
}
#IBAction func selectImage(sender: UITapGestureRecognizer) {
presentViewController(imagePicker, animated: true, completion: nil)
}
func postToFirebase() {
// LoadingOverlay.shared.showOverlay(self.overlayView)
var post: Dictionary<String, AnyObject> = ["username": self.currentUsername, "description": self.addDescription.text!, "title": self.addTitle.text!, "location": self.addLocation.text!, "images": self.base64String]
if self.freeSwitch.on && self.tradeSwitch.on {
post["tradeOption"] = "Gratis/Te ruil"
} else if self.freeSwitch.on {
post["tradeOption"] = "Gratis"
} else if self.tradeSwitch.on {
post["tradeOption"] = "Te ruil"
}
let firebasePost = DataService.ds.REF_LISTS.childByAutoId()
firebasePost.setValue(post)
}
#IBAction func postListItem(sender: AnyObject) {
if let addTitle = addTitle.text where addTitle != "", let addDescription = addDescription.text where addDescription != "", let addLocation = addLocation.text where addLocation != "" {
dispatch_async(backgroundQueue, {
self.postToFirebase()
dispatch_async(dispatch_get_main_queue(), { () -> Void in
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let listVC = storyboard.instantiateViewControllerWithIdentifier("TBC") as! UITabBarController
listVC.selectedIndex = 1
self.presentViewController(listVC, animated: false, completion: nil)
})
})
}
}
func textViewDidBeginEditing(textView: UITextView) {
placeholderLbl.hidden = true
}
func textViewDidEndEditing(textView: UITextView) {
if textView.text == "" {
placeholderLbl.hidden = false
}
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
}
and the swift file to configure the cell:
import UIKit
import Alamofire
import Firebase
class ListCell: UITableViewCell {
#IBOutlet weak var listImg: UIImageView!
#IBOutlet weak var listTitle: UILabel!
#IBOutlet weak var listTradeOption: UILabel!
#IBOutlet weak var listLocation: UILabel!
#IBOutlet weak var headImg: UIImageView!
var list: List!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
func retrieveImages() {
DataService.ds.REF_LISTS.observeEventType(FEventType.Value, withBlock: { snapshot in
if let snapshots = snapshot.children.allObjects as? [FDataSnapshot] {
for snap in snapshots {
let image = snap.value.objectForKey("images") as! String
let decodedData = NSData(base64EncodedString: image, options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters)
let decodedImage = UIImage(data: decodedData!)
self.headImg.image = decodedImage
}
}
})
}
func configureCell(list: List) {
self.list = list
self.listTitle.text = list.listTitle
self.listTradeOption.text = list.listTradeOption
self.listLocation.text = list.listLocation
retrieveImages()
}
}
also the list Model file:
import Foundation
import Firebase
class List {
private var _listTitle: String!
private var _listDescription: String!
private var _listTradeOption: String!
private var _listLocation: String!
private var _listImageURL: String?
private var _listKey: String!
private var _listRef: Firebase!
var listTitle: String? {
return _listTitle
}
var listDescription: String? {
return _listDescription
}
var listTradeOption: String? {
return _listTradeOption
}
var listLocation: String? {
return _listLocation
}
var listKey: String {
return _listKey
}
var listImageURL: String? {
return _listImageURL
}
init(title: String, description: String, tradeOption: String, location: String, listImageURL: String?) {
self._listTitle = title
self._listDescription = description
self._listTradeOption = tradeOption
self._listLocation = location
self._listImageURL = listImageURL
}
init(listKey: String, dictionary: Dictionary<String, AnyObject>) {
self._listKey = listKey
if let title = dictionary ["title"] as? String {
self._listTitle = title
}
if let desc = dictionary ["description"] as? String {
self._listDescription = desc
}
if let trade = dictionary ["tradeOption"] as? String {
self._listTradeOption = trade
}
if let loc = dictionary ["location"] as? String {
self._listLocation = loc
}
if let imgUrl = dictionary["images"] as? String {
self._listImageURL = imgUrl
}
self._listRef = DataService.ds.REF_LISTS.childByAppendingPath(self._listKey)
}
}
i've got also a DataServicefile, where i create a user by unique ID with this code:
var REF_USER_CURRENT: Firebase {
let uid = NSUserDefaults.standardUserDefaults().valueForKey(KEY_UID) as! String
let user = Firebase(url: "\(REF_BASE)").childByAppendingPath("users").childByAppendingPath(uid)
return user!
}
func createFirebaseUser(uid: String, user: Dictionary<String, String>) {
REF_USERS.childByAppendingPath(uid).setValue(user)
}
i know it's a lot but maybe the best way to help :)
Try editing this in List Cell
var imageURL = String()
func retrieveImages() {
let decodedData = NSData(base64EncodedString: imageURL, options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters)
let decodedImage = UIImage(data: decodedData!)
self.headImg.image = decodedImage
}
func configureCell(list: List) {
self.list = list
self.listTitle.text = list.listTitle
self.listTradeOption.text = list.listTradeOption
self.listLocation.text = list.listLocation
self.imageURL = list.listImageURL //you already had the image url for that specific cell
retrieveImages()
}
Storing and accessing images using base64String in firebase is not an
efficient way, instead of that we can use FirebaseStorage (Google cloud storage
bucket) for uploading images to Firebase and it will provide us
download URL for a particular image. We can store that URL into our database simply in a string format and access it whenever we
required and then download the corresponding image from that URL by
using SDWebImage.
Refer below link for integrating FirebaseStorage into your project: https://firebase.google.com/docs/storage/ios/upload-files