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

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

Related

User variable is nil when creating a user with Firebase

I am following a tutorial and cannot seem to register my user as the user variable in the Firebase .createUser method appears to be nil. Therefore, when I unwrap it, I get an error.
I have read through a lot of the documentation as well as checked many other questions similar to mine but nothing seems to work
import UIKit
import Firebase
import SwiftKeychainWrapper
class ViewController: UIViewController {
#IBOutlet weak var userImgView: UIImageView!
#IBOutlet weak var usernameField: UITextField!
#IBOutlet weak var emailField: UITextField!
#IBOutlet weak var passwordField: UITextField!
var imagePicker: UIImagePickerController!
var selectedImage: UIImage!
override func viewDidLoad() {
super.viewDidLoad()
imagePicker = UIImagePickerController()
imagePicker.allowsEditing = true
imagePicker.delegate = self
}
override func viewDidAppear(_ animated: Bool) {
if let _ = KeychainWrapper.standard.string(forKey: "uid") {
self.performSegue(withIdentifier: "toFeed", sender: nil)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func setupUser(userUid: String) {
if let imageData = self.userImgView.image!.jpegData(compressionQuality: 0.2) {
let imgUid = NSUUID().uuidString
let metaData = StorageMetadata()
Storage.storage().reference().child(imgUid).putData(imageData, metadata: metaData) { (metadata, error) in
let downloadURL = metadata
let userData = [
"username": self.usernameField.text!,
"userImg": downloadURL!
] as [String : Any]
Database.database().reference().child("users").child(userUid).setValue(userData)
self.performSegue(withIdentifier: "toFeed", sender: nil)
}
}
}
#IBAction func signInPressed(_ sender: Any) {
if let email = emailField.text, let password = passwordField.text {
Auth.auth().signIn(withEmail: email, password: password) { user, error in
if error != nil && !(self.usernameField.text?.isEmpty)! {
Auth.auth().createUser(withEmail: email, password: password) { (user, error) in
self.performSegue(withIdentifier: "toFeed", sender: nil)
let userID = (user?.user.uid)!
self.setupUser(userUid: userID)
KeychainWrapper.standard.set(userID, forKey: "uid")
}
} else {
if let userID = (user?.user.uid) {
KeychainWrapper.standard.set((userID), forKey: "uid")
self.performSegue(withIdentifier: "toFeed", sender: nil)
}
}
}
}
}
#IBAction func getPhoto (_ sender: AnyObject) {
present(imagePicker, animated: true, completion: nil)
}
}
extension ViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
internal func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let image = info[.originalImage] as? UIImage {
userImgView.image = image
} else {
print("image wasnt selected")
}
imagePicker.dismiss(animated: true, completion: nil)
}
}
The error I am getting is one the "let userID = (user?.user.uid)!". It is
Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
The completion block for createUser(withEmail:,password:) gets called with either a AuthResult.user or an error. That why, as Joshua commented, you should check if error is nil before accessing any of the user properties.
From the auth quickstart for Swift:
Auth.auth().createUser(withEmail: email, password: password) { authResult, error in
strongSelf.hideSpinner {
guard let user = authResult?.user, error == nil else {
strongSelf.showMessagePrompt(error!.localizedDescription)
return
}
print("\(user.email!) created")
strongSelf.navigationController?.popViewController(animated: true)
}
}

Issues Uploading Images and posts to Firebase 5 Database

