CocoaAction with RxSwift and UIAlertController - ios

I'm trying to implement the current behavior with a textfield and a button:
1 - the textfield should be validated not realtime but only after tapping the button it has to show an error label for the validation error
2 - if the textfield is validate I have to show an uialertcontroller to cancel or continue the operation
I tried especially the second part with the following code but It works only the first time, if I tap cancel for example and I tap an other time the button it looks like disabled....no more taps are allowed.
let action = CocoaAction {
return Observable.create {
[weak self] observer -> Disposable in
let alertController = self.getAlertController()
let ok = UIAlertAction.Action(NSLocalizedString("OK_BUTTON", comment: "OK_BUTTON"), style: .Default)
ok.rx_action = CocoaAction { _ in
return self!.viewModel!.modify(self?.addressTextFiled.rx_text)
.doOnNext({ data in
if let data = data
{
self!.showMessage(data.message)
}
})
.map { _ in Void() }
}
let cancelAction = UIAlertAction(title: NSLocalizedString("CANCEL_BUTTON", comment: "CANCEL_BUTTON"), style: .Cancel) { (_) in }
alertController.addAction(ok)
alertController.addAction(cancelAction)
self!.presentViewController(alertController, animated: true, completion: nil)
return NopDisposable.instance
}
}
confirmButton.rx_action = action
For the first point do you have some advise?
Thanks to help me out!!

In the viewModel the observable returned is:
func modify() -> Observable<StatusResponse?>
{
return input!.continueClick
.withLatestFrom(requestData!)
.flatMap{ [unowned self] (code, mail) -> Observable<StatusResponse?> in
return self.provider.request(APIProvider.ModifyRequest(code, "A", mail))
.mapObjectOptional(ModifyStatusResponse.self)
.trackActivity(self.activityIndicator)
}
.retry()
.shareReplay(1)
}
What does avoid the completion of the observable?

Related

how can i do it: library's callback wait until user choose from popover and then get return

