How to call present code inside a struct in Swift - ios

I try to alert when something went wrong but i try to create alert i get error like this :
Instance member 'present' of type 'UIViewController' cannot be used on instance of nested type 'SignUpViewController.FirebaseServices'
How can i alert my users without this my way? Or any solution about my problem? I think my problem is about Struct. Thank you!
import UIKit
import FirebaseAuth
import FirebaseFirestore
import FirebaseStorage
extension SignUpViewController {
struct SignUpModelFirebase {
let emailText : String
let passwordText : String
let nameText : String
let usernameText : String
let profileImage : UIImage
}
struct FirebaseServices {
static func createUser(user : SignUpModelFirebase, completion: #escaping(Error?) -> Void) {
let storage = Storage.storage()
let storageReference = storage.reference()
let mediaFolder = storageReference.child("Profile Image")
if let data = user.profileImage.jpegData(compressionQuality: 0.5) {
let uuidImg = UUID().uuidString
let imageReference = mediaFolder.child("\(uuidImg)")
imageReference.putData(data) { storageMetaData, error in
if let error = error {
let alert = UIAlertController(title: "Error", message: error.localizedDescription, preferredStyle: UIAlertController.Style.alert)
let action = UIAlertAction(title: "OK!", style: UIAlertAction.Style.default)
alert.addAction(action)
present(alert, animated: true)
print(error.localizedDescription)
}else {
imageReference.downloadURL { url, error in
if let error = error {
print(error.localizedDescription)
}else {
guard let imageURL = url?.absoluteString else {return}
print(imageURL)
Auth.auth().createUser(withEmail: user.emailText, password: user.passwordText) { data, error in
guard let uid = data?.user.uid else {return}
let data = [
"email" : user.emailText,
"username" : user.usernameText,
"name" : user.nameText,
"profileImageUrl" : imageURL,
"uid" : uid
] as [String : Any]
let firestoreDatabase = Firestore.firestore()
firestoreDatabase.collection("Users").document(uid).setData(data,completion: completion)
}
}
}
}
}
}
}
}
}

You are going about this all wrong. Your FirebaseServices struct should not be performing any user interface actions. It should only perform some logic and get data or return an error.
It is the caller (such as a view controller) of FirebaseServices that should handle the resulting data or error and update the UI as needed.
Remove the alert code from FirebaseServices. Simply pass the error back through the createUser completion block to the caller. Let the caller determine how to handle the error. That may or may not be the display of an alert.

Related

How to access AnyHashable Data in Swift Firebase remote Notification

I am attempting to access a certain data tag and show an alert if it is not null in the following remote notification:
[AnyHashable("google.c.sender.id"): ************, AnyHashable("google.c.fid"): asdfjkl1234556, AnyHashable("aps"): {
alert = {
body = "Shipment is no longer available and has been removed from the app.
title = "Shipment ****** no longer available";
};
},
AnyHashable("gcm.message_id"): 1234567891234567,
AnyHashable("google.c.a.e"): 1,
AnyHashable("shipmentMessage"): ****** is no longer available and has been removed form the app.]
AnyHashable("shipmentMessage"): ****** is no longer available and has been removed form the app is what I am trying to access. I believe my code should not be calling this null:
if UIApplication.shared.applicationState == .active{
print("ACTIVE< CHECK > SHIPMENT MESSAGE : : : : : : \(String(describing: userInfo["shipmentMessage"] as? [AnyHashable:Any]))")
guard let arrAPS = userInfo["aps"] as? [String: Any] else { return }
guard let arrAlert = arrAPS["alert"] as? [String:Any] else { return }
if (userInfo["shipmentMessage"] as? [AnyHashable:Any]) != nil {
print("***********NOT NULL***************")
let strTitle:String = arrAlert["title"] as? String ?? ""
let strBody:String = arrAlert["body"] as? String ?? ""
let alert = UIAlertController(title: strTitle, message: strBody, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default) { action in
print("OK Action")
})
self.window?.rootViewController?.present(alert, animated: true)
} else {
print("The shipmentMessage was null")
}
}
Is it the way I am iterating to the shipment message? Any help would be appreciated
Just replace AnyHashable to string like this (userInfo["shipmentMessage"] as? [String:Any])
or copy paste this below code -
if (userInfo["shipmentMessage"] as? [String:Any]) != nil {
print("***********NOT NULL***************")
let strTitle:String = arrAlert["title"] as? String ?? ""
let strBody:String = arrAlert["body"] as? String ?? ""
let alert = UIAlertController(title: strTitle, message: strBody, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default) { action in
print("OK Action")
})
self.window?.rootViewController?.present(alert, animated: true)
} else {
print("The shipmentMessage was null")
}

Other Function get value but not the other [duplicate]

This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed 3 years ago.
Within my function to load users I'm able to retrieve a value. However, when I want to assign it to my variable outside the function it has nothing, as shown in the login function.
Load User Function
func loadUser(userid: String) -> User {
//print(userid)
let userid = "56ldZFJiv0dpfaABzo78"
var user = User()
let docRef = db.collection("users").document(userid)
docRef.getDocument { (document, error) in
if let document = document {
let first = document.data()!["first"] as! String
let last = document.data()!["last"] as! String
let position = document.data()!["position"] as! String
let company = document.data()!["company"] as! String
let email = document.data()!["email"] as! String
let address = document.data()!["address"] as! String
let userID = document.data()!["userID"] as! String
//Initalize user
user = User(userID: userID,
firstName: first,
lastName: last,
company: company,
address: address,
position: position,
email: email)
print(user.position)
} else {
print("Document does not exist")
}
}
return user
}
Login Function
//MARK: LOGIN
func login() {
Auth.auth().signIn(withEmail: emailField.text!, password: passwordField.text!) { (user, error) in
if error == nil{
//self.performSegue(withIdentifier: "loginToAdmin", sender: self)
//Load user
let loggedOnUser = self.loadUser(userid: Auth.auth().currentUser!.uid)
print(loggedOnUser.userID)
// let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
// let chatViewController = storyBoard.instantiateViewController(withIdentifier: "chatVC") as! UINavigationController
// self.present(chatViewController, animated: true, completion: nil)
}
else {
DispatchQueue.main.async{
//Display Alert Message if login failed
let alertController = UIAlertController(title: "Error", message: error?.localizedDescription, preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "OK", style: .cancel, handler: nil)
alertController.addAction(defaultAction)
self.present(alertController, animated: true, completion: nil)
}
}
}
}
For the first function, I get a position value, as stated in the print statement.
For the second function, my variable, "loggedOnUser" is empty.
You need a completion as loadUser is asynchronous
func loadUser(userid: String,completion:#escaping(User?) ->()) {
//print(userid)
let userid = "56ldZFJiv0dpfaABzo78"
var user = User()
let docRef = db.collection("users").document(userid)
docRef.getDocument { (document, error) in
if let document = document {
let first = document.data()!["first"] as! String
let last = document.data()!["last"] as! String
let position = document.data()!["position"] as! String
let company = document.data()!["company"] as! String
let email = document.data()!["email"] as! String
let address = document.data()!["address"] as! String
let userID = document.data()!["userID"] as! String
//Initalize user
user = User(userID: userID,
firstName: first,
lastName: last,
company: company,
address: address,
position: position,
email: email)
print(user.position)
completion(user)
} else {
print("Document does not exist")
completion(nil)
}
}
}
Call
self.loadUser(userid: Auth.auth().currentUser!.uid) { res in
if let user = res {
print(user)
}
}

Firebase Inserting data into Database three times instead of once

For some reason when I am adding data to Firebase database through a form on my app it saves the data to the database but three times instead of just once as it's supposed to.
I can't quite figure out why because I have used this code before and it has worked fine...
Code:
#IBAction func createPostTapped(_ sender: UIButton) {
if let uid = Auth.auth().currentUser?.uid {
Database.database().reference().child("users").child(uid).observeSingleEvent(of: .value, with: {
(snapshot) in
if let userDictionary = snapshot.value as? [String: AnyObject] {
for user in userDictionary {
if let username = user.value as? String {
if let game = self.gameTextField.text {
if let activity = self.activityTextField.text {
if let console = self.consoleTextField.text {
if let skill = self.skillTextField.text {
if let communication = self.communicationTextField.text {
if let lfglfm = self.lfglfmTextField.text {
if let description = self.descriptionTextView.text {
let postObject: Dictionary<String, Any> = [
"uid" : uid,
"username" : username,
"game" : game,
"activity" : activity,
"console" : console,
"skill" : skill,
"communication" : communication,
"lfglfm" : lfglfm,
"description" : description
]
Database.database().reference().child("posts").childByAutoId().setValue(postObject)
let alert = UIAlertController(title: "Success!", message: "Your post was added successfully.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action) in
//code will run when ok button is pressed
let vc = self.storyboard?.instantiateViewController(withIdentifier: "LoggedInVC")
self.present(vc!, animated: true, completion: nil)
}))
self.present(alert, animated: true, completion: nil)
}
}
}
}
}
}
}
}
}
}
})
}
}
If anyone has any idea why my code would be posting the data three times instead of once I would appreciate the help.
Thank you!
I am guessing it is this line of code:
for user in userDictionary {
Looks like you have 3 entries inside that node so
Database.database().reference().child("posts").childByAutoId().setValue(postObject)
actually executes 3 times.

forEach Loop skips the last item

This forEach loop works sometimes and sometimes it skips. I am not sure what I am doing wrong here. The loop will skip the last item and will never exit. So the completion block does not get fired at all.
I am using firebase, Eureka forms and it's ImageRow extension.
I would appreciate some help here.
//MARK: - Get Form Values
var returnedValues: [String: Any] = [:]
fileprivate func getFormValues(values: [String: Any], completion: #escaping ([String:Any])->()) {
if let name = values["name"] as? String,
let description = values["description"] as? String,
let images = values["images"] as? [UIImage],
let category = values["category"] as? String,
let price = values["price"] as? Double,
let deliveryFee = values["deliveryFee"] as? Double,
let deliveryAreas = values["deliveryArea"] as? Set<String>,
let deliveryTime = values["deliveryTime"] as? String {
guard let uid = Auth.auth().currentUser?.uid else { return }
var imagesData = [[String: Any]]()
var counter = 0
images.forEach({ (image) in
let imageName = NSUUID().uuidString
let productImageStorageRef = Storage.storage().reference().child("product_images").child(uid).child("\(imageName).jpg")
var resizedImage = UIImage()
if image.size.width > 800 {
resizedImage = image.resizeWithWidth(width: 800)!
}
if let uploadData = UIImageJPEGRepresentation(resizedImage, 0.5) {
productImageStorageRef.putData(uploadData, metadata: nil, completion: { (metadata, error) in
if error != nil {
print("Failed to upload image: \(error?.localizedDescription ?? "")")
return
}
//Successfully uploaded product Image
print("Successfully uploaded product Image")
if let productImageUrl = metadata?.downloadURL()?.absoluteString {
counter += 1
let imageData: [String: Any] = [imageName: productImageUrl]
imagesData.append(imageData)
if counter == images.count {
let deliveryAreasArr = Array(deliveryAreas)
self.returnedValues = ["name": name, "description": description, "images": imagesData , "category": category, "price": price, "deliveryFee": deliveryFee, "deliveryArea": deliveryAreasArr, "deliveryTime": deliveryTime, "creationDate": Date().timeIntervalSince1970, "userId": uid]
completion(self.returnedValues)
}
}
})
}
})
} else {
let alert = UIAlertController(title: "Missing Information", message: "All fields are required. Please fill all fields.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { (_) in
alert.dismiss(animated: true, completion: nil)
}))
UIActivityIndicatorView.stopActivityIndicator(indicator: self.activityIndicator, container: self.activityIndicatorContainer, loadingView: self.activityIndicatorLoadingView)
self.present(alert, animated: true, completion: nil)
}
}
There are a number of if statements inside your for loop that can result in counter not being incremented. If any of these fail then you will never call the completion handler.
I understand that you are using the counter in an attempt to know when all of the asynchronous tasks are complete, but a dispatch group is a better solution for this.
It is also important that your completion handler is called in all paths; such as when the initial guard fails or in the else clause of the initial if - Your completion handler should probably accept an Error parameter so that it knows that there was a problem.
//MARK: - Get Form Values
fileprivate func getFormValues(values: [String: Any], completion: #escaping ([String:Any]?)->()) {
var returnedValues: [String: Any] = [:]
if let name = values["name"] as? String,
let description = values["description"] as? String,
let images = values["images"] as? [UIImage],
let category = values["category"] as? String,
let price = values["price"] as? Double,
let deliveryFee = values["deliveryFee"] as? Double,
let deliveryAreas = values["deliveryArea"] as? Set<String>,
let deliveryTime = values["deliveryTime"] as? String {
guard let uid = Auth.auth().currentUser?.uid else {
completion(nil)
return
}
var imagesData = [[String: Any]]()
let dispatchGroup = DispatchGroup() // Create a Dispatch Group
images.forEach({ (image) in
let imageName = NSUUID().uuidString
let productImageStorageRef = Storage.storage().reference().child("product_images").child(uid).child("\(imageName).jpg")
var resizedImage = UIImage()
if image.size.width > 800 {
resizedImage = image.resizeWithWidth(width: 800)!
}
if let uploadData = UIImageJPEGRepresentation(resizedImage, 0.5) {
dispatchGroup.enter() // Enter the group
productImageStorageRef.putData(uploadData, metadata: nil, completion: { (metadata, error) in
guard error == nil else {
print("Failed to upload image: \(error?.localizedDescription ?? "")")
dispatchGroup.leave() // Leave the dispatch group if there was an error
return
}
//Successfully uploaded product Image
print("Successfully uploaded product Image")
if let productImageUrl = metadata?.downloadURL()?.absoluteString {
let imageData: [String: Any] = [imageName: productImageUrl]
imagesData.append(imageData)
}
dispatchGroup.leave() // Leave the dispatch group in normal circumstances
})
}
})
// Schedule a notify closure for execution when the dispatch group is empty
dispatchGroup.notify(queue: .main) {
let deliveryAreasArr = Array(deliveryAreas)
returnedValues = ["name": name, "description": description, "images": imagesData , "category": category, "price": price, "deliveryFee": deliveryFee, "deliveryArea": deliveryAreasArr, "deliveryTime": deliveryTime, "creationDate": Date().timeIntervalSince1970, "userId": uid]
completion(self.returnedValues)
}
} else {
completion(nil)
let alert = UIAlertController(title: "Missing Information", message: "All fields are required. Please fill all fields.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { (_) in
alert.dismiss(animated: true, completion: nil)
}))
UIActivityIndicatorView.stopActivityIndicator(indicator: self.activityIndicator, container: self.activityIndicatorContainer, loadingView: self.activityIndicatorLoadingView)
self.present(alert, animated: true, completion: nil)
}
}
Some other points:
It would be better to pass structs rather than dictionaries. Using a struct for your input would get rid of that massive if let at the start of your function since you would know the types of the values and by making them non-optional properties of the struct you would know that the values were present.
It is unusual for a function such as this to present an alert; it would normally just return an error via the completion or perhaps throw back to the caller to indicate that there was a problem and let the caller handle it
I don't see why imagesData needs to be an array of dictionaries. Each dictionary in the array only has one entry, so you could just use a dictionary of [String:String] (There is no need to use Any when you know what the type will be.

Xcode 8, Swift 3. Value of type DataService has no member "Sign up"

I am trying to make a Sign in / Sign up screen with Firebase. And i created a swift class for send data to Firebase and i called the class "DataService". I have also Sign in and Sign up classes. In the "DataService" class i cant create more than one function, i am getting error like Value of type DataService has no member "Sign up" when i am trying to create a sign up function. But the other function works fine. I can have just only one function in this class(DataService).
DATASERVICE class
import Foundation
import Firebase
import FirebaseAuth
import FirebaseStorage
let rootRef = FIRDatabase.database().reference()
class DataService {
static let dataService = DataService()
private var _BASE_REF = rootRef
private var _PHOTO_REF = rootRef.child("photos")
var BASE_REF: FIRDatabaseReference {
return _BASE_REF
}
var PHOTO_REF: FIRDatabaseReference {
return _PHOTO_REF
}
var storageRef: FIRStorageReference{
return FIRStorage.storage().reference()
}
var fileUrl: String!
// Share Photo Data
func shareNewPhoto(user: FIRUser, caption: String, data: NSData) {
let filePath = "\(user.uid)/\ (Int(NSDate.timeIntervalSinceReferenceDate))"
let metaData = FIRStorageMetadata()
metaData.contentType = "image/jpg"
storageRef.child(filePath).put(data as Data, metadata: metaData) { (metadata, error) in
if let error = error {
print("Error uploading: /\(error.localizedDescription)")
}
// Create a Url for data ( Story Photo)
self.fileUrl = metadata!.downloadURLs![0].absoluteString
if let user = FIRAuth.auth()?.currentUser {
let idPhotoRoom = self.BASE_REF.child("PhotoRooms").childByAutoId()
idPhotoRoom.setValue(["caption": caption, "StoryPhotoUrlFromStorage": self.storageRef.child(metadata!.path!).description, "fileUrl": self.fileUrl])
}
}
// Story Photo (upload and dowload from server)
func fetchDataFromServer(callback:#escaping (StoryPhoto) -> ()) {
DataService.dataService._PHOTO_REF.observe(.childAdded, with: { (snapshot) in
let photo = StoryPhoto(key: snapshot.key, snapshot: snapshot.value as! Dictionary<String, AnyObject>)
callback(photo)
})
// Sign Up
func signUp(username: String, email: String, password: String, data: NSData) {
FIRAuth.auth()?.createUser(withEmail: email, password: password, completion: { (user, error) in
if let error = error {
print(error.localizedDescription)
return
}
let changeRequest = user?.profileChangeRequest()
changeRequest?.displayName = username
changeRequest?.commitChanges(completion: { (error) in
if let error = error{
print(error.localizedDescription)
return
}
})
let filePath = "profileimage/\(user!.uid)"
let metaData = FIRStorageMetadata()
metaData.contentType = "image/jpeg"
self.storageRef.child(filePath).put(data as Data, metadata: metaData, completion: { (metadata, error) in
if let error = error {
print("\(error.localizedDescription)")
return
}
self .fileUrl = metadata?.downloadURLs![0].absoluteString
let changeRequestPhoto = user!.profileChangeRequest()
changeRequestPhoto.photoURL = NSURL(string: self.fileUrl) as URL?
changeRequestPhoto.commitChanges(completion: { (error) in
if let error = error {
print(error.localizedDescription)
return
}else{
print("profile uptaded")
}
})
let appDelegate: AppDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.Login()
})
})
}
}
}
}
Sign Up class. Here i am getting error like Value of type DataService has no member "Sign up".
// Register Button
#IBAction func RegisterDidTapped(_ sender: AnyObject) {
guard let email = emailTextField.text, let password = passwordTextField.text, let username = usernameTextField.text else {
return
}
var data = NSData()
data = UIImageJPEGRepresentation(profileImage.image!, 0.1)! as NSData
//Signin up
Here i am getting Error: (Value of type DataService has no member "sign Up")
DataService.dataService.signUp(username: username, email: email, password: password, data: data)
}
}
Your issue is that you putting functions inside other functions. Don't do that in this case. Functions that need to be called from other code should be top-level functions of the class, not embedded inside other functions.
And, unrelated, please use proper, standard naming conventions. Method and variable names should start with lowercase letters. Classnames start with uppercase letters.

Resources