I am currently trying to upload a photo URL and post caption onto my firebase database. The photos are currently being saved on firebase storage which is fine, however I would like for it to also appear on the firebase database.
I repeatedly find myself dealing with this error "Value of type 'StorageMetadata' has no member 'downloadURL'"
I understand that in firebase 5 to get the url from storage you need to call downloadURL on storage reference, not metadata. I have tried multiple ways and examples, but it all leads to errors.screenshot of error
#IBOutlet weak var photo: UIImageView!
#IBOutlet weak var captionTextView: UITextView!
#IBOutlet weak var removeButton: UIBarButtonItem!
#IBOutlet weak var shareButton: UIButton!
var selectedImage: UIImage?
override func viewDidLoad() {
super.viewDidLoad()
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.handleSelectPhoto)); photo.addGestureRecognizer(tapGesture)
photo.isUserInteractionEnabled = true
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
handlePost()
}
func handlePost() {
if selectedImage != nil {
self.shareButton.isEnabled = true
self.removeButton.isEnabled = true
self.shareButton.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 1)
}else{
self.shareButton.isEnabled = false
self.shareButton.backgroundColor = .lightGray
self.removeButton.isEnabled = false
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
view.endEditing(true)
}
#objc func handleSelectPhoto() {
let pickerController = UIImagePickerController()
pickerController.delegate = self
present(pickerController, animated: true, completion: nil)
}
#IBAction func shareButton_TouchUpInside(_ sender: Any) {
view.endEditing(true)
let hud = JGProgressHUD(style: .dark)
hud.textLabel.text = "Loading"
hud.show(in: self.view)
hud.dismiss(afterDelay: 3.0)
if let profileImg = self.selectedImage, let imageData = UIImageJPEGRepresentation(profileImg, 0.1) {
let photoIdString = NSUUID().uuidString
let storageRef = Storage.storage().reference(forURL: ".......").child("posts").child(photoIdString)
storageRef.putData(imageData, metadata: nil, completion: { (metadata, error) in
if error != nil {
return
}
//let photoUrl = url?.absoluteString
let photoUrl = metadata?.downloadURL()?.absoluteString
self.sendDataToDatabase(photoUrl: photoUrl!)
}
)}
}
#IBAction func remove_TouchUpInside(_ sender: Any) {
clean()
handlePost()
}
func sendDataToDatabase(photoUrl: String) {
let ref = Database.database().reference()
let postsReference = ref.child("posts")
let newPostId = postsReference.childByAutoId().key
let newPostReference = postsReference.child(newPostId)
newPostReference.setValue(["photoUrl": photoUrl, "caption": captionTextView.text!], withCompletionBlock: {
(error, ref) in
if error != nil {
ProgressHUD.showError(error!.localizedDescription)
return
}
ProgressHUD.showSuccess("Success")
self.clean()
self.tabBarController?.selectedIndex = 0 // switches user back to selected tabbar 0 = first 1 = second etc.
})
}
func clean() {
self.captionTextView.text = ""
self.photo.image = UIImage(named:"placeholder-photo")
self.selectedImage = nil
}
}
extension CameraViewController: UIImagePickerControllerDelegate,
UINavigationControllerDelegate {
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [String : Any]) {
print("did finish picking media")
if let image = info["UIImagePickerControllerOriginalImage"]
as? UIImage {
selectedImage = image
photo.image = image
}
// profileImage.image = infoPhoto
dismiss(animated: true, completion: nil)
}
}
import UIKit
import ProgressHUD
import FirebaseStorage
import FirebaseDatabase
class CameraViewController: UIViewController {
#IBOutlet weak var photo: UIImageView!
#IBOutlet weak var shareButton: UIButton!
#IBOutlet weak var captionTextView: UITextView!
#IBOutlet weak var removeButton: UIBarButtonItem!
var selectedImage: UIImage?
override func viewDidLoad() {
super.viewDidLoad()
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.handleSelectPhoto))
photo.addGestureRecognizer(tapGesture)
photo.isUserInteractionEnabled = true
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
handlePost()
}
func handlePost(){
if selectedImage != nil {
self.shareButton.isEnabled = true
self.removeButton.isEnabled = true
self.shareButton.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 1)
} else {
self.shareButton.isEnabled = false
self.removeButton.isEnabled = false
self.shareButton.backgroundColor = .lightGray
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
view.endEditing(true)
}
#objc func handleSelectPhoto(){
let pickerController = UIImagePickerController()
pickerController.delegate = self
present(pickerController, animated: true, completion: nil)
}
// Share photos to the storage database
#IBAction func shareButton_TouchUpInside(_ sender: Any) {
view.endEditing(true)
ProgressHUD.show("Please wait...", interaction: false)
if let profileImg = self.selectedImage, let photoData = profileImg.jpegData(compressionQuality: 0.1) {
let photoIdString = NSUUID().uuidString
print(photoIdString)
let storageRef = Storage.storage().reference(forURL:Config.STORAGE_ROOT_REF).child("Posts").child(photoIdString)
storageRef.putData(photoData, metadata: nil, completion: { (metadata, error) in
if error != nil {
ProgressHUD.showError(error!.localizedDescription)
return
}
let photoUrl = metadata?.downloadURL()?.absoluteString
self.sendDataToDatabase(photoUrl: photoUrl!)
})
} else {
ProgressHUD.showError("Profile image can't be empty")
}
}
#IBAction func remove_touchUpInside(_ sender: Any) {
clean()
handlePost()
}
func sendDataToDatabase(photoUrl:String){
let ref = Database.database().reference()
let postsReference = ref.child("posts")
let newPostId = postsReference.childByAutoId().key
let newPostReference = postsReference.child(newPostId)
newPostReference.setValue(["photoUrl": photoUrl, "caption": captionTextView.text!], withCompletionBlock: {
(error, ref) in
if error != nil{
ProgressHUD.showError(error!.localizedDescription)
return
}
ProgressHUD.showSuccess("Success")
self.clean()
self.tabBarController?.selectedIndex = 0
})
}
func clean(){
self.captionTextView.text = ""
self.photo.image = UIImage(named: "placeholder-img")
self.shareButton = nil
}
}
extension CameraViewController : UIImagePickerControllerDelegate, UINavigationControllerDelegate {
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
print("did Finish picking media")
if let chosenImage = info[.originalImage] as? UIImage {
selectedImage = chosenImage
photo.image = chosenImage
}
dismiss(animated: true, completion: nil)
}
}
Use Above code, it will successfully upload the image and post that to the Firebase Database