I have a problem with understanding how work with view/delegate and completion.
I use library which have callback - something like:
func youShouldChoose()->String.
I desided to give a choice to user and open popover. But I don't understand how to return the selected value.
I read about completion. So i've tried this:
func youShouldChoose() -> String {
askUser()
return self.valueForResult //This line is executed earlier than askUser is finished
}
func askUser(){
showAlert(completion: {(result)->Void in
self.valueForResult = result
})
}
func showAlert(completion:#escaping (_ result:String)->Void)
{
let alert = UIAlertController(...)
alert.addAction(UIAlertAction(title: "Click", style: UIAlertAction.Style.default, handler: { action in
completion(textField.text)
}))
alert.addTextField(configurationHandler: {(textField: UITextField!) in
textField.placeholder = "Enter text:"
})
self.present(alert, animated: true, completion: nil )
}
How can I wait until askUser() will end completely? Is there a way to return value from completion to my library?
I found two ways to solve out this problem:
1. Use loop. Showing view until flag is false
askUser() //we should set flag to true here
while( flag == false ) {
CFRunLoopRunInMode(CFRunLoopMode.defaultMode, 1, true);
}
return self.valueForResult
Use semaphore
let semaphore = DispatchSemaphore(value: 0)
askUser()
semaphore.lock()
return self.valueForResult
Here is an example solution (Swift 4.2 / 5.0):
func youShouldChoose(_ completion: #escaping ((String) -> Void)) {
askUser(completion) // handing over the completion block to `askUser.
// Alternative completion block execution:
// askUser { (enteredText) in
// // This block is called when the "Click" action button on the alert was tapped.
// completion(enteredText)
// }
}
func askUser(_ completion: #escaping ((String) -> Void)) {
showAlert(completion) // handing over the completion block to `showAlert`.
}
func showAlert(_ completion: #escaping (String) -> Void) {
let alert = UIAlertController(title: nil, message: nil, preferredStyle: .alert)
alert.addAction(UIAlertAction.init(title: "Click", style: .default, handler: { (_) in
if let textField = alert.textFields?.first, let text = textField.text {
completion(text) // -> handing over the text of the textField!
} else {
// No text field or text available. Something went wrong!
}
}))
alert.addTextField { (textField) in
textField.placeholder = "Enter text:"
}
self.present(alert, animated: true, completion: nil)
}
// How to use `youShouldChoose `:
func foo() {
youShouldChoose { (enteredText) in
// This block is called when `youShouldChoose` is finished.
print(enteredText) // -> prints the user's entered text.
print("Hello")
}
}

Stop program execution and return if else condition is hit

In the sign-up process for my app, I have two function - first checks the user's entered handle to see if it's already taken, and then the second function sets the rest of their values:
#IBAction func setupDoneButtonPressed(_ sender: Any) {
checkHandle()
setUserInfo()
self.performSegue(withIdentifier: "setupToChat", sender: nil)
}
The checkHandle function seems to be doing it's job, in that it checks the database then prints the "else" condition print statement - however I don't see the alert, and the program simply segues into the app.
If that handle is already in use, I need to program to halt and not move on to setUserInfo and then segue into the app. I'd like to display the alert that I have in there, then allow the user to try again with a different handle.
This is the checkHandle function:
func checkHandle() {
if self.handleTextField.text != nil {
if let handle = self.handleTextField.text {
let handleRef: FIRDatabaseReference = FIRDatabase.database().reference().child("users")
handleRef.queryOrdered(byChild: "handle").queryEqual(toValue: "\(handle)").observeSingleEvent(of: .value, with: { (snapshot) in
if (snapshot.value is NSNull) {
print("handle not in use") // handle not found
userRef.child("handle").setValue(handle)
} else {
print("Handle already in use. Value: \(snapshot.value)") // handle is in use
let alertController = UIAlertController(title: "Handle Taken", message: "Please choose a different handle", preferredStyle: UIAlertControllerStyle.actionSheet)
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {(alert :UIAlertAction!) in
})
alertController.addAction(okAction)
self.present(alertController, animated: true, completion: nil)
}
})
}
}
}
What can I do to ensure that the sign-up process stops in the case of an already-existing handle?
You can solve it like that:
First make checkHandle() a function that takes completion like checkHandle(completion: #escaping (Bool) -> Void)
Then inside this function invoke completion(true) if the if condition is met and completion(false) if it is not. Then in your button handler use checkHandle() like that:
checkHandle { [weak self] success in
guard success else { return }
self?.setUserInfo()
self?.performSegue(withIdentifier: "setupToChat", sender: nil)
}
your code is just continuing processing and not taking any conditational action. I would suggest that you make your checkHandle function return a boolean value and then respond accordingly.
func checkHandle(handle: String) -> Bool {
someBool = // some logic to see if handle exists.
return someBool
}
#IBAction func setupDoneButtonPressed(_ sender: Any) {
if checkHandle() {
setUserInfo()
self.performSegue(withIdentifier: "setupToChat", sender: nil)
} else {
// present an error, or make suggestions etc
}
}
#IBAction func setupDoneButtonPressed(_ sender: Any) {
if checkHandle()
{
setUserInfo()
self.performSegue(withIdentifier: "setupToChat", sender: nil)
}
}
func checkHandle() -> Bool {
let isAvailable = false
if self.handleTextField.text != nil {
if let handle = self.handleTextField.text {
let handleRef: FIRDatabaseReference = FIRDatabase.database().reference().child("users")
handleRef.queryOrdered(byChild: "handle").queryEqual(toValue: "\(handle)").observeSingleEvent(of: .value, with: { (snapshot) in
if (snapshot.value is NSNull) {
print("handle not in use") // handle not found
userRef.child("handle").setValue(handle)
isAvailable = true
} else {
print("Handle already in use. Value: \(snapshot.value)") // handle is in use
let alertController = UIAlertController(title: "Handle Taken", message: "Please choose a different handle", preferredStyle: UIAlertControllerStyle.actionSheet)
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {(alert :UIAlertAction!) in
})
alertController.addAction(okAction)
self.present(alertController, animated: true, completion: nil)
isAvailable = false
}
})
}
}
if isAvailable
{
return true
}
return false
}

use RxSwift's retryWhen operator to prompt the user for a retry or cancel

