Completion block in IOS swift - ios

I am trying add a nullable completion block to a custom function
func disPlayAlertMessage(titleMessage:String, alertMsg:String, completion: (() -> Void)? = nil){
AlertMessage.alertMessageController = UIAlertController(title: titleMessage, message:
alertMsg, preferredStyle: UIAlertControllerStyle.Alert)
AlertMessage.alertMessageController.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default,handler: nil))
if completion == nil {
controller.presentViewController(AlertMessage.alertMessageController, animated: true, completion: nil)
} else {
controller.presentViewController(AlertMessage.alertMessageController, animated: true, completion: {
completion!()
})
}
return
}
When I am trying to call the above function like below
AlertMessage(controller: self).disPlayAlertMessage(CustomAlertMessages.AlertTitle, alertMsg: CustomAlertMessages.DOANoUpdate, completion: { () -> Void in
{
self.navigationController?.popViewControllerAnimated(true)
}
})
The completion block is always nil.

Here is how you define a nil'able completion
func function(completion: (Void -> Void)? = nil) {
completion?()
}
There are a few different ways in which you can call it
function() //without any argument
function({ //with parens and braces
print("I will get called")
})
function() { //with parens and braces
print("I will get called")
}
function { //without parens
print("I will get called")
}

Edit: Tested with Swift 2.0 only..
You should change your completion parameter.
Example:
func Test( completion: () -> () = {_ in }) {
completion()
}
This function can be called in two different ways:
Test() // Nothing happens
Test({ print("Completed") }) // Prints Completed
Hope this helps :)

This works for me
typealias CompletionHandler = (_ success:Bool) -> Void
func yourCompletionBlockName(completionHandler: CompletionHandler) {
//code
let flag = true
completionHandler(flag)
}
call completion block whenever you need
yourCompletionBlockName(completionHandler: { (success) -> Void in
if success {
} else {
}
})

Related

UIAlertController not working with Rx-Single

So, I added this extension function to the PrimitiveSequenceType to show a loader on screen when making a network call
extension PrimitiveSequenceType where Trait == SingleTrait {
func subscribeWithLoader(showLoaderOn viewController: MyUIViewController, onSuccess: ((Element) -> Void)? = nil, onFailure: ((Swift.Error) -> Void)? = nil)-> Disposable {
let loader = viewController.showLoading()
return subscribe { (element) in
DispatchQueue.main.async {
loader.dismiss(animated: true, completion: {
onSuccess?(element)
})
}
} onFailure: { (error) in
DispatchQueue.main.async {
loader.dismiss(animated: true, completion: {
onFailure?(error)
})
}
}
}
}
Here is my showLoading function
func showLoading()-> UIAlertController {
let alert = UIAlertController(title: nil, message: "Please wait...", preferredStyle: .alert)
let loadingIndicator = UIActivityIndicatorView(frame: CGRect(x: 10, y: 5, width: 50, height: 50))
loadingIndicator.hidesWhenStopped = true
loadingIndicator.style = UIActivityIndicatorView.Style.medium
loadingIndicator.startAnimating();
alert.view.addSubview(loadingIndicator)
present(alert, animated: true, completion: nil)
return alert
}
But the loader never stops. Can anybody let me know what I'm doing wrong. Any help would be appreciated.
It's not very Rx like, but it works... except for one edge case. You dismiss the alert on success and on failure, but what if the Single is disposed without emitting either? Then the alert won't dismiss.
Try something like this instead:
extension PrimitiveSequenceType where Trait == SingleTrait {
func withLoader(showLoaderOn viewController: UIViewController) -> Single<Element> {
func loadingController() -> UIAlertController {
let alert = UIAlertController(title: nil, message: "Please wait...", preferredStyle: .alert)
let loadingIndicator = UIActivityIndicatorView(frame: CGRect(x: 10, y: 5, width: 50, height: 50))
loadingIndicator.hidesWhenStopped = true
loadingIndicator.style = UIActivityIndicatorView.Style.medium
loadingIndicator.startAnimating();
alert.view.addSubview(loadingIndicator)
return alert
}
return Single.create { fullfil in
let loader = loadingController()
viewController.present(loader, animated: true)
let disposable = self.subscribe(fullfil)
return Disposables.create {
disposable.dispose()
loader.dismiss(animated: true)
}
}
}
}
If you like wrapping view controllers up like this. Check out this library... Cause-Logic-Effect
So, I end up adding a delay like this
extension PrimitiveSequenceType where Trait == SingleTrait {
func subscribeWithLoader(showLoaderOn viewController: MyUIViewController, onSuccess: ((Element) -> Void)? = nil, onFailure: ((Swift.Error) -> Void)? = nil)-> Disposable {
let loader = viewController.showLoading()
return subscribe { (element) in
onSuccess?(element)
DispatchQueue.global().asyncAfter(deadline: .now() + 0.5, execute: {
DispatchQueue.main.async {
loader.dismiss(animated: true, completion: nil)
}
})
} onFailure: { (error) in
onFailure?(error)
DispatchQueue.global().asyncAfter(deadline: .now() + 0.5, execute: {
DispatchQueue.main.async {
loader.dismiss(animated: true, completion: nil)
}
})
} onDisposed: {
DispatchQueue.global().asyncAfter(deadline: .now() + 0.5, execute: {
DispatchQueue.main.async {
loader.dismiss(animated: true, completion: nil)
}
})
}
}
}
I think the problem was onSuccess (that means calling loader.dismiss) was getting called even before UIAlertController could show itself. So, by adding a delay of 500ms solves the issue the UIAlertController is going to have enough time to show itself, and then we are dismissing it.
Open to new ideas and improvements.

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
}

CocoaAction with RxSwift and UIAlertController

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?

error in implementing completion in iOS swift

let mailComposeViewController = self.configuredMailComposeViewController(randomNumber, email: email!)
if MFMailComposeViewController.canSendMail()
{
self.presentViewController(mailComposeViewController, animated: true, completion { () -> Void in
self.securityCode(randomNumber, email: email!)
})
}
else
{
self.showSendMailErrorAlert()
}
You are using completion incorrectly. Use it like this
self.presentViewController(mailComposeViewController, animated:true) { () -> Void in
self.securityCode(randomNumber, email: email!)
}

Resources