Reactive Cocoa Validation - swift

How can I validate a form after a RAC Command or a button click? I have this somewhere in my View Model
self.executeLoginRacCommand = RACCommand(enabled: activateButtonSignal, signalBlock: { (any : AnyObject!) -> RACSignal! in
return self.executeLoginAPI()
})
//My question is here, how can I display an alert to my view controller saying may email is invalid.
//MARK: - Execute Login API
private func executeLoginAPI() -> RACSignal {
if self.isEmailValid(self.email) {
return self.signInServices.signInAPIService.signInAPISignal(self.email, password: self.password)
} else {
self.errorMessage = "Please insert a valid email"
return RACSignal.empty()
}
}
//MARK: - Is Email Valid
func isEmailValid(email: String) -> Bool {
let emailRegEx = "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+#[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$"
let emailTest = NSPredicate(format:"SELF MATCHES %#", emailRegEx)
return emailTest.evaluateWithObject(email)
}
I access it in my View Controller like this:
self.signInButton.rac_command = self.signInViewModel.executeLoginRacCommand
Can you please suggest a good way of validating an invalid email and display alert after a button click? Thanks in advance!

In your else branch, you are returning an empty RACSignal.
Instead, return the a RACSignal that sends the error (RACSignal.error(<the error>)).
In your ViewController, you can subscribe to the errors property of self.executeLoginRacCommand and display the error to the user.

Related

Download single Object of Firestore and save it into an struct/class object

I am coding since January 2019 and this is my first post here.
I am using Swift and Firestore. In my App is a tableView where I display events loaded out of a single Document with an array of events inside as [String: [String:Any]]. If the user wants to get more infos about an event he taps on it. In the background the TableViewController will open a new "DetailEventViewController" with a segue and give it the value of the eventID in the tapped cell.
When the user is on the DetailViewController Screen the app will download a new Document with the EventID as key for the document.
I wanna save this Data out of Firestore in a Struct called Event. For this example just with Event(eventName: String).
When I get all the data I can print it directly out but I can't save it in a variable and print it out later. I really don't know why. If I print the struct INSIDE the brackets where I get the data its working but if I save it into a variable and try to use this variable it says its nil.
So how can I fetch data out of Firestore and save in just a Single ValueObject (var currentEvent = Event? -> currentEvent = Event.event(for: data as [String:Any]) )
I search in google, firebaseDoc and stackoverflow but didn't find anything about it so I tried to save all the singe infos of the data inside a singe value.
// Struct
struct Event {
var eventName: String!
static func event(for eventData: [String:Any]) -> Event? {
guard let _eventName = eventData["eventName"] as? String
else {
print("error")
return nil
}
return Event(eventName: _eventName)
}
// TableView VC this should work
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ShowEventDetailSegue" {
if let ShowEvent = segue.destination as? DetailEventViewController, let event = eventForSegue{
ShowEvent.currentEventId = event.eventID
}
}
}
// DetailViewController
var currentEvent = Event()
var currentEventId: String?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
guard let _eventID = currentEventId else {
print("error in EventID")
return}
setupEvent(eventID: _eventID) /* currentEvent should be set here */
setupView(event: currentEvent) /* currentEvent has after "setupEvent" the value of nil */
}
func setupEvent(eventID: String) {
let FirestoreRef = Firestore.firestore().collection("events").document(eventID)
FirestoreRef.getDocument { (document, error) in
if let err = error {
debugPrint("Error fetching docs: \(err)")
SVProgressHUD.showError(withStatus: "Error in Download")
}else {
if let document = document, document.exists {
guard let data = document.data() else {return}
let eventData = Event.event(for: data as [String:Any])
print(eventData)
//here all infos are printed out - so I get them
self.currentEvent = eventData!
//Here is the error.. I can't save the fetched Data in my single current Event
} else {
SVProgressHUD.showError(withStatus: "Error")
}
}
}
}
func setupView(event: Event) {
self.titleLabel.text = event.eventName
}
I expect that the function setupEvents will give the currentEvent in the DetailViewController a SINGLEvalue cause its a SINGLE document not an array. So I can use this single Eventvalue for further actions. Like starting a new segue for a new ViewController and just push the Event there not

Should perform segue on request