I am trying to make an iOS app using MVVM architecture. Now what I want is that if an observable fails, I will show the user a prompt asking if he wants to cancel or retry. This should be simple enough with retryWhen but retry is never called. Here my code:
.retryWhen({ (errorObservable: Observable<Error>) -> Observable<Error> in
return promptFor("test", cancelAction: RetryResult.cancel, actions: [RetryResult.retry])
.flatMap { action -> Observable<Error> in
switch action {
case .retry:
return errorObservable
case .cancel:
return errorObservable.flatMap { Observable.error($0) }
}
}
})
prompt is just a method I took from RxSwiftExample and can be found in the repo, but for ease here it is:
func promptFor<Action : CustomStringConvertible>(_ message: String, cancelAction: Action, actions: [Action]) -> Observable<Action> {
#if os(iOS)
return Observable.create { observer in
let alertView = UIAlertController(title: "RxExample", message: message, preferredStyle: .alert)
alertView.addAction(UIAlertAction(title: cancelAction.description, style: .cancel) { _ in
observer.on(.next(cancelAction))
})
for action in actions {
alertView.addAction(UIAlertAction(title: action.description, style: .default) { _ in
observer.on(.next(action))
})
}
(UIApplication.shared.delegate as! AppDelegate).window?.rootViewController?.present(alertView, animated: true, completion: nil)
return Disposables.create {
alertView.dismiss(animated:false, completion: nil)
}
}
#elseif os(macOS)
return Observable.error(NSError(domain: "Unimplemented", code: -1, userInfo: nil))
#endif
can anyone explain why this is not working? or even offer a solution?

Use of closure passed into a function causes a malloc crash

I have a custom UIView controller that I init like so:
inputPhoneNumberView.frame = CGRect(x: 0, y: 0, width: 286, height: 73)
let alert = SAAlertView(title: "Enter Your Phone Number", message: "Enter or update your phone number and we will send you a verification code via SMS.", customView: inputPhoneNumberView)
alert.addAction("Confirm", style: .default, dismissAfterAction: false, hasLoadingIndicator: true) { () -> Void in
print("Hello!") //Testing code
}
alert.addAction(NSLocalizedString("cancel", comment: ""), style: .cancel, actionBlock: nil)
present(alert, animated: true, completion: nil)
Actions can be added to the view controller that are associated to UIButtons inside it. e.g. alert.addAction(NSLocalizedString("cancel", comment: ""), style: .cancel, actionBlock: nil
An action is a struct defined as so:
struct Action {
var title: String
var style: ActionStyle
var actionBlock: (() -> Void)?
var dismissAfterAction: Bool
var hasLoadingIndicator: Bool
}
The code block in an action is handled in this method:
fileprivate dynamic func doAction(_ sender: CustomButton) {
// Make sure the action should be allowed for default only.
let action = actions[sender.tag]
guard sender.tag >= 0 && sender.tag < actions.count else {
print("No action at that index.", logType: .Error)
return
}
if let block = action.actionBlock {
if action.dismissAfterAction {
dismiss(animated: true, completion: {
block()
})
} else {
block() // The point the crash occurs!
}
} else {
dismiss(animated: true, completion: nil)
}
}
There is an option when creating an action to dismissAfterAction i.e. auto dismiss the view controller then perform the code block. However if dismissAfterAction is false the app will crash with a malloc error. It doesn't always crash but if I repeatedly tap the button associated with that action it will eventually. Not sure what's going on here. Has anyone come across anything like this before? Seems to be a problem with the code block.
I've had likely issue, some time ago. As a fix you can change your code in this way:
//we already checked action block, so
action.actionBlock!()
//block() The point the crash occurs!

iOS - User Authentication Function That Returns Bool

Ultimately, what I want to have is one function (or probably a function within a separate class) that prompts the user to authenticate via TouchID, then passcode and if either of these are successful then returns a true boolean.
I've figured out the authentication mostly however I can't get the function to return a boolean, here's roughly what I have so far:
The authenticate user function:
func authenticateUser() -> Bool {
let context = LAContext()
var error: NSError?
let reasonString = "Authentication is needed to access your places."
if context.canEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, error: &error) {
context.evaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, localizedReason: reasonString, reply: { (success, policyError) -> Void in
if success {
print("touchID authentication succesful")
} else {
switch policyError!.code {
case LAError.UserFallback.rawValue:
print("User selected to enter password.")
NSOperationQueue.mainQueue().addOperationWithBlock({ () -> Void in
self.showPasswordAlert()
})
default:
print("Authentication failed! :(")
}
}
})
} else {
print(error?.localizedDescription)
NSOperationQueue.mainQueue().addOperationWithBlock({ () -> Void in
self.showPasswordAlert()
})
}
return true
}
It's just set to return true for now for testing purposes. However I'd like to have it return true whenever there's a successful authentication. I can't place the return within the context.evaluatePolicy because it's inside the block method. Is there another way to do what I want? Or am I going about this in totally the wrong manner?
Also, for reference here is my showPasswordAlert function:
func showPasswordAlert() {
let alertController = UIAlertController(title: "Passcode", message: "Please enter your passcode.", preferredStyle: .Alert)
let defaultAction = UIAlertAction(title: "OK", style: .Default) { (action) -> Void in
if let textField = alertController.textFields?.first as UITextField? {
if let passcode = self.keychainWrapper.myObjectForKey("v_Data") as? String {
if textField.text == passcode {
print("Authentication successful! :) ")
} else {
}
}
}
}
let cancelAction = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel, handler: nil)
alertController.addAction(defaultAction)
alertController.addAction(cancelAction)
alertController.addTextFieldWithConfigurationHandler { (textField) -> Void in
textField.placeholder = "Enter passcode..."
textField.textAlignment = NSTextAlignment.Center
textField.secureTextEntry = true
textField.keyboardType = UIKeyboardType.NumberPad
}
self.presentViewController(alertController, animated: true, completion: nil)
}
So in my head what I'm thinking is: showPasswordAlert could also return a true boolean to authenticateUser and then this would in turn return a true boolean to where the authenticateUser function is being called. I know there's a simpler way to do that but I'd just like to get it working for now.
So after much trial and error I've come up with possibly what is the best solution for me at the moment.
It seems that since evaluatePolicy and co. are run asynchronously you can't return variables from them or access variables. You can however, call selectors from inside these blocks (why this is I have no idea).
So my current solution as of writing this post is to call the authenticate function as such:
func authenticateUserWithAction(actionSelector: Selector, object: AnyObject){}
I pass it an action (declared elsewhere in the class, but basically what you want to do if authentication is successful) and an object. The object is just incase the action requires something to be passed to the function. So in my app for example, after authentication a viewController is presented and an object on that viewController is set to an object in the original viewController. This object is passed in the authenticate function.
From within the authenticate user function I can call to an authenticateUserWithPasscode(actionSelector: Selector, object: AnyObject) that takes in the same action and object as the original authenticate function.
The action and object are passed down the chain until the user is authenticated and they are performed.
Pretty hacky code overall but it seems to be working fine for me.
Also had this problem, I ended up making a struct called Authentication which has a static function authenticate which gets passed the view controller from where you're calling it and a callback function:
import LocalAuthentication
import UIKit
struct Authentication {
static func authenticate(viewController: UIViewController, callback:
#escaping (Bool) -> ()) {
let context = LAContext()
var error: NSError?
if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) {
let reason = "Please Authenticate"
context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason) {
[weak viewController] success, authenticationError in
guard let viewController = viewController else {
return
}
DispatchQueue.main.async {
if success {
callback(true)
} else {
let ac = UIAlertController(title: "Authentication failed", message: "Please try again", preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "OK", style: .default))
viewController.present(ac, animated: true)
callback(false)
}
}
}
} else {
let ac = UIAlertController(title: "Touch ID not available", message: "Your device is not configured for Touch ID.", preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "OK", style: .default))
viewController.present(ac, animated: true)
callback(false)
}
}
}
Then calling it:
Authentication.authenticate(viewController: parentViewController, callback: {
[weak self] (authenticated: Bool) in
if authenticated {
self?.yourFunctionHere()
}
})

Resources