How to show alerts in Swift UIKit using MVVM, Firebase and Delegates - swift

I would like to know how can i implement a delegate or a function that helps me to show an alert in the ViewModel. Basically i need to validate two text fields and when the user writes a wrong password or a wrong email it should trigger an alert. The function to trigger this alert is inside of the ViewController and i need to create a new class (ViewModel) that helps me to validate the text fields and sign to firebase
Here is my code:
ViewController
import UIKit
import FirebaseAuth
class ViewController: UIViewController, UIWindowSceneDelegate {
//MARK: - #IBOutlets
#IBOutlet weak var emailTextField: UITextField!
#IBOutlet weak var passwordTextField: UITextField!
#IBOutlet weak var startButton: UIButton!
//MARK: - LifeCycle
override func viewDidLoad() {
super.viewDidLoad()
setDelegates()
startButton.isEnabled = false
}
//MARK: - Delegates
func setDelegates()->Void {
emailTextField.delegate = self
passwordTextField.delegate = self
}
// Function that i need to trigger in the ViewModel
fileprivate func showErrorMessage(_ errorMessage : String) {
let alertController = UIAlertController(title: "UPS!", message: errorMessage, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Aceptar", style: .default))
self.present(alertController, animated: true, completion: nil)
}
// Function to go to the next ViewController
fileprivate func navigateToHomeViewController() {
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let homeViewController = storyBoard.instantiateViewController(withIdentifier: "HomeViewController") as! HomeViewController
homeViewController.modalPresentationStyle = .fullScreen
self.present(homeViewController, animated: true, completion: nil)
}
// Function of the ViewModel all validations
fileprivate func validateUserLogin(_ error: Error?, _ result: AuthDataResult?) {
switch error {
case .some(let error as NSError) where error.code == AuthErrorCode.wrongPassword.rawValue:
// validate the text field and then show the alert, i need to this, but inside the ViewModel
self.showErrorMessage("Contraseña incorrecta")
case .some(let error as NSError) where error.code == AuthErrorCode.userNotFound.rawValue:
// validate the text field and then show the alert, i need to this, but inside the ViewModel
self.showErrorMessage("Correo incorrecto")
case .some(let error):
// validate the text field and then show the alert, i need to this, but inside the ViewModel
self.showErrorMessage("Login error: \(error.localizedDescription)")
self.hideActivityIndicatorView()
case .none:
if (result?.user) != nil {
navigateToHomeViewController()
}
}
}
#IBAction func startButtonAction(_ sender: Any) {
if let email = emailTextField.text, let password = passwordTextField.text{
// Function to sing in firebase, it would be inside the ViewModel
Auth.auth().signIn(withEmail: email, password: password){(result, error) in
self.validateUserLogin(error, result)
}
}
}
fileprivate func validateFields() -> Bool {
return (passwordTextField.text!.count >= 8) && (isValidEmail(emailTextField.text ?? ""))
}
fileprivate func updateView() {
if(validateFields()){
startButton.isEnabled = true
startButton.tintColor = UIColor.blue
}else{
startButton.isEnabled = false
startButton.tintColor = UIColor.gray
}
}
}
func isValidEmail(_ email: String) -> Bool {
let emailRegEx = "[A-Z0-9a-z._%+-]+#[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
let emailPred = NSPredicate(format:"SELF MATCHES %#", emailRegEx)
return emailPred.evaluate(with: email)
}
//MARK: - Text Field Delegate Methods
extension ViewController : UITextFieldDelegate{
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString
string: String) -> Bool {
if textField == self.emailTextField {
updateView()
} else if textField == self.passwordTextField {
updateView()
}
return true
}
}
StoryBoard

Here is the solution, thanks to Not Bad.
Model
import Foundation
struct User{
var email : String
var password : String
}
ViewModel
import Foundation
import FirebaseAuth
class AuthViewModel {
var delegate: AuthViewModelDelegate?
var user : User? = User(email: "", password: "")
func checkCredentials(user: User) {
Auth.auth().signIn(withEmail: user.email, password: user.password){(result, error) in
switch error {
case .some(let error as NSError) where error.code == AuthErrorCode.wrongPassword.rawValue:
self.delegate?.showError("Contraseña incorrecta")
self.delegate?.hideActivityIndicator()
case .some(let error as NSError) where error.code == AuthErrorCode.userNotFound.rawValue:
self.delegate?.showError("Correo incorrecto")
self.delegate?.hideActivityIndicator()
case .some(let error):
self.delegate?.showError("Login error: \(error.localizedDescription)")
self.delegate?.hideActivityIndicator()
case .none:
if (result?.user) != nil {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let homeViewController = storyboard.instantiateViewController(identifier: "HomeViewController")
self.delegate?.navigateToHomeViewController(homeViewController)
self.delegate?.hideActivityIndicator()
}
}
}
}
}
View
import UIKit
import FirebaseAuth
//MARK: - # protocols
protocol AuthViewModelDelegate {
func showError(_ message: String)
func hideActivityIndicator()
func navigateToHomeViewController(_ homeViewController: UIViewController)
}
class ViewController: UIViewController {
//MARK: - #IBOutlets
#IBOutlet weak var emailTextField: UITextField!
#IBOutlet weak var passwordTextField: UITextField!
#IBOutlet weak var startButton: UIButton!
//MARK: - # Variables
var activityIndicator:UIActivityIndicatorView!
var authViewModel : AuthViewModel = AuthViewModel()
//MARK: - # Life cycle
override func viewDidLoad() {
super.viewDidLoad()
setDelegates()
setupUI()
}
fileprivate func setupUI() {
setActivityIndicator()
startButton.isEnabled = false
}
func setDelegates()->Void {
authViewModel.delegate = self
emailTextField.delegate = self
passwordTextField.delegate = self
}
func setActivityIndicator()->Void{
activityIndicator = UIActivityIndicatorView(style: UIActivityIndicatorView.Style.medium)
activityIndicator.center = view.center
activityIndicator.isHidden = true
self.view.addSubview(activityIndicator)
}
func displayActivityIndicatorView() -> () {
self.view.isUserInteractionEnabled = false
self.view.bringSubviewToFront(self.activityIndicator)
self.activityIndicator.isHidden = false
self.activityIndicator.startAnimating()
}
#IBAction func startButtonAction(_ sender: Any) {
self.displayActivityIndicatorView()
guard let email = emailTextField.text else {return}
guard let password = passwordTextField.text else {return}
self.authViewModel.user?.email = email
self.authViewModel.user?.password = password
self.authViewModel.checkCredentials(user: self.authViewModel.user!)
}
fileprivate func validateFields() -> Bool {
return (passwordTextField.text!.count >= 8) && (isValidEmail(emailTextField.text ?? ""))
}
fileprivate func updateView() {
if(validateFields()){
startButton.isEnabled = true
}else{
startButton.isEnabled = false
}
}
}
func isValidEmail(_ email: String) -> Bool {
let emailRegEx = "[A-Z0-9a-z._%+-]+#[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
let emailPred = NSPredicate(format:"SELF MATCHES %#", emailRegEx)
return emailPred.evaluate(with: email)
}
//MARK: - Text Field Delegate Methods
extension ViewController : UITextFieldDelegate{
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString
string: String) -> Bool {
if textField == self.emailTextField {
updateView()
} else if textField == self.passwordTextField {
updateView()
}
return true
}
}
//MARK: - # AuthViewModel Delegate Methods
extension ViewController : AuthViewModelDelegate{
func hideActivityIndicator() {
DispatchQueue.main.async {
if !self.activityIndicator.isHidden{
DispatchQueue.main.async {
self.view.isUserInteractionEnabled = true
self.activityIndicator.stopAnimating()
self.activityIndicator.isHidden = true
}
}
}
}
func navigateToHomeViewController(_ homeViewController: UIViewController) {
(UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?.changeRootViewController(homeViewController)
}
func showError(_ message: String) {
DispatchQueue.main.async {
let alertController = UIAlertController(title: "UPS!", message: message, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Aceptar", style: .default))
self.present(alertController, animated: true, completion: nil)
}
}
}

Related

I can not see all items on my prototype cell.It seems like default cell but it is not

I want to make some blog or forum app.I made a home page to see other users's posts.I used tableview and prototype cell to do that.But when i run my app, i can not see all my items in my prototype cell.And they seem like default cells but they are not.I debugged it with changing cell's background color.I can see it but not completely true.
Here is my Home Page View Controller
import UIKit
import Parse
class FeedViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var postDizisi = [Post]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
verileriAl()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return postDizisi.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "protCell", for: indexPath) as! FeedTableViewCell
cell.textView.text = postDizisi[indexPath.row].content
cell.usernameLabel.text = postDizisi[indexPath.row].username
return cell
}
#objc func verileriAl() {
print("done")
let query = PFQuery(className: "Post")
query.addDescendingOrder("createdAt")
query.findObjectsInBackground { (objects, error) in
if error != nil {
self.createAlert(title: "HATA", message: error?.localizedDescription ?? "HATA")
}else {
if objects!.count > 0 {
self.postDizisi.removeAll(keepingCapacity: false)
for object in objects! {
if let username = object.object(forKey: "username") as? String {
if let userContent = object.object(forKey: "content") as? String {
let post = Post(username: username, content: userContent)
self.postDizisi.append(post)
}
}else {
print("hata aldık")
}
}
self.tableView.reloadData()
}
}
}
}
func createAlert(title : String , message : String) {
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertController.Style.alert)
let okBut
ton = UIAlertAction(title: "OK", style: UIAlertAction.Style.default)
alert.addAction(okButton)
present(alert, animated: true)
}
override func viewWillAppear(_ animated: Bool) {
NotificationCenter.default.addObserver(self, selector: #selector(verileriAl), name: NSNotification.Name(rawValue: "veriGirildi"), object: nil)
}
}
Here is my upload View Controller
import UIKit
import Parse
class UploadViewController: UIViewController {
#IBOutlet weak var shareButton: UIButton!
#IBOutlet weak var usernameLabel: UILabel!
#IBOutlet weak var postTextView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
usernameLabel.text = PFUser.current()?.username
}
#IBAction func shareButtonClicked(_ sender: Any) {
let post = PFObject(className: "Post")
if PFUser.current() != nil {
post["username"] = PFUser.current()?.username
post["content"] = postTextView.text
post.saveInBackground { (success, error) in
if error != nil {
let alert = UIAlertController(title: "HAT", message: error?.localizedDescription ?? "HATA", preferredStyle: UIAlertController.Style.alert)
let okButton = UIAlertAction(title: "OK", style: UIAlertAction.Style.default)
alert.addAction(okButton)
self.present(alert, animated: true)
}else {
self.postTextView.text = ""
self.tabBarController?.selectedIndex = 0
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "veriGirildi"), object: nil)
}
}
}
}
}
I want to see like this
But when i runned my app it seems like this
Atil and ibrahimmbyrrm is just usernameLabel contents.Why it doesn't display other items on prototype cell
I want to see other users's posts on Home Page but my cell doesn't work successfully.I need help to fix it.I have this problem at all my projects.

Sending a string from one viewcontroller to another using delegate in swift

I'm trying to make a button click in PopupViewController fill a textfield in DagbogsindlaegViewController with a string "svimmelhed, ".
I'm not getting any errors when running the code, but the text is not there when i run the app simulation and press "ButtonPressSvimmelhed" in the class PopupViewController.
Is there a kind stranger who can help me and tell me what i am doing wrong?
Code below:
import UIKit
import EventKit
class DagbogsindlaegViewController: UIViewController{
#IBAction func BackToSVC(_ sender: Any){
self.performSegue(withIdentifier: "BackToSVCSegue", sender: self)
}
#IBAction func ToCalenderButtonPress(_ sender: Any) {
self.performSegue(withIdentifier: "ToCalenderSegue", sender: self)
}
override func viewDidLoad() {
super.viewDidLoad()
TitleTextField.delegate = self
DescriptionTextField.delegate = self
// Do any additional setup after loading the view.
}
#IBOutlet weak var TitleTextField: UITextField!
#IBOutlet weak var DescriptionTextField: UITextField!
func addEventToCalendar(title: String, description: String?, startDate: Date, endDate: Date, completion: ((_ success: Bool, _ error: NSError?) -> Void)? = nil) {
let eventStore = EKEventStore()
eventStore.requestAccess(to: .event, completion: { (granted, error) in
if (granted) && (error == nil) {
let event = EKEvent(eventStore: eventStore)
event.title = title
event.startDate = startDate
event.endDate = endDate
event.notes = description
event.calendar = eventStore.defaultCalendarForNewEvents
do {
try eventStore.save(event, span: .thisEvent)
} catch let e as NSError {
completion?(false, e)
return
}
completion?(true, nil)
} else {
completion?(false, error as NSError?)
}
})
}
#IBAction func ButtonPressGemDagbog(_ sender: Any) {
addEventToCalendar(title: "\(String(describing: TitleTextField.text!))", description: "\(String(describing: DescriptionTextField.text!))", startDate: NSDate() as Date, endDate: NSDate() as Date)
}
#IBAction func ButtonPressPopupMenu(_ sender: Any) {
let popOverVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "PopupViewControllerID") as! PopupViewController
self.addChild(popOverVC)
popOverVC.view.frame = self.view.frame
self.view.addSubview(popOverVC.view)
popOverVC.didMove(toParent: self)
}
}
extension DagbogsindlaegViewController : UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
}
extension DagbogsindlaegViewController : TextFraPopupDelegate {
func Symptomer(Svimmelhed: String) {
TitleTextField.text = Svimmelhed
}
}
And the other view controller:
import UIKit
protocol TextFraPopupDelegate{
func Symptomer(Svimmelhed: String)
}
class PopupViewController: UIViewController {
var symptomDelegate: TextFraPopupDelegate?
override func viewDidLoad() {
super.viewDidLoad()
self.showAnimate()
self.view.backgroundColor = UIColor.black.withAlphaComponent(0.8)
// Do any additional setup after loading the view.
}
#IBAction func ButtonPressBackToDagbogsindlaeg(_ sender: Any) {
self.removeAnimate()
//self.view.removeFromSuperview()
}
func showAnimate()
{
self.view.transform = CGAffineTransform(scaleX: 1.3, y: 1.3)
self.view.alpha = 0.0;
UIView.animate(withDuration: 0.25, animations: {
self.view.alpha = 1.0
self.view.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)
});
}
func removeAnimate()
{
UIView.animate(withDuration: 0.25, animations: {
self.view.transform = CGAffineTransform(scaleX: 1.3, y: 1.3)
self.view.alpha = 0.0;
}, completion:{(finished : Bool) in
if (finished)
{
self.view.removeFromSuperview()
}
});
}
#IBAction func ButtonPressSvimmelhed(_ sender: UIButton) {
if sender.isSelected {
sender.isSelected = false
} else {
sender.isSelected = true
}
symptomDelegate?.Symptomer(Svimmelhed: "Svimmelhed, ")
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
}
}
As mentioned in the comments the delegate is not set.
However in Swift there is a more convenient way to pass the string back to the presenting view controller, a callback closure. You get rid of the protocol and of the duty to set the delegate
Delete
protocol TextFraPopupDelegate{
func Symptomer(Svimmelhed: String)
}
In PopupViewController replace
var symptomDelegate: TextFraPopupDelegate?
with
var callback : ((String) -> Void)?
and
#IBAction func ButtonPressSvimmelhed(_ sender: UIButton) {
if sender.isSelected {
sender.isSelected = false
} else {
sender.isSelected = true
}
symptomDelegate?.Symptomer(Svimmelhed: "Svimmelhed, ")
}
with
#IBAction func ButtonPressSvimmelhed(_ sender: UIButton) {
sender.isSelected.toggle()
callback?("Svimmelhed, ")
}
In DagbogsindlaegViewController in ButtonPressPopupMenu add the closure
#IBAction func ButtonPressPopupMenu(_ sender: Any) {
let popOverVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "PopupViewControllerID") as! PopupViewController
self.addChild(popOverVC)
popOverVC.view.frame = self.view.frame
popOverVC.callback = { string in
self.TitleTextField.text = string
}
self.view.addSubview(popOverVC.view)
popOverVC.didMove(toParent: self)
}
Finally delete
extension DagbogsindlaegViewController : TextFraPopupDelegate {
func Symptomer(Svimmelhed: String) {
TitleTextField.text = Svimmelhed
}
}
Notes:
The syntax (_ success: Bool, _ error: NSError?) -> Void)? is outdated. Since Swift 3 the parameter labels are gone, this is sufficient: (Bool, NSError?) -> Void)?
Don't use NS... classes in Swift if there are native counterparts for example Date
Please conform to the naming convention to name variables and functions / methods with a starting lowercase letter.
The syntax "\(String(describing: TitleTextField.text!))" is double redundant. You create a string from a string from a string. Replace it with TitleTextField.text!

how to filter data with multiple buttons using delegate?

The Delegate that I am using is used to filter out the specified category using a delegate when a button is pressed in the FilterVC
what im struggling with is setting up buttons in the FilterVC so that filter works in the HomeVC
ive noticed that issue might be in my FilterVC when using the delegate in the #IBAction func acceptSelections where im getting the error Cannot convert value of type 'RoundButton?' to expected argument type 'String?' when calling the buttons when using the delegate to control which category
import UIKit
import Firebase
import FirebaseFirestore
class HomeViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
#IBOutlet var activeFiltersStackView: UIStackView!
#IBOutlet var stackViewHeightConstraint: NSLayoutConstraint!
#IBOutlet var jewelryFilterLbl: UILabel!
#IBOutlet var hatFilterLbl: UILabel!
#IBOutlet var shoeFilterLbl: UILabel!
#IBOutlet var apparelFilterLbl: UILabel!
#IBOutlet var gearFilterLbl: UILabel!
private lazy var baseQuery: Query = {
return Firestore.firestore().collection("products").limit(to: 50)
}()
fileprivate var query: Query?
lazy private var filters: (navigationController: UINavigationController,
filtersController: FilterViewController) = {
return FilterViewController.fromStoryboard(delegate: self)
}()
#IBAction func didTapClearBtn(_ sender: Any){
filters.filtersController.clearFilters()
controller(filters.filtersController, didSelectCategory: nil, sativa: nil, indica: nil, hybrid: nil, gear: nil)
}
var productSetup: [ProductList] = []
var products: ProductList?
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
// arranges products by store nearest you
fetchProducts { (products) in
self.productSetup = products.sorted(by: { $0.itemName < $1.itemName })
self.productListTableView.reloadData()
}
}
// fetches Firebase Data
func fetchProducts(_ completion: #escaping ([ProductList]) -> Void) {
let productQuery = Firestore.firestore().collection("products").limit(to: 50)
productQuery.addSnapshotListener { (snapshot, error) in
guard error == nil, let snapshot = snapshot, !snapshot.isEmpty else {
return
}
completion(snapshot.documents.compactMap( {ProductList(dictionary: $0.data())} ))
}
// shows Firestore data in log (not neccessary code just used to be seen in logs)
productQuery.getDocuments { (snapshot, error) in
if let error = error {
print("Oh no! Got an error! \(error.localizedDescription)")
return
}
guard let snapshot = snapshot else { return }
let allDocuments = snapshot.documents
for productDocument in allDocuments {
print("I have this product \(productDocument.data())")
}
}
}
}
extension HomeViewController: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return productSetup.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "HomeCell") as?
HomeCell else { return UITableViewCell() }
cell.configure(withProduct: productSetup[indexPath.row])
return cell
}
}
extension HomeViewController: FiltersViewControllerDelegate{
func query(withCategory jewelry: String?, hat: String?, shoe: String?, gear: String?, apparel: String?) -> Query {
if jewelry == nil && hat == nil && shoe == nil && gear == nil && apparel == nil {
stackViewHeightConstraint.constant = 0
activeFiltersStackView.isHidden = true
} else {
stackViewHeightConstraint.constant = 44
activeFiltersStackView.isHidden = false
}
var filtered = baseQuery
// Sort and Filter data
if let jewelry = jewelry, !jewelry.isEmpty {
filtered = filtered.whereField("category", isEqualTo: jewelry)
}
if let hat = hat, ! hat.isEmpty {
filtered = filtered.whereField("category", isEqualTo: hat)
}
if let shoe = shoe, !shoe.isEmpty {
filtered = filtered.whereField("category", isEqualTo: shoe)
}
if let gear = gear, !gear.isEmpty {
filtered = filtered.whereField("category", isEqualTo: gear)
}
if let apparel = apparel, !apparel.isEmpty {
filtered = filtered.whereField("category", isEqualTo: apparel)
}
return filtered
}
func controller(_ controller: FilterViewController,
didSelectCategory jewelry: String?,
hat: String?,
shoe: String?,
gear: String?,
apparel: String?) {
if jewelry == nil && hat == nil && shoe == nil && gear == nil && apparel == nil {
stackViewHeightConstraint.constant = 0
activeFiltersStackView.isHidden = true
} else {
stackViewHeightConstraint.constant = 44
activeFiltersStackView.isHidden = false
}
let filtered = query(withCategory: jewelry, hat: hat, shoe: shoe, gear: gear, apparel: apparel)
if let jewelry = jewelry, ! jewelry.isEmpty {
jewelryFilterLbl.text = jewelry
jewelryFilterLbl.isHidden = false
} else {
jewelryFilterLbl.isHidden = true
}
if let hat = hat, ! hat.isEmpty {
hatFilterLbl.text = hat
hatFilterLbl.isHidden = false
} else {
hatFilterLbl.isHidden = true
}
if let shoe = shoe, ! shoe.isEmpty {
shoeFilterLbl.text = shoe
shoeFilterLbl.isHidden = false
} else {
shoeFilterLbl.isHidden = true
}
if let gear = gear, !gear.isEmpty {
gearFilterLbl.text = gear
gearFilterLbl.isHidden = false
} else {
gearFilterLbl.isHidden = true
}
if let apparel = apparel, ! apparel.isEmpty {
apparelFilterLbl.text = apparel
apparelFilterLbl.isHidden = false
} else {
apparelFilterLbl.isHidden = true
}
query = filtered
}
}
import UIKit
import Firebase
protocol FiltersViewControllerDelegate: NSObjectProtocol {
func controller(_ controller: FilterViewController,
didSelectCategory jewelry: String?,
hat: String?,
shoe: String?,
gear: String?,
apparel: String?)
}
class FilterViewController: UIViewController {
#IBOutlet weak var jewelryBtn: RoundButton!
#IBOutlet weak var hatBtn: RoundButton!
#IBOutlet weak var shoeBtn: RoundButton!
#IBOutlet weak var gearBtn: RoundButton!
#IBOutlet weak var apparelBtn: RoundButton!
static func fromStoryboard(delegate: FiltersViewControllerDelegate? = nil) ->
(navigationController: UINavigationController, filtersController: FilterViewController) {
let navController = UIStoryboard(name: "Main", bundle: nil)
.instantiateViewController(withIdentifier: "FiltersViewController")
as! UINavigationController
let controller = navController.viewControllers[0] as! FilterViewController
controller.delegate = delegate
return (navigationController: navController, filtersController: controller)
}
weak var delegate: FiltersViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func filterSelect(_ sender: Any) {
if let button : UIButton = sender as? UIButton
{
button.isSelected = !button.isSelected
if (button.isSelected)
{
button.backgroundColor = .green
}
else
{
button.backgroundColor = .gray
}
}
}
func clearFilters() {
apparelBtn.isSelected = false
jewelryBtn.isSelected = false
shoeBtn.isSelected = false
hatBtn.isSelected = false
gearBtn.isSelected = false
}
#IBAction func closeFilter(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
#IBAction func acceptSelections(_ sender: Any) {
delegate?.controller(self, //Problem integrating the buttons to get the correct category
didSelectCategory: jewelryBtn,
hat: hatBtn,
shoe: shoeBtn,
gear: gearBtn,
apparel: apparelBtn)
dismiss(animated: true)
}
}
As the filter functionality is pure boolean I recommend to just return the isSelected values of the buttons
protocol FiltersViewControllerDelegate: NSObjectProtocol {
func controller(_ controller: FilterViewController,
didSelectCategory jewelry: Bool,
hat: Bool,
shoe: Bool,
gear: Bool,
apparel: Bool)
}
And call it
#IBAction func acceptSelections(_ sender: Any) {
delegate?.controller(self,
didSelectCategory: jewelryBtn.isSelected,
hat: hatBtn.isSelected,
shoe: shoeBtn.isSelected,
gear: gearBtn.isSelected,
apparel: apparelBtn.isSelected)
dismiss(animated: true)
}
It seems to be a multiple choice selection so you have to combine the options in the query.

Swift: Retrieve data from Firebase and display in table view

I am making an app which has a news feed of images (HomeViewController). The user can tap on each image which segues to the SiteViewController which has a table view of empty data and a button that when clicked segues to ContextSheetViewController where the user can upload data of the image that they clicked on in the news feed. The user then presses upload and this data (siteCodeTextView, areaCodeTextView, trenchTextView) is saved to firebase and it dismisses back to the SiteViewController. I then want to retrieve the data value of siteCodeTextView that has just been uploaded of the image in the table view of the SiteViewController. But when I press upload in the ContextSheetViewController the error: Unexpectedly found nil while unwrapping an Optional value occurs. And my sheetId is printing nil in SiteViewController so I am not sure how to retrieve it correctly? Any help?
Here is my storyboard of relevant view controllers:
Code for SiteViewController:
class SiteViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var sheets = [Sheet]()
var users = [User]()
var sheetId: String!
var postId: String!
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
loadSheets()
print(postId)
print(sheetId)
}
func loadSheets() {
Api.Sheet.REF_SHEETS.child(self.postId!).observe(.childAdded, with: {
snapshot in
Api.Sheet.observeSheets(withSheetId: snapshot.key, completion: {
sheet in
// self.fetchUser(uid: sheet.uid!, completed: {
print("sheet id: \(sheet.id)")
print("sheet uid: \(sheet.uid)")
self.sheets.append(sheet)
self.tableView.reloadData()
// })
})
})
}
func fetchUser(uid: String, completed: #escaping () -> Void ) {
Api.User.observeUser(withId: uid, completion: {
user in
self.users.append(user)
completed()
})
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "SheetSegue" {
let sheetVC = segue.destination as! SheetViewController
let sheetId = sender as! String
sheetVC.sheetId = sheetId
}
}
}
extension SiteViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sheets.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SheetCell", for: indexPath) as! SiteTableViewCell
let sheet = sheets[indexPath.row]
print("sheet id: \(sheet.id)")
print("sheet uid: \(sheet.uid)")
// let user = users[indexPath.row]
// cell.user = user
cell.sheet = sheet
cell.delegate = self
return cell
}
}
extension SiteViewController: SiteTableViewCellDelegate {
func goToSheetVC(sheetId: String) {
performSegue(withIdentifier: "SheetSegue", sender: sheetId)
}
}
Code for ContextSheetViewController:
class ContextSheetViewController: UIViewController {
#IBOutlet weak var siteCodeTextView: UITextField!
#IBOutlet weak var areaCodeTextView: UITextField!
#IBOutlet weak var trenchTextView: UITextField!
#IBOutlet weak var uploadArtefactImage: UIImageView!
#IBOutlet weak var artefactImageView: UIImageView!
var selectedImage: UIImage?
var postId: String!
override func viewDidLoad() {
super.viewDidLoad()
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.handleSelectPhoto))
uploadArtefactImage.addGestureRecognizer(tapGesture)
uploadArtefactImage.isUserInteractionEnabled = true
}
#objc func handleSelectPhoto() {
let pickerController = UIImagePickerController()
pickerController.delegate = self
present(pickerController, animated: true, completion: nil)
}
#IBAction func uploadButton_TouchUpInside(_sender: Any) {
if let profileImg = self.artefactImageView.image, let imageData = UIImageJPEGRepresentation(profileImg, 0.1) {
let photoIdString = NSUUID().uuidString
print(photoIdString)
let storageRef = Storage.storage().reference(forURL: Config.STORAGE_ROOF_REF).child("sheets").child(photoIdString)
storageRef.putData(imageData, 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("Sheet Image can not be empty!")
}
}
func sendDataToDatabase(photoUrl: String) {
// let ref = Database.database().reference()
let sheetsReference = Api.Sheet.REF_SHEETS
// let sheetsReference = ref.child("sheets")
let newSheetId = sheetsReference.childByAutoId().key
let newSheetReference = sheetsReference.child(newSheetId)
guard let currentUser = Auth.auth().currentUser else {
return
}
let currentUserId = currentUser.uid
newSheetReference.setValue(["uid": currentUserId, "photoUrl": photoUrl, "siteCodeTextView": siteCodeTextView.text!, "areaCodeTextView": areaCodeTextView.text!, "trenchTextView": trenchTextView.text!], withCompletionBlock: {
(error, ref) in
if error != nil {
ProgressHUD.showError(error!.localizedDescription)
return
}
let postSheetRef = Api.Sheet.REF_SHEETS.child(self.postId!).child(newSheetId)
// let postSheetRef = Api.Sheet.REF_SHEETS.child("post-sheets").child(self.postId).child(newSheetId)
postSheetRef.setValue(true, withCompletionBlock: { (error, ref) in
if error != nil {
ProgressHUD.showError(error!.localizedDescription)
return
}
})
ProgressHUD.showSuccess("Success")
self.clean()
self.navigationController?.popViewController(animated: true)
})
}
func clean() {
self.siteCodeTextView.text = ""
self.uploadArtefactImage.image = UIImage(named: "upload")
self.artefactImageView.image = UIImage(named: "image")
}
}
extension ContextSheetViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
print("did Finish Picking Media")
if let image = info["UIImagePickerControllerOriginalImage"] as? UIImage{
artefactImageView.image = image
// selectedImage = image
// uploadArtefactImage.image = image
}
dismiss(animated: true, completion: nil)
}
}
Code for SiteTableViewCell:
protocol SiteTableViewCellDelegate {
func goToSheetVC(sheetId: String)
}
class SiteTableViewCell: UITableViewCell {
#IBOutlet weak var profileImageView: UIImageView!
#IBOutlet weak var siteSheetLabel: UILabel!
#IBOutlet weak var nameLabel: UILabel!
var delegate: SiteTableViewCellDelegate?
var sheet: Sheet? {
didSet {
updateView()
}
}
var user: User? {
didSet {
setupUserInfo()
}
}
func updateView() {
siteSheetLabel.text = sheet?.siteCodeTextView
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
func setupUserInfo() {
nameLabel.text = user?.username
if let photoUrlString = user?.profileImageUrl {
let photoUrl = URL(string: photoUrlString)
profileImageView.sd_setImage(with: photoUrl, placeholderImage: UIImage(named: "placeholderImg"))
}
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.siteSheetLabel_TouchUpInside))
siteSheetLabel.addGestureRecognizer(tapGesture)
siteSheetLabel.isUserInteractionEnabled = true
}
#objc func siteSheetLabel_TouchUpInside() {
print(sheet?.id)
if let id = sheet?.id{
delegate?.goToSheetVC(sheetId: id)
}
}
override func prepareForReuse() {
super.prepareForReuse()
profileImageView.image = UIImage(named: "placeholderImg")
}
}
From what you have shown the culprit is the postId, you are using it to fetch data from Firebase and yet you haven't shown anywhere what its value is. When the user taps on an image, the postId is not transfered to the SiteViewController.
In the SiteViewController remove the ! and replace it with ?, put an initializer that will take the postID as a parameter.
var postId:String?
func initPost(forImage postId: String) {
self.postId=postId
}
In the previous news feed VC inside the segue or didSelectForRow(i don't know what you use for transition, initialize the SiteViewController, so when it is presented it knows which ID to retrieve data for.
Another thing that needs mentioning is that you are using observe but you are not removing the observers.
EDIT: this answer was based on me not knowing what your HomeVC looked like.
if segue.identifier == "SiteSegue" {
let siteVC = segue.destination as! SiteViewController
let postId = sender as! String
siteVC.postId = postId
}

Swift Error: EXC BAD INSTRUCTION code=EXCI386 for userid

I am writing an app login page when I countered this error. I do not know how to debug it so I am asking where I coded wrong. I got a problem on the keychain wrapper code:
KeychainWrapper.standard.set((user?.uid)!,forKey: "uid")
I am following a YouTube tutorial on Swift 4, the most updated version. However, it seems this line of code has some problems, therefore I would like to know the problem in the whole code, whether I missed I bracket or failed to import.
import UIKit
import Firebase
import FirebaseAuth
import FirebaseDatabase
import FirebaseCore
import SwiftKeychainWrapper
import FirebaseStorage
class ViewController: UIViewController {
#IBOutlet weak var usernameField: UITextField!
#IBOutlet weak var emailField: UITextField!
#IBOutlet weak var passwordField: UITextField!
#IBOutlet weak var userimageview: UIImageView!
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 KeychainWrapper.standard.object(forKey: "KEY_UID") != nil {
self.performSegue(withIdentifier: "toFeed", sender: nil)
}
}
func storeuserdata(userId: String) {
if let imageData = UIImageJPEGRepresentation(selectedImage, 0.2) {
Storage.storage().reference().putData(imageData, metadata: nil) { (metadata, error) in
guard let metadata = metadata else {
// Uh-oh, an error occurred!
return
}
// Metadata contains file metadata such as size, content-type, and download URL.
let downloadURL = metadata.downloadURL
Database.database().reference().child("users").child(userId).setValue(["username": self.usernameField.text!,"userImg": downloadURL
])
}
}
}
#IBAction func signinpressed(_ sender: Any) {
if let email = emailField.text, let password = passwordField.text {
Auth.auth().createUser(withEmail: email, password: password) { (user, error) in
if error != nil && (self.usernameField.text?.isEmpty)! && self.userimageview.image != nil {
Auth.auth().createUser(withEmail: email, password: password) { (user, error) in
self.storeuserdata(userId: (user?.uid)!)
KeychainWrapper.standard.set((user?.uid)!,forKey: "uid")
self.performSegue(withIdentifier: "toFeed", sender: nil)
}
}else {
KeychainWrapper.standard.set((user?.uid)!,forKey: "uid")
self.performSegue(withIdentifier: "toFeed", sender: nil)
}
}
}
}
#IBAction func getPhoto (_ sender: AnyObject) {
present(imagePicker, animated: true, completion: nil)
}
}
extension ViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate{
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
if let image = info[UIImagePickerControllerEditedImage] as? UIImage {
selectedImage = image
}
imagePicker.dismiss(animated: true, completion: nil)
}
}