i'm starting with Swift3 and i'm having a recurrent problem due to the asynchronism. But until now, i always find a solution with callback.
I have a textField and a button, when i click on the button, i check on the API if there is a existing user named as in the textField.
Using shouldPerformSegue, i return the value if the users exist or no.
I have a separated class for handling calls on the Api
class Api {
static let urlApi = "https://XXXXXXXXXXXXX"
private let CUSTOMER_ID = "XXXXXXXX"
private let CUSTOMER_SECRET = "XXXXXXXX"
private var access_token : String? = nil
private var userInfo : User?
init() {
self.connect()
}
func connect() {
// Do the connect...
}
func get(user: String, callback: #escaping (_ status: Bool) -> Void) {
Alamofire.request(URL(string: "\(Api.urlApi)/v2/users/\(user)")!,
method: .get,
parameters: nil,
encoding: URLEncoding.default,
headers: ["Authorization": "Bearer \(self.access_token!)"])
.responseJSON(completionHandler: { response in
if response.result.isFailure {
print("ERROR: GET USER", response)
callback(false)
} else {
print("SUCCESS Getting user ", user)
callback(true)
}
})
}
}
And in my shouldPerformSegue
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
var userExist : Bool? = nil
let dispatchQueue = DispatchQueue(label: "getUser")
let semaphore = DispatchSemaphore(value: 1)
dispatchQueue.sync {
self.api?.get(user: self.userTextField.text!, callback: { status in
userExist = status
print("1 USEREXIST", userExist)
})
}
semaphore.wait()
print("2 USEREXIST", userExist)
return userExist ?? false // always false because userExist == nil
}
Sorry for the function mess, i don't really find the right way to do my DispachQueue and my Semaphore .. All googling answer look that i need those
The proper way to handle this scenario would be to make the request when the user taps on the button. If there is an error, you would present some error that says the username already exists. Then they would try again.
If the request is successful and that username has not been taken, then you would call performSegueWithIdentifier. The link below shows a good demonstration of the steps to take after this. Your current implementation isn't necessary.
https://stackoverflow.com/a/37823730/653839

Swift Parse not displaying error message on signup

I've created a user class which has a Parse PFUser() object in it and a string for error_messages.
In the user class is a signup function which will use the PFUser object and perform signUpInBackgroundWithBlock().
In that function it should set a flag notifying the main view controller that an error occurred if one does as well as set the error_message string in the User object with the error message passed back from PFUser.
However what happens is the function doesn't finish executing once an error occurs for example if an incorrect email format is entered such as aaaa.com instead of aaa#a.com the function won't return and set the flag instead the error message passed from PFUser is just displayed in the console window.
I've spent a few days now trying everything imaginable to set the flag and the error message in the user class but I can't figure it out.
Here is the class code
class User {
var user_db_obj = PFUser()
var error_message = "Please try again later"
//var user_name: String
//var pass_word: String
//Constructor for User object
init(user: String, pass: String){
user_db_obj.username = user
user_db_obj.email = user
user_db_obj.password = pass
}
//This function signs a user up to the database
func signUp() -> Bool {
var error_in_sign_up: Bool = true
user_db_obj.signUpInBackgroundWithBlock {(succeeded: Bool?, error: NSError?) -> Void in
//stop spinner and allow interaction events from user
activityIndicator.stopAnimating()
UIApplication.sharedApplication().endIgnoringInteractionEvents()
if error == nil{
error_in_sign_up = false
//sign up successful
}
else{
let error_string = error!.userInfo["error"] as? String
self.error_message = error_string!
}
}
if error_in_sign_up == true{
return false
}
else{
return true
}
}
Here is the view controller code that calls the signup function from User class.
//Action for signup button
#available(iOS 8.0, *)
#IBAction func signup_btn(sender: AnyObject) {
if email_tf.text!.isEmpty || pass_tf.text!.isEmpty {
//if email or password field blank display error message
displayAlert("Error in form", msg: "Please enter a username and password")
}
else{ //perform actual signup/login if email and password supplied
//Display spinner while database interaction occuring and ignore user interactions as well
activityIndicator = UIActivityIndicatorView(frame: CGRectMake(0, 0, 50, 50))
activityIndicator.center = self.view.center
activityIndicator.hidesWhenStopped = true
activityIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.Gray
view.addSubview(activityIndicator)
activityIndicator.startAnimating()
UIApplication.sharedApplication().beginIgnoringInteractionEvents()
let theUser = User(user: email_tf.text!, pass: pass_tf.text!)
//sign up
if signup_mode == true{
if theUser.signUp() == false{
displayAlert("Failed SignUp", msg: theUser.error_message)
}
}
//login
else{
if theUser.login() == false{
displayAlert("Failed Login", msg: theUser.error_message)
}
}
}
}
the problem is that function signUpInBackgroundWithBlock doesnt run on mainthread, if you want to keep this functions you would have to register notification and then listen when it is successful or not in the other viewController... something like this

issue receiving outcomes when sending text to wit.ai

