How to add data to specific use reference in firebase database? - ios

My current iOS requires the user to sign up and log in and once the user logs in there data is presented in the database by their user.uid and their email address they signed up with. Once the user has logged in, I want to the user to be able to store data at their specific user.uid node. For example, if "User A" is logged in and they enter some type of data, I want the data to be saved just under that user (so each user should contain their own set of data and should not be to access or modify any other users data besides there own). So my question is, what would be the best way to keep track of the logged in user or access the logged in user to store future references in later view controllers outside of just the log in controller? It's a food delivery app, so once a user is logged in, I wanted all future entered data to be saved under that user in the database.
Code for login controller:
import UIKit
import FirebaseDatabase
import FirebaseAuth
class LogInController: UIViewController {
var ref: DatabaseReference!
#IBOutlet weak var emailField: UITextField!
#IBOutlet weak var passwordField: UITextField!
func placeholders() {
emailField.placeholder = "Enter Email"
passwordField.placeholder = "Enter Password"
}
#IBAction func loginButton(_ sender: AnyObject) {
Auth.auth().signIn(withEmail: emailField.text!, password: passwordField.text!, completion: { (user, error) in
let userID: String = (user?.uid)!
let userEmail: String = self.emailField.text!
self.ref.child("Users/\(userID)").setValue(userEmail)
if error != nil {
print(error?.localizedDescription as Any)
}
else {
print("User logged in with UserID of: " + (user?.uid)!)
}
})
performSegue(withIdentifier: "signedIn", sender: self)
}
#IBAction func signoutButton(_ sender: Any) {
print("User has logged out...")
try! Auth.auth().signOut()
}
#IBAction func registerButton(_ sender: AnyObject) {
Auth.auth().createUser(withEmail: emailField.text!, password: passwordField.text!, completion: { (user, error) in
if error != nil {
print(error?.localizedDescription as Any)
return
}
print("User created with UserId of: " + (user?.uid)!)
})
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let flavorsVC = segue.destination as? FlavorsController {
flavorsVC.ref = ref
let userEmail = emailField.text
flavorsVC.email = userEmail!
}
}
override func viewDidLoad() {
super.viewDidLoad()
placeholders()
ref = Database.database().reference()
}
So again, once the user has logged in, I want all data entered to be saved just under that logged in user on following view controllers. For example in the next view controller I have a variable bookieAmount, how could I modify my code to where each logged in user has a separate node where their individual bookieAmount can be saved to? So far when I try to implement such functionality it just replaces the previous reference and doesn't create a new for that specific user like I want it to.
Code for next view controller:
class FlavorsController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var bookieFlavors = ["Chocolate Chip", "Sugar w/o icing", "Sugar w/ icing", "Peanut Butter", "Honey", "Shortbread", "Ginger", "Double Chocolate", "Macadamie Nut", "Oatmeal Raisin", "Snickerdoodle"]
var amount = [Int]()
var bookieTotal = Int()
var ref: DatabaseReference!
var flavorRef: DatabaseReference!
var email = String()
override func viewDidLoad() {
super.viewDidLoad()
for _ in self.bookieFlavors {
self.amount.append(0)
}
flavorTable.delegate = self
flavorTable.dataSource = self
//database references
ref = Database.database().reference()
flavorRef = Database.database().reference()
}
func emptyAmount(_ sender: UIButton) {
print("Button Held, Amount Emptied")
self.amount[sender.tag] = self.amount[sender.tag] - (self.amount[sender.tag] + 1)
let cell = self.flavorTable.cellForRow(at: IndexPath(row: sender.tag, section: 0)) as? FlavorTableCell
cell?.bookieAmount.text = "= \(self.amount[sender.tag])"
}
#IBAction func bookieButton(_ sender: UIButton) {
self.amount[sender.tag] = self.amount[sender.tag] + 1
let cell = self.flavorTable.cellForRow(at: IndexPath(row: sender.tag, section: 0)) as? FlavorTableCell
cell?.bookieAmount.text = "= \(self.amount[sender.tag])"
// print(amount[sender.tag])
self.bookieTotal = amount.reduce(0, +)
print(bookieTotal)
}
#IBOutlet weak var flavorTable: UITableView!
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return bookieFlavors.count
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! FlavorTableCell
//flavor label configuration
cell.flavorLabel.text = bookieFlavors[indexPath.row]
//amount configuration
cell.bookieAmount.text = "= \(self.amount[indexPath.row])"
cell.bookieButton.tag = indexPath.row
cell.bookieButton.addTarget(self, action: #selector(bookieButton(_:)), for: .touchUpInside)
cell.bookieButton.addTarget(self, action: #selector(emptyAmount(_:)), for: .touchDownRepeat)
return cell
}
#IBAction func registerBookieAmount(_ sender: Any) {
print(bookieTotal)
let amount: Int = bookieTotal
let user = Auth.auth().currentUser
if ((user) != nil) {
}
}

What you can do is to make a request to Firebase server to see if the user is still logged in by this code
if Auth.auth().currentUser != nil {
// User is signed in.
let ref = Database.database().reference()
ref.child("users").child(user.uid).child("orders").setValue(["order": orderNumber])
} else {
// No user is signed in.
}

Related

How to keep label results on secondViewController?