Customize FUIAuthPickerViewController

How can I customize the Firebase UI Auth Picker controller with custom buttons, custom actions, background, loader etc..
I already try to subclass the FUIAuthPickerViewController but we can't access to login buttons
This is how you can create your own class of FUIAuthPickerViewController:
Create FUICustomLoginController.swift with:
import UIKit
import FirebaseUI
import FirebaseAuth
class FUICustomLoginController: ViewController {
var authUI: FUIAuth! = FUIAuth.defaultAuthUI()
var auth: Auth = Auth.auth()
private func didSignIn(auth: AuthCredential?, error: Error?, callBack: AuthResultCallback?) {
let callBack: (AuthDataResult?, Error?) -> Void = { [unowned self] result, error in
callBack?(result?.user, error)
self.authUI.delegate?.authUI?(self.authUI, didSignInWith: result, error: error)
}
if let auth = auth {
self.auth.signInAndRetrieveData(with: auth, completion: callBack)
} else if let error = error {
callBack(nil, error)
}
}
func signIn<T: FUIAuthProvider>(type: T.Type, defaultValue: String? = nil) {
try? self.authUI.signOut() // logout from google etc..
self.authUI.providers.first(where: { $0 is T })?.signIn(withDefaultValue: defaultValue, presenting: self, completion: self.didSignIn)
}
}
Subclass your controller from FUICustomLoginController:
class LoginPickerController: FUICustomLoginController {
override func viewDidLoad() {
super.viewDidLoad()
// Customize authUI if needed
//self.authUI.providers = ...
self.authUI.delegate = self
}
#IBAction func loginFacebook(_ sender: Any) {
self.signIn(type: FUIFacebookAuth.self)
}
#IBAction func loginGoogle(_ sender: Any) {
self.signIn(type: FUIGoogleAuth.self)
}
#IBAction func loginPhone(_ sender: Any) {
self.signIn(type: FUIPhoneAuth.self)
}
}
extension LoginPickerController: FUIAuthDelegate {
func authUI(_ authUI: FUIAuth, didSignInWith authDataResult: AuthDataResult?, error: Error?) {
// perform login actions
}
}
You can customize the default buttons, add images etc.. (a working hack )
class SignInViewController: FUIAuthPickerViewController {
weak var delegate: signInProtocol?
// Unhashed nonce.
fileprivate var currentNonce: String?
var backgView: UIView?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .clear
for each in view.subviews[0].subviews[0].subviews[0].subviews {
if let button = each as? UIButton {
button.layer.cornerRadius = 20.0
button.layer.masksToBounds = true
///do any other button customization here
}
}
///add background image
let scrollView = view.subviews[0]
scrollView.backgroundColor = .clear
let contentView = scrollView.subviews[0]
contentView.backgroundColor = .clear
let background = UIImage(named: "imagename")
let backgroundImageView = UIImageView(image: background)
backgroundImageView.contentMode = .scaleToFill
view.insertSubview(backgroundImageView, at: 0)
}
}

how to keep a user login even if they close the app? I'm using swift 3, and Firebase

I'm new to coding and to Stack overflow, I'm trying to have a user stay logged in even after they close the app. I also don't want them to always see the login in screen. how do I do i go about keeping the user Login even if they close the app and re-open the app. I'm using Swift 3.0, Xcode 8, and Firebase.
import UIKit
import Firebase
import SwiftKeychainWrapper
class LoginViewController: UIViewController {
#IBOutlet weak var emailField: UITextField!
#IBOutlet weak var pwField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(LoginViewController.dismissKeyboard))
//Uncomment the line below if you want the tap not not interfere and cancel other interactions.
//tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)
// Do any additional setup after loading the view.
}
func dismissKeyboard() {
//Causes the view (or one of its embedded text fields) to resign the first responder status.
view.endEditing(true)
}
#IBAction func loginPressed(_ sender: Any) {
guard emailField.text != "", pwField.text != "" else {return}
FIRAuth.auth()?.signIn(withEmail: emailField.text!, password: pwField.text!, completion: { (user, error) in
if let error = error {
print(error.localizedDescription)
}
if user != nil {
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "TabBarViewController")
self.present(vc, animated: true, completion: nil)
}
})
}
}
Below is my UsersViewController code it has the log-out button
import UIKit
import Firebase
class UsersViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableview: UITableView!
var user = [User]()
override func viewDidLoad() {
super.viewDidLoad()
retrieveUsers()
}
func retrieveUsers() {
let ref = FIRDatabase.database().reference()
ref.child("users").queryOrderedByKey().observeSingleEvent(of: .value, with: { snapshot in
let users = snapshot.value as! [String : AnyObject]
self.user.removeAll()
for (_, value) in users {
if let uid = value["uid"] as? String {
if uid != FIRAuth.auth()!.currentUser!.uid {
let userToShow = User()
if let fullName = value["full name"] as? String, let imagePath = value["urlToImage"] as? String {
userToShow.fullName = fullName
userToShow.imagePath = imagePath
userToShow.userID = uid
self.user.append(userToShow)
}
}
}
}
self.tableview.reloadData()
})
ref.removeAllObservers()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableview.dequeueReusableCell(withIdentifier: "userCell", for: indexPath) as! UserCell
cell.nameLabel.text = self.user[indexPath.row].fullName
cell.userID = self.user[indexPath.row].userID
cell.userImage.downloadImage(from: self.user[indexPath.row].imagePath!)
checkFollowing(indexPath: indexPath)
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return user.count
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let uid = FIRAuth.auth()!.currentUser!.uid
let ref = FIRDatabase.database().reference()
let key = ref.child("users").childByAutoId().key
var isFollower = false
ref.child("users").child(uid).child("following").queryOrderedByKey().observeSingleEvent(of: .value, with: { snapshot in
if let following = snapshot.value as? [String : AnyObject] {
for (ke, value) in following {
if value as! String == self.user[indexPath.row].userID {
isFollower = true
ref.child("users").child(uid).child("following/\(ke)").removeValue()
ref.child("users").child(self.user[indexPath.row].userID).child("followers/\(ke)").removeValue()
self.tableview.cellForRow(at: indexPath)?.accessoryType = .none
}
}
}
if !isFollower {
let following = ["following/\(key)" : self.user[indexPath.row].userID]
let followers = ["followers/\(key)" : uid]
ref.child("users").child(uid).updateChildValues(following as Any as! [AnyHashable : Any])
ref.child("users").child(self.user[indexPath.row].userID).updateChildValues(followers)
self.tableview.cellForRow(at: indexPath)?.accessoryType = .checkmark
}
})
ref.removeAllObservers()
}
func checkFollowing(indexPath: IndexPath) {
let uid = FIRAuth.auth()!.currentUser!.uid
let ref = FIRDatabase.database().reference()
ref.child("users").child(uid).child("following").queryOrderedByKey().observeSingleEvent(of: .value, with: { snapshot in
if let following = snapshot.value as? [String : AnyObject] {
for (_, value) in following {
if value as! String == self.user[indexPath.row].userID {
self.tableview.cellForRow(at: indexPath)?.accessoryType = .checkmark
}
}
}
})
ref.removeAllObservers()
}
#IBAction func logOutPressed(_ sender: Any) {
do {
try FIRAuth.auth()?.signOut()
if FIRAuth.auth()?.currentUser == nil {
// Remove User Session from device
UserDefaults.standard.removeObject(forKey: "uid")
UserDefaults.standard.synchronize()
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "LoginVC") as! LoginViewController
}
} catch let signOutError as NSError {
// handle logout error
}
}
}
extension UIImageView {
func downloadImage(from imgURL: String!) {
let url = URLRequest(url: URL(string: imgURL)!)
let task = URLSession.shared.dataTask(with: url) {
(data, response, error) in
if error != nil {
print(error!)
return
}
DispatchQueue.main.async {
self.image = UIImage(data: data!)
}
}
task.resume()
}
}
Firebase Auth can handle this for you. Like with Firebase Database, Auth works by setting up listeners. You can listen for an existing user in your App Delegate like so:
final class AppDelegate: UIResponder, UIApplicationDelegate {
private let auth = FIRAuth.auth()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
auth?.addStateDidChangeListener { [weak self] (_, user) in
if let user = user {
// user is already logged in
} else {
// user is not logged in
}
}
}
}
When the user is successfully logged in, put their UID in UserDefaults to store the session like so:
UserDefaults.standard.set(FIRAuth.auth()!.currentUser!.uid, forKey: "user_uid_key")
UserDefaults.standard.synchronize()
Then, whatever is the first View Controller that your apps loads, check for that key in the viewDidAppear() like this:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Check if the user is logged in
if UserDefaults.standard.object(forKey: "user_uid_key") != nil {
// send them to a new view controller or do whatever you want
}
}
Put the UserDefaults in the success block of the IBAction/Function where you register/login your user like shown below:
FIRAuth.auth()?.signIn(withEmail: email, password: password, completion: { (user, error) in
if user != nil {
// Login success
// Saves User UID to UserDefaults
UserDefaults.standard.set(FIRAuth.auth()!.currentUser!.uid, forKey: "USER_KEY_UID")
UserDefaults.standard.synchronize()
}
else {
// login error
})
Remove the UserDefault when the user logs out:
#IBAction func logoutButtonPressed(sender: UIButton!) {
do {
try FIRAuth.auth()?.signOut()
if FIRAuth.auth()?.currentUser == nil {
// Remove User Session from device
UserDefaults.standard.removeObject(forKey: "USER_KEY_UID")
UserDefaults.standard.synchronize()
let loginVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "LoginVC") as! LoginVC
present("TheVCYouWantToSendTheUserTo", animated: true, completion: nil)
}
} catch let signOutError as NSError {
// handle logout error
}
}
UPDATED:
You forgot to include UserDefaults in your Login func. This is why it gives you an error. Add this to you login IBAction.
#IBAction func loginPressed(_ sender: Any) {
guard emailField.text != "", pwField.text != "" else {return}
FIRAuth.auth()?.signIn(withEmail: emailField.text!, password: pwField.text!, completion: { (user, error) in
if let error = error {
print(error.localizedDescription)
// You forgot to save User UID to UserDefaults here...
UserDefaults.standard.set(FIRAuth.auth()!.currentUser!.uid, forKey: "uid")
UserDefaults.standard.synchronize()
}
if user != nil {
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "TabBarViewController")
self.present(vc, animated: true, completion: nil)
}
})
}

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

Resources