I'm using the following to send text to wit.ai through a button press function:
#IBAction func searchButton(sender: AnyObject) {
searchQueryText = searchTextInput.text!
if searchQueryText != "" {
wit.interpretString(searchQueryText, customData: nil)
}
func interpretString(string: String, customData: AnyObject) {
}
this works fine as the text is sent to wit.ai. However I get no response from wit.ai back to the app. I can get the response fine if a microphone is used, just not text. I have tried calling the witDidGraspIntent function to force it to run on button press, but I can't work out what I should use in the 'outcomes' parameter. Can anybody help on this? I'm not sure if there is a different way to run the function after button press? This is the function:
func witDidGraspIntent(outcomes: [AnyObject]!, messageId: String!, customData: AnyObject!, error e: NSError!) {
if ((e) != nil) {
print("\(e.localizedDescription)")
return
}
let outcomes : NSArray = outcomes!
let firstOutcome : NSDictionary = outcomes.objectAtIndex(0) as! NSDictionary
if let intent = firstOutcome.objectForKey("intent") as? String {
searchResultsIntent = intent
}
if searchResultsIntent == "searchIntent" {
intentLabel.text = "\(searchResultsIntent)"
print(outcomes[0])
} else {
intentLabel.text = "I'm sorry, I did not understand that."
}
}
here is the documentation for wit.ai: https://wit.ai/docs/ios/4.0.0/api
any assistance is greatly appreciated!
cheers.
Wit sdk gives a sharedInstance (singleton) for users to work on, so you have initiate it like -:
Wit.sharedInstance().accessToken = "TOKEN"
Wit.sharedInstance().delegate = self
and invoke the interpretString function using the sharedInstance i.e.
Wit.sharedInstance().interpretString(text, customData: nil)

How should i handle Alamofire response in shouldPerformSegueWithIdentifier?

I am trying to learn Swift and what i am trying to do is to create simple login page. Whenever user click on login button, i make a request to my web service in shouldPerformSegueWithIdentifier in order to check if username and password is correct. I dont want to use IBAction (like touchupinside etc). After parsing response that comes from web service, i check if there is any error comes from web service response (like user name is not valid). If there is any error I would like to prevent segue by returning false. But i cant return false inside of Alamofire. So how should i handle it? Here is my code :
override func shouldPerformSegueWithIdentifier(identifier: String?, sender: AnyObject?) -> Bool {
if identifier == "segueLoginToMainPage" {
//...
//Checking if text fields are filled or not
else {
let callURL : String = CONFIG.BASE_URL + "user/login"
let parameters = [HTTPParamNames.FIELD_USER_NAME : self.txtUserName.text,
HTTPParamNames.FIELD_PASSWORD : self.txtPassword.text]
Alamofire.request(.POST, callURL, parameters: parameters)
.responseJSON { (_,_,JSON,_) in
var returnedData = JSON as NSDictionary
if returnedData["status"] as String == "error" {
//thats what i want to do
return false
}
}
}
}
return true
}
But i cant do return false because it is async. I have found some questions like this in stackoverflow but answers are not what exactly i am looking for. Thanks!
You simply should not try to use asynchronous method within shouldPerformSegueWithIdentifier. Instead you should:
Remove segue from login button, itself.
Add segue between the view controllers, themselves. Specifically, control-drag from the originating scene's view controller icon (in Xcode 6+, this icon is in the bar above the scene, in earlier versions of Xcode, this icon is in the bar below the scene) to the destination scene.
Give this new segue a "storyboard identifier".
Hook up login button to an IBAction function.
In that IBAction function, perform your asynchronous network request, and if the login was successful, then programmatically performSegueWithIdentifier, supplying the storyboard identifier that you specified in step 3.
I think that not using IBAction is a good solution in this case but anyway there is a workaround how to achieve your goal. For this workaround you need to declare a variable typed boolean to check wether user is ready to login. Your shouldPerformSegueWithIdentifier method should always return value of that variable but after you make a request. Finally after your asynchronous request finishes call the prepareForSegue method
var readyForLogin = false
override func shouldPerformSegueWithIdentifier(identifier: String?, sender: AnyObject?) -> Bool {
if identifier == "segueLoginToMainPage" {
//...
//Checking if text fields are filled or not
else {
if(!readyForLogin){
let callURL : String = CONFIG.BASE_URL + "user/login"
let parameters = [HTTPParamNames.FIELD_USER_NAME : self.txtUserName.text,
HTTPParamNames.FIELD_PASSWORD : self.txtPassword.text]
Alamofire.request(.POST, callURL, parameters: parameters)
.responseJSON { (_,_,JSON,_) in
var returnedData = JSON as NSDictionary
if returnedData["status"] as String != "error" {
//thats what i want to do
self.readyForLogin = true
self.performSegueWithIdentifier("segueLoginToMainPage", sender: self)
}
}
}}
}
return readyForLogin
}