I am currently working on an app and I am stuck on the following: I have my mainVC (ReceiveInputVC), which after I enter an input, it goes to the secondVC (TimeLeftVC) and it updates all of its labels with results from the inputs received from the mainVC. My question is: How can I, after clicking on the arrow to go back to the mainVC or even if I close the app, when I click on the arrow from the mainVC to go to the secondVC have my labels showing the same values as before the user closed the application or returned to the main screen?
import UIKit
extension UIViewController {
func hideKeyboard() {
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
view.addGestureRecognizer(tap)
}
#objc func dismissKeyboard() {
view.endEditing(true)
}
}
class ReceiveInputVC: UIViewController {
#IBOutlet weak var hourglassButton: UIButton!
#IBOutlet weak var whatIsYourAgeField: UITextField!
#IBOutlet weak var ageToDieField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.hideKeyboard()
}
#IBAction func arrowBtnPressed(_ sender: Any) {
// When pressed should show go to TimeLeftVC and show last result from the first time user entered the inputs, if nothing has been typed yet and no data has been saved an alert should pop up asking the user to enter an input on both fields
}
#IBAction func hourglassBtnPressed(_ sender: Any) {
let checkAgeField: Int? = Int(whatIsYourAgeField.text!)
let checkDyingAgeField: Int? = Int(ageToDieField.text!)
if (whatIsYourAgeField.text == "" || ageToDieField.text == "") || (whatIsYourAgeField.text == "" && ageToDieField.text == "") {
alert(message: "You must enter an input on both fields")
} else if checkAgeField! < 1 || checkDyingAgeField! > 100 {
alert(message: "You must enter an age higher than 1 and a dying age lower than 100")
} else if (checkAgeField! > checkDyingAgeField!) || (checkAgeField! == checkDyingAgeField!) {
alert(message: "You must enter an age lower than a dying age")
} else {
performSegue(withIdentifier: "goToSecondScreen", sender: self)
}
}
func alert(message: String, title: String = "Alert") {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Try Again", style: UIAlertAction.Style.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
// Passing the data entered from ReceiveInputVC to TimeLeftVC
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToSecondScreen" {
let destinationTimeLeftVC = segue.destination as! TimeLeftVC
destinationTimeLeftVC.ageReceived = whatIsYourAgeField.text
destinationTimeLeftVC.ageToDieReceived = ageToDieField.text
}
}
}
import CircleProgressBar
class TimeLeftVC: UIViewController {
var ageReceived: String! // receive whatIsYourAgeField data from ReceiveInputVC
var ageToDieReceived: String! // receive ageToDieField data from ReceiveInputVC
#IBOutlet weak var yearsLeftLabel: UILabel!
#IBOutlet weak var daysLeftLabel: UILabel!
#IBOutlet weak var hoursLeftLabel: UILabel!
#IBOutlet weak var progressBar: CircleProgressBar!
override func viewDidLoad() {
super.viewDidLoad()
createResults()
}
func createResults() {
if let userAge = Int(ageReceived), let dyingAge = Int(ageToDieReceived) {
let yearsLeft = dyingAge - userAge
let daysLeft = yearsLeft * 365
let hoursLeft = daysLeft * 24
// Update UI
yearsLeftLabel.text = "\(yearsLeft)"
daysLeftLabel.text = "\(daysLeft)"
hoursLeftLabel.text = "\(hoursLeft)"
let percentage = (CGFloat(yearsLeft) / CGFloat(dyingAge)) * 100
let formatted = String(format: "%.1f", percentage)
// Update Circle Progress Bar
progressBar.setHintTextGenerationBlock { (progress) -> String? in
return String.init(format: "\(formatted)%%", arguments: [progress])
}
progressBar.setProgress(percentage/100, animated: true, duration: 4.0)
}
}
Project on GitHub: https://github.com/mvvieira95/Time-Life.git
You can use Coredata or another data base or user default
User default implementation:
#IBAction func arrowBtnPressed(_ sender: Any) {
UserDefaults.standard.set("your input values from text field or ...", forKey: "key")
}
In second view controller get it with
UserDefaults.standard.string(forKey: "key")
You can save and restore states with these methods
application:shouldSaveApplicationState and application:shouldRestoreApplicationStat.
Example:
func application(_ application: UIApplication,
shouldSaveApplicationState coder: NSCoder) -> Bool {
// Save the current app version to the archive.
coder.encode(11.0, forKey: "MyAppVersion")
// Always save state information.
return true
}
func application(_ application: UIApplication,
shouldRestoreApplicationState coder: NSCoder) -> Bool {
// Restore the state only if the app version matches.
let version = coder.decodeFloat(forKey: "MyAppVersion")
if version == 11.0 {
return true
}
// Do not restore from old data.
return false
}
You can explore the document in https://developer.apple.com/documentation/uikit/view_controllers/preserving_your_app_s_ui_across_launches?language=objc
Thanks guys, I came up with a solution:
class ReceiveInputVC: UIViewController {
#IBAction func arrowBtnPressed(_ sender: Any) {
let defaults = UserDefaults.standard
if let _ = defaults.object(forKey: "yearsSaved"), let _ = defaults.object(forKey: "daysSaved"), let _ = defaults.object(forKey: "hoursSaved") {
performSegue(withIdentifier: "goToSecondScreen", sender: self)
} else {
alert(message: "You must first enter an input")
}
}
class TimeLeftVC: UIViewController {
var ageReceived: String! // receive whatIsYourAgeField data from ReceiveInputVC
var ageToDieReceived: String! // receive ageToDieField data from ReceiveInputVC
#IBOutlet weak var yearsLeftLabel: UILabel!
#IBOutlet weak var daysLeftLabel: UILabel!
#IBOutlet weak var hoursLeftLabel: UILabel!
#IBOutlet weak var progressBar: CircleProgressBar!
override func viewDidLoad() {
super.viewDidLoad()
let defaults = UserDefaults.standard
yearsLeftLabel.text = defaults.object(forKey: "yearsSaved") as? String
daysLeftLabel.text = defaults.object(forKey: "daysSaved") as? String
hoursLeftLabel.text = defaults.object(forKey: "hoursSaved") as? String
}
override func viewWillAppear(_ animated: Bool) {
createResults()
}
func createResults() {
if let userAge = Int(ageReceived), let dyingAge = Int(ageToDieReceived) {
let yearsLeft = dyingAge - userAge
let daysLeft = yearsLeft * 365
let hoursLeft = daysLeft * 24
// Update UI
yearsLeftLabel.text = "\(yearsLeft)"
daysLeftLabel.text = "\(daysLeft)"
hoursLeftLabel.text = "\(hoursLeft)"
// Store Data
let defaults = UserDefaults.standard
defaults.set(yearsLeftLabel.text, forKey: "yearsSaved")
defaults.set(daysLeftLabel.text, forKey: "daysSaved")
defaults.set(hoursLeftLabel.text, forKey: "hoursSaved")
// Update Circle Progress Bar
let percentage = (CGFloat(yearsLeft) / CGFloat(dyingAge)) * 100
let formatted = String(format: "%.1f", percentage)
progressBar.setHintTextGenerationBlock { (progress) -> String? in
return String.init(format: "\(formatted)%%", arguments: [progress])
}
progressBar.setProgress(percentage/100, animated: true, duration: 4.0)
}
}
Having troubles now updating that progressBar when I go back to the view...

Show posts from TableView only from current user login

I have a viewcontroller displaying some post using a tableview and cells. All the datas are being fetched from Firebase ( Database and Users).
I have a login screen before to arrive to the feedview.
I'd like to show only the pots from the current user login in that tableview controller. How is that possible ?
My database is build like this:
For my Signin Page, I am using the following:
//
// ViewController.swift
// MobileAppDemo
//
// Created by Mikko Hilpinen on 27.10.2016.
// Copyright © 2016 Mikkomario. All rights reserved.
//
import UIKit
import FBSDKCoreKit
import FBSDKLoginKit
import Firebase
import FirebaseAuth
import SwiftKeychainWrapper
fileprivate struct RegisterInfo
{
let email: String
let password: String
}
class SignInVC: UIViewController
{
#IBOutlet weak var emailField: UITextField!
#IBOutlet weak var passwordField: UITextField!
override func viewDidLoad()
{
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool)
{
if User.currentUserId != nil
{
print("AUTH: USING EXISTING KEYCHAIN")
User.startTrackingCurrentUser()
//performSegue(withIdentifier: "ToFeed", sender: nil)
}
else
{
print("AUTH: NO EXSTING KEYCHAIN")
}
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
print("AUTH: Preparing for segue \(segue.identifier)")
if let registrationVC = segue.destination as? RegisterVC
{
print("AUTH: Found registration VC")
if let info = sender as? RegisterInfo
{
print("AUTH: Sending email (\(info.email)) and password (\(info.password.characters.count) chars) information: ")
registrationVC.setBaseInfo(email: info.email, password: info.password)
}
}
}
#IBAction func signInButtonPressed(_ sender: UIButton)
{
if let email = emailField.text, let password = passwordField.text
{
FIRAuth.auth()?.signIn(withEmail: email, password: password)
{
(user, error) in
if let error = error
{
// TODO: Handle other errors here as well
switch FIRAuthErrorCode(rawValue: error._code)!
{
case .errorCodeUserNotFound:
print("AUTH: USER NOT FOUND -> CREATING NEW USER")
print("AUTH: Sending email \(email) and password \(password.characters.count) characters")
self.performSegue(withIdentifier: "RegisterUser", sender: RegisterInfo(email: email, password: password))
default: print("AUTH: ERROR IN EMAIL LOGIN \(error)") // TODO: Inform user
}
}
else
{
print("AUTH: EMAIL AUTH SUCCESSFUL")
User.currentUserId = user?.uid
User.startTrackingCurrentUser()
self.performSegue(withIdentifier: "ToFeed", sender: nil)
}
}
}
// TODO: Inform user that the field contents are missing
}
fileprivate func firebaseAuth(with credential: FIRAuthCredential)
{
if FIRAuth.auth() == nil
{
print("AUTH: NO AUTH SERVICE AVAILABLE")
}
FIRAuth.auth()?.signIn(with: credential)
{
(user, error) in
if let error = error
{
print("AUTH: UNABLE TO AUTHENTICATE TO FIREBASE")
print("AUTH: \(error)")
}
else
{
if let user = user
{
print("AUTH: SUCCESSFULLY AUTHENTICATED WITH FIREBASE")
// Updates current user data
var userName = "User"
var image: UIImage?
if let retrievedName = user.displayName
{
userName = retrievedName
}
if let retrievedImageUrl = user.photoURL
{
if let data = try? Data(contentsOf: retrievedImageUrl)
{
image = UIImage(data: data)
}
}
User.post(uid: user.uid, provider: user.providerID, userName: userName, image: image)
{
user in
User.currentUser = user
User.startTrackingCurrentUser()
self.performSegue(withIdentifier: "ToFeed", sender: nil)
}
}
}
}
}
}
To display my data in the table view, I'm using this view controller:
//
// Feed.swift
// MobileAppDemo
//
// Created by Mikko Hilpinen on 31.10.2016.
// Copyright © 2016 Mikkomario. All rights reserved.
//
import UIKit
import FirebaseAuth
import FirebaseDatabase
import FirebaseStorage
import SwiftKeychainWrapper
import SwiftyJSON
var posts = [Post]()
var selectedIndexPath: Int = 0
class FeedVC: UIViewController, UITableViewDataSource, UIImagePickerControllerDelegate, UINavigationControllerDelegate, UITableViewDelegate {
#IBOutlet weak var feedTableView: UITableView!
private var readPosts: ObserveTask?
override func viewDidLoad() {
super.viewDidLoad()
feedTableView.dataSource = self
feedTableView.delegate = self
readPosts = Post.observeList(from: Post.parentReference.queryOrdered(byChild: Post.PROPERTY_CREATED)) {
observedPosts in
posts = observedPosts.reversed()
self.feedTableView.reloadData()
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.feedTableView.dequeueReusableCell(withIdentifier: "MessageCell")! as UITableViewCell
let imageView = cell.viewWithTag(1) as! UIImageView
let titleLabel = cell.viewWithTag(2) as! UILabel
titleLabel.text = posts[indexPath.row].title
titleLabel.numberOfLines = 0
Storage.getImage(with: posts[indexPath.row].imageUrl){
postPic in
imageView.image = postPic
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedIndexPath = indexPath.row
self.performSegue(withIdentifier: "push", sender: self)
self.feedTableView.reloadData()
}
}
and finally for my Cells:
//
// MessageCell.swift
// MobileAppDemo
//
// Created by Mikko Hilpinen on 31.10.2016.
// Copyright © 2016 Mikkomario. All rights reserved.
//
import UIKit
import FirebaseStorage
class MessageCell: UITableViewCell
{
#IBOutlet weak var messageImageView: UIImageView!
#IBOutlet weak var messageTextView: UITextView!
#IBOutlet weak var titleTextView: UITextView!
#IBOutlet weak var linkbutton: UIButton!
private var post: Post!
func configureCell(tableView: UITableView, post: Post)
{
self.post = post
// Basic info
titleTextView.text = post.title
messageTextView.text = post.caption
// Post user
User.get(id: post.creatorId)
{
postCreator in
}
// Image
Storage.getImage(with: post.imageUrl)
{
postPic in
self.messageImageView.image = postPic
// Row height changes so table needs to be reset
tableView.beginUpdates()
tableView.endUpdates()
}
}
}
If anybody have any clue / suggestion how I can achieve it ?? It will be incredible :) :)
Thanks a lot !!!
With your Posts database reference with queryOrdered(byChild:) add queryEqual(toValue:) and compare it with User.currentUserId.
readPosts = Post.observeList(from: Post.parentReference
.queryOrdered(byChild: Post.PROPERTY_CREATED)
.queryEqual(toValue: User.currentUserId)) {
observedPosts in
posts = observedPosts.reversed()
self.feedTableView.reloadData()
}

ios Firebase - segue data in a "details post" Controller

Here is my situation:
I have a controller "Feed" which list multiple posts via a table ( a title and image) from Firebase.
On touch of a button, it bring to a "Feed Details" controller, where I would like the data ( title image and caption) from the post clicked previously (parent) being display. ( see screenshot 2)
At the moment nothing is being fetch when I arrive to the feed details controllers ...
How is it possible to fetch the details from the item click previously ??
Currently this is my feed controller:
//
// FeedVC.swift
// MobileAppDemo
//
// Created by Mikko Hilpinen on 31.10.2016.
// Copyright © 2016 Mikkomario. All rights reserved.
//
import UIKit
import FirebaseAuth
import FirebaseDatabase
import FirebaseStorage
import SwiftKeychainWrapper
import SwiftyJSON
class FeedVC: UIViewController, UITableViewDataSource, UIImagePickerControllerDelegate, UINavigationControllerDelegate
{
#IBOutlet weak var addImageView: UIImageView!
#IBOutlet weak var feedTableView: UITableView!
#IBOutlet weak var titleInputView: InputTextView!
#IBOutlet weak var captionInputView: InputTextView!
private var posts = [Post]()
private var imagePicker = UIImagePickerController()
private var imageSelected = false
private var readPosts: ObserveTask?
override func viewDidLoad()
{
super.viewDidLoad()
imagePicker.delegate = self
imagePicker.allowsEditing = true
feedTableView.dataSource = self
feedTableView.rowHeight = UITableViewAutomaticDimension
feedTableView.estimatedRowHeight = 320
readPosts = Post.observeList(from: Post.parentReference.queryOrdered(byChild: Post.PROPERTY_CREATED))
{
posts in
self.posts = posts.reversed()
self.feedTableView.reloadData()
}
}
func numberOfSections(in tableView: UITableView) -> Int
{
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return posts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
if let cell = tableView.dequeueReusableCell(withIdentifier: "MessageCell", for: indexPath) as? MessageCell
{
let post = posts[indexPath.row]
cell.configureCell(tableView: tableView, post: post)
return cell
}
else
{
fatalError()
}
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any])
{
if let image = info[UIImagePickerControllerEditedImage] as? UIImage
{
addImageView.image = image
imageSelected = true
}
picker.dismiss(animated: true, completion: nil)
}
#IBAction func selectImagePressed(_ sender: AnyObject)
{
present(imagePicker, animated: true, completion: nil)
}
#IBAction func postButtonPressed(_ sender: AnyObject)
{
guard let caption = captionInputView.text, !caption.isEmpty else
{
// TODO: Inform the user
print("POST: Caption must be entered")
return
}
guard let title = titleInputView.text, !title.isEmpty else
{
// TODO: Inform the user
print("POST: title must be entered")
return
}
guard let image = addImageView.image, imageSelected else
{
print("POST: Image must be selected")
return
}
guard let currentUserId = User.currentUserId else
{
print("POST: Can't post before logging in")
return
}
imageSelected = false
addImageView.image = UIImage(named: "add-image")
captionInputView.text = nil
titleInputView.text = nil
// Uploads the image
if let imageData = UIImageJPEGRepresentation(image, 0.2)
{
let imageUid = NSUUID().uuidString
let metadata = FIRStorageMetadata()
metadata.contentType = "image/jpeg"
Storage.REF_POST_IMAGES.child(imageUid).put(imageData, metadata: metadata)
{
(metadata, error) in
if let error = error
{
print("STORAGE: Failed to upload image to storage \(error)")
}
if let downloadURL = metadata?.downloadURL()?.absoluteString
{
// Caches the image for faster display
Storage.imageCache.setObject(image, forKey: downloadURL as NSString)
print("STORAGE: Successfully uploaded image to storage")
_ = Post.post(caption: caption, title: title, imageUrl: downloadURL, creatorId: currentUserId)
}
}
}
}
#IBAction func signOutButtonPressed(_ sender: AnyObject)
{
// Doesn't listen to posts anymore
readPosts?.stop()
try! FIRAuth.auth()?.signOut()
User.currentUserId = nil
dismiss(animated: true, completion: nil)
}
}
I'm using the code for the feeddetails - Which of course doesn't feet the data
My database structure look like below:
How is it possible to fetch the data in that details view ?? If anybody could explain me the process, it will be really fantastic !!
Thank you for all your time and help !!
----- EDIT: --------
Thanks to Retterdesdialogs, I've update the code and the App start, but when i click on the button linkbutton nothin happen and the App crash:
The Console display:
Could not cast value of type 'UIViewController' (0x1087f6758) to 'MobileAppDemo.FeedDetailsController' (0x104bc34a0).
I'm having now for code:
feedVC.swift ( there all the post are listed):
//
// FeedVC.swift
// MobileAppDemo
//
// Created by Mikko Hilpinen on 31.10.2016.
// Copyright © 2016 Mikkomario. All rights reserved.
//
import UIKit
import FirebaseAuth
import FirebaseDatabase
import FirebaseStorage
import SwiftKeychainWrapper
import SwiftyJSON
class FeedVC: UIViewController, UITableViewDataSource, UIImagePickerControllerDelegate, UINavigationControllerDelegate
{
#IBOutlet weak var addImageView: UIImageView!
#IBOutlet weak var feedTableView: UITableView!
#IBOutlet weak var titleInputView: InputTextView!
#IBOutlet weak var linkbutton: UIButton!
#IBOutlet weak var captionInputView: InputTextView!
private var posts = [Post]()
private var imagePicker = UIImagePickerController()
private var imageSelected = false
private var readPosts: ObserveTask?
override func viewDidLoad()
{
super.viewDidLoad()
imagePicker.delegate = self
imagePicker.allowsEditing = true
feedTableView.dataSource = self
feedTableView.rowHeight = UITableViewAutomaticDimension
feedTableView.estimatedRowHeight = 320
readPosts = Post.observeList(from: Post.parentReference.queryOrdered(byChild: Post.PROPERTY_CREATED))
{
posts in
self.posts = posts.reversed()
self.feedTableView.reloadData()
}
}
func numberOfSections(in tableView: UITableView) -> Int
{
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return posts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
// here you need to add
{
if let cell = tableView.dequeueReusableCell(withIdentifier: "MessageCell", for: indexPath) as? MessageCell
{
let post = posts[indexPath.row]
cell.configureCell(tableView: tableView, post: post)
cell.linkbutton.tag = indexPath.row
cell.linkbutton.addTarget(self, action: #selector(FeedVC.toFeedDetailAction(_:)), for: .touchUpInside)
return cell
}
else
{
fatalError()
}
}
func toFeedDetailAction(_ sender: UIButton) {
let FeedDetailsController = self.storyboard?.instantiateViewController(withIdentifier: "FeedDetailsIdentifier") as! FeedDetailsController
FeedDetailsController.post = posts[sender.tag]
self.navigationController?.pushViewController(FeedDetailsController, animated: true)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any])
{
if let image = info[UIImagePickerControllerEditedImage] as? UIImage
{
addImageView.image = image
imageSelected = true
}
picker.dismiss(animated: true, completion: nil)
}
#IBAction func selectImagePressed(_ sender: AnyObject)
{
present(imagePicker, animated: true, completion: nil)
}
#IBAction func postButtonPressed(_ sender: AnyObject)
{
guard let caption = captionInputView.text, !caption.isEmpty else
{
// TODO: Inform the user
print("POST: Caption must be entered")
return
}
guard let title = titleInputView.text, !title.isEmpty else
{
// TODO: Inform the user
print("POST: title must be entered")
return
}
guard let image = addImageView.image, imageSelected else
{
print("POST: Image must be selected")
return
}
guard let currentUserId = User.currentUserId else
{
print("POST: Can't post before logging in")
return
}
imageSelected = false
addImageView.image = UIImage(named: "add-image")
captionInputView.text = nil
titleInputView.text = nil
// Uploads the image
if let imageData = UIImageJPEGRepresentation(image, 0.2)
{
let imageUid = NSUUID().uuidString
let metadata = FIRStorageMetadata()
metadata.contentType = "image/jpeg"
Storage.REF_POST_IMAGES.child(imageUid).put(imageData, metadata: metadata)
{
(metadata, error) in
if let error = error
{
print("STORAGE: Failed to upload image to storage \(error)")
}
if let downloadURL = metadata?.downloadURL()?.absoluteString
{
// Caches the image for faster display
Storage.imageCache.setObject(image, forKey: downloadURL as NSString)
print("STORAGE: Successfully uploaded image to storage")
_ = Post.post(caption: caption, title: title, imageUrl: downloadURL, creatorId: currentUserId)
}
}
}
}
#IBAction func signOutButtonPressed(_ sender: AnyObject)
{
// Doesn't listen to posts anymore
readPosts?.stop()
try! FIRAuth.auth()?.signOut()
User.currentUserId = nil
dismiss(animated: true, completion: nil)
}
}
and the FeedDetails.swift ( where the Detail are listed once you have clicked the button, button has class: linkbutton from the viewVC Controller:
//
// FeedDetailsController.swift
// MobileAppDemo
//
// Created by Mikko Hilpinen on 27.10.2016.
// Copyright © 2016 Mikkomario. All rights reserved.
//
import UIKit
import FBSDKCoreKit
import FBSDKLoginKit
import Firebase
import FirebaseAuth
import SwiftKeychainWrapper
fileprivate struct RegisterInfo
{
let email: String
let password: String
}
class FeedDetailsController: UIViewController
{
#IBOutlet weak var emailField: UITextField!
#IBOutlet weak var passwordField: UITextField!
var post: Post!
override func viewDidLoad()
{
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool)
{
if User.currentUserId != nil
{
print("AUTH: USING EXISTING KEYCHAIN")
User.startTrackingCurrentUser()
performSegue(withIdentifier: "ToFeed", sender: nil)
}
else
{
print("AUTH: NO EXSTING KEYCHAIN")
}
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
print("AUTH: Preparing for segue \(segue.identifier)")
if let registrationVC = segue.destination as? RegisterVC
{
print("AUTH: Found registration VC")
if let info = sender as? RegisterInfo
{
print("AUTH: Sending email (\(info.email)) and password (\(info.password.characters.count) chars) information: ")
registrationVC.setBaseInfo(email: info.email, password: info.password)
}
}
}
#IBAction func signInButtonPressed(_ sender: UIButton)
{
if let email = emailField.text, let password = passwordField.text
{
FIRAuth.auth()?.signIn(withEmail: email, password: password)
{
(user, error) in
if let error = error
{
// TODO: Handle other errors here as well
switch FIRAuthErrorCode(rawValue: error._code)!
{
case .errorCodeUserNotFound:
print("AUTH: USER NOT FOUND -> CREATING NEW USER")
print("AUTH: Sending email \(email) and password \(password.characters.count) characters")
self.performSegue(withIdentifier: "RegisterUser", sender: RegisterInfo(email: email, password: password))
default: print("AUTH: ERROR IN EMAIL LOGIN \(error)") // TODO: Inform user
}
}
else
{
print("AUTH: EMAIL AUTH SUCCESSFUL")
User.currentUserId = user?.uid
User.startTrackingCurrentUser()
self.performSegue(withIdentifier: "ToFeed", sender: nil)
}
}
}
// TODO: Inform user that the field contents are missing
}
#IBAction func facebookButtonPressed(_ sender: UIButton)
{
// (Already logged in to FB)
if let fbAccessToken = FBSDKAccessToken.current()
{
print("AUTH: Already logged in to FB")
firebaseAuth(with: FIRFacebookAuthProvider.credential(withAccessToken: fbAccessToken.tokenString))
}
else
{
let facebookLogin = FBSDKLoginManager()
facebookLogin.logIn(withReadPermissions: ["public_profile", "email"], from: self)
{
(result, error) in
if let error = error
{
print("AUTH: UNABLE TO AUTHENTICATE WITH FACEBOOK")
print("AUTH: \(error)")
}
else if let result = result
{
if result.isCancelled
{
print("AUTH: USER CANCELLED FACEBOOK AUTH")
}
else
{
print("AUTH: FACEBOOK AUTH SUCCESS")
let credential = FIRFacebookAuthProvider.credential(withAccessToken: FBSDKAccessToken.current().tokenString)
self.firebaseAuth(with: credential)
}
}
}
}
}
fileprivate func firebaseAuth(with credential: FIRAuthCredential)
{
if FIRAuth.auth() == nil
{
print("AUTH: NO AUTH SERVICE AVAILABLE")
}
FIRAuth.auth()?.signIn(with: credential)
{
(user, error) in
if let error = error
{
print("AUTH: UNABLE TO AUTHENTICATE TO FIREBASE")
print("AUTH: \(error)")
}
else
{
if let user = user
{
print("AUTH: SUCCESSFULLY AUTHENTICATED WITH FIREBASE")
// Updates current user data
var userName = "User"
var image: UIImage?
if let retrievedName = user.displayName
{
userName = retrievedName
}
if let retrievedImageUrl = user.photoURL
{
if let data = try? Data(contentsOf: retrievedImageUrl)
{
image = UIImage(data: data)
}
}
User.post(uid: user.uid, provider: user.providerID, userName: userName, image: image)
{
user in
User.currentUser = user
User.startTrackingCurrentUser()
self.performSegue(withIdentifier: "ToFeed", sender: nil)
}
}
}
}
}
}
1) you need to be able to access the button through IBOutlet or programmatically
2) set the button to the cells indexPath.row and add an action to it
let post = posts[indexPath.row]
cell.configureCell(tableView: tableView, post: post)
// here you need to add
cell.yourButton.tag = indexPath.row
cell.yourButton.addTarget(self, action: #selector(FeedViewController.toFeedDetailAction(_:)), for: .touchUpInside)
return cell
3) when you then press the button you can identify your feed, because you have the button tag, so you know the feed from your Posts-Array
func toFeedDetailAction(_ sender: UIButton) {
let feedDetailsViewController = self.storyboard?.instantiateViewController(withIdentifier: "FeedDetailsIdentifier") as! FeedDetailsViewController
feedDetailsViewController.post = posts[sender.tag]
self.navigationController?.pushViewController(feedDetailsViewController, animated: true)
}
4) in your FeedDetailsViewController add
var post: Post!
5) in FeedDetailsViewController do what ever you want with the post
Hope this helps :)

How to Update UITableView with data passed from another ViewController?

I am trying to populate a UITableView with data passed to the ViewController from LoginViewController after the user logs in.
The current process is:
ViewController loads first, if user is not logged in LoginViewController pops up over the top. User logs in, details are fetched from the database (userDetails and communities). LoginViewController is then dismissed and ViewController is again visible.
The communities variable is being populated and values transferred from LoginViewController to ViewController.
I believe my problem is func tableView is run before the data is fetched from the user logging in.
print ("test 1: ",communities) just prints [],[],[],[]
However print ("test 2: ",communities) prints the correct values.
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UsernameSentDelegate {
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var receiveUsername: UILabel!
#IBOutlet weak var userEmailText: UILabel!
var userEmail: String?
var communities = [String]()
#IBOutlet weak var communitiesTableView: UITableView!
#IBAction func unwindToHome(_ segue: UIStoryboardSegue) {
}
//recieves email address from delegate from LoginViewController
func userLoggedIn(data: String) {
userEmailText.text = data
}
override func viewDidLoad() {
super.viewDidLoad()
self.communitiesTableView.delegate = self
self.communitiesTableView.dataSource = self
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print ("test 1: ",communities) //not printing value
return self.communities.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let title = self.communities[indexPath.row]
let cell = UITableViewCell()
cell.textLabel?.text = title
return cell
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
if segue.identifier == "loginView" {
let loginViewController: LoginViewController = segue.destination as! LoginViewController
loginViewController.delegate = self
}
if segue.identifier == "createCommunitySegue" {
let createCommunityController: CreateNewCommunity = segue.destination as! CreateNewCommunity
createCommunityController.myEmail = userEmailText.text
}
}
override func viewDidAppear(_ animated: Bool)
{
print ("test 2: ",communities) //prints values correctly
let isUserLoggedIn = UserDefaults.bool(UserDefaults.standard)(forKey: "isUserLoggedIn");
if(!isUserLoggedIn)
{
self.performSegue(withIdentifier: "loginView", sender: self);
}
}
#IBAction func logoutButtonTapped(_ sender: AnyObject) {
UserDefaults.set(UserDefaults.standard)(false, forKey: "isUserLoggedIn");
self.performSegue(withIdentifier: "loginView", sender: self);
}
#IBAction func createCommunityTapped(_ sender: AnyObject) {
}
}
This is the code for CreateCommunityViewController:
import UIKit
class CreateNewCommunity: UIViewController {
#IBOutlet weak var communityNameTextField: UITextField!
#IBOutlet weak var passwordTextField: UILabel!
#IBOutlet weak var emailLabel: UILabel!
var myEmail: String?
#IBAction func cancelButtonPapped(_ sender: AnyObject) {
self.performSegue(withIdentifier: "unwindCommunity", sender: self)
}
#IBAction func createCommunityButtonTapped(_ sender: AnyObject) {
let communityName = communityNameTextField.text;
if (communityName!.isEmpty){
displayMyAlertMessage(userMessage: "You must name your Community");
return;
}else{
func generateRandomStringWithLength(length: Int) -> String {
var randomString = ""
let letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
for _ in 1...length {
let randomIndex = Int(arc4random_uniform(UInt32(letters.characters.count)))
let a = letters.index(letters.startIndex, offsetBy: randomIndex)
randomString += String(letters[a])
}
return randomString
}
let communityCode = generateRandomStringWithLength(length: 6)
passwordTextField.text = communityCode
let myUrl = URL(string: "http://www.quasisquest.uk/KeepScore/createCommunity.php?");
var request = URLRequest(url:myUrl!);
request.httpMethod = "POST";
let postString = "communityname=\(communityName!)&code=\(communityCode)&email=\(myEmail!)";
request.httpBody = postString.data(using: String.Encoding.utf8);
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
DispatchQueue.main.async
{
if (try! JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String:AnyObject]) != nil {
let myAlert = UIAlertController(title: "Community Registered", message: "Community Code:\(communityCode)", preferredStyle: UIAlertControllerStyle.alert);
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.default){(action) in
self.dismiss(animated: true, completion: nil)
}
myAlert.addAction(okAction);
self.present(myAlert, animated: true, completion: nil)
}
}
}
task.resume()
}
}
}
Try to call reload data in didSet. E.g. var communities = [] { didSet { yourtableview.realoadData()
}
}

Changing ViewController if user is Facebook Logged in

I'm having quite some troubles implementing Facebook login in my iOS , everything works fine if the user is not already logged in, the application fetches correctly the data from Facebook and pass them to the next ViewController , instead if is already logged in it should automatically segue to a recap page that shows user's info but i can't make it happen, currently I'm using this method :
override func viewDidLoad() {
super.viewDidLoad()
LoginButton.delegate = self
if (FBSDKAccessToken.currentAccessToken() != nil) {
self.performSegueWithIdentifier("Login", sender: self)
}
}
but in the console i get :
Facebook_Login.LoginViewController: 0x7fc04a519ca0 on Facebook_Login.ViewController: 0x7fc04a41c1e0 whose view is not in the window hierarchy!
i've also tried using the viewdidAppear method, but it segues to the recap page without updating the variables so i get an empty page
here' the complete code:
View Controller 1
import UIKit
import FBSDKLoginKit
class ViewController: UIViewController,FBSDKLoginButtonDelegate {
var nome1:String = ""
var cognome1:String = ""
var email1:String = ""
var compleanno:String = ""
var città:String = ""
var genere:String = ""
var immagine_url:String = ""
#IBOutlet weak var LoginButton: FBSDKLoginButton!
#IBAction func LoginAction(sender: AnyObject) {
LoginButton.delegate = self
LoginButton.readPermissions = ["email"]
}
func FetchInfo(){
print("scarico le informazioni...")
let parametri = ["fields":"email, first_name, last_name, birthday, hometown, gender, picture.type(large)"]
FBSDKGraphRequest(graphPath: "me", parameters: parametri).startWithCompletionHandler{(connection,result,error) -> Void in
if (error != nil){
print ("errore")
return
}
else {
if let email = result["email"] as? String {
print(email)
self.email1 = email
}
if let nome = result["first_name"] as? String {
print(nome)
self.nome1 = nome
}
if let cognome = result["last_name"] as? String {
print(cognome)
self.cognome1 = cognome
}
if let compleanno = result["birthday"] as? String{
print(compleanno)
}
if let città = result["hometown"] as? String{
print(città)
}
if var genere = result["gender"] as? String{
if (genere == "male"){
genere = "maschio"
}
else {
genere = "femmina"
}
print(genere)
}
}
if let picture = result["picture"] as? NSDictionary, data = picture["data"] as? NSDictionary, url = data["url"] as? String{
self.immagine_url = url
print(self.immagine_url)
}
}
return
}
func loginButton(loginButton: FBSDKLoginButton!, didCompleteWithResult result: FBSDKLoginManagerLoginResult!, error: NSError!){
if (result.isCancelled == true){
print("cancellato")
}
else {
print("login effettuato")
FetchInfo()
self.performSegueWithIdentifier("Login", sender: self)
}
}
func loginButtonDidLogOut(loginButton: FBSDKLoginButton!){
}
func loginButtonWillLogin(loginButton: FBSDKLoginButton!) -> Bool{
return true
}
#IBAction func returned(segue:UIStoryboardSegue){
}
override func viewDidLoad() {
super.viewDidLoad()
LoginButton.delegate = self
}
override func viewDidAppear(animated: Bool) {
if (FBSDKAccessToken.currentAccessToken() != nil) {
FetchInfo()
if(nome1 == ""){
}
else {
self.performSegueWithIdentifier("Login", sender: self)
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let destinazione:LoginViewController = segue.destinationViewController
as! LoginViewController
destinazione.temp_nome = nome1
destinazione.temp_cognome = cognome1
destinazione.temp_email = email1
destinazione.img_profile_url = immagine_url
}
}
ViewController 2:
import UIKit
import FBSDKLoginKit
class LoginViewController: UIViewController, FBSDKLoginButtonDelegate {
var temp_nome = ""
var temp_cognome = ""
var temp_email = ""
var img_profile_url:String = ""
#IBOutlet weak var Nome_Utente: UILabel!
#IBOutlet weak var email: UILabel!
#IBOutlet weak var Immagine_Utente: UIImageView!
#IBOutlet weak var Login_button: FBSDKLoginButton!
#IBAction func Login_button_Action(sender: AnyObject) {
Login_button.delegate = self
}
func loginButton(loginButton: FBSDKLoginButton!, didCompleteWithResult result: FBSDKLoginManagerLoginResult!, error: NSError!){
}
func loginButtonDidLogOut(loginButton: FBSDKLoginButton!){
self.performSegueWithIdentifier("Back", sender: self)
}
override func viewDidLoad() {
super.viewDidLoad()
Nome_Utente.text = "\(temp_nome)" + " " + "\(temp_cognome)"
email.text = "\(temp_email)"
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
First of all, avoid to reset your LoginButton.delegate in the LoginAction() function.
If the user is already logged in, to avoid an useless call to the Facebook Graph API (if you do not necessarily need to update his informations), you can store your user informations in CoreData or in a NSUserDefault.
If you never used it you can use the CDHelper lib (CoreDataHelper) which will allow you to use it without difficulties.
Hope I was helpful, if not, do not hesitate to give us a feedback.
Btw, in your viewDidAppear(animated: Bool) function, you have to call super.viewDidAppear(animated) !

Resources