Is it possible to have a function inside a completion handler? - swift

Is it possible to have a function inside a completion handler?
I want to save an image through a function after the user shared that same image in activity controller.
I want to do the following:
let controller = UIActivityViewController(activityItems: [generateMemedImage()], applicationActivities: nil)
self.presentViewController(controller, animated: true, completion: nil)
controller.completionWithItemsHandler = {
// Have this save() function when the activity has been completed
func save() {
//Save the meme
let meme = Meme(topText: self.upperTextField.text!, lowerText: self.lowerTextField.text!, originalImage:
self.imageRetrieved.image!, memedImage: generateMemedImage())
}
}
Is there any better solution? Or how can I achieve this?

You don't need to declare a function inside the completion handler. Just write the code to perform the saving inside it, look
class Controller: UIViewController {
func foo() {
let controller = UIActivityViewController(activityItems: [generateMemedImage()], applicationActivities: nil)
self.presentViewController(controller, animated: true, completion: nil)
controller.completionWithItemsHandler = { activityType, completed, returnedItems, error -> () in
// write code to perform the save here <---
}
}
func generateMemedImage() -> [AnyObject] {
return []
}
}

Related

access the last element of the UIPageViewController

How do I access the last item of a UIPageViewController so I can set self as its delegate?
self.present(wizard, animated: true, completion: {
let lastVC = wizard.viewControllers?.last as! thirdPageController
lastVC.delegate = self
})
I think the third item wasn't loaded yet so .last returns the wrong page (the first one)
Swift 5
Create your own UIPageViewController:
class MyPageViewController: UIPageViewController {
private lazy var _viewControllers = [UIViewController]()
public var lastViewController: UIViewController? {
return _viewControllers.last
}
override func setViewControllers(_ viewControllers: [UIViewController]?, direction: UIPageViewController.NavigationDirection, animated: Bool, completion: ((Bool) -> Void)? = nil) {
super.setViewControllers([viewController], direction: direction, animated: animated, completion: completion)
_viewControllers.removeAll()
_viewControllers.append(viewControllers)
}
}
Class usage:
let myPageViewController = MyPageViewController()
myPageViewController.lastViewController // This is an optional value
Finally you should change the class of wizard UIPageViewController to MyPageViewController
Then you can use this new class like:
self.present(wizard, animated: true, completion: {
if let lastViewController = wizard.lastViewController as? thirdPageController {
lastViewController.delegate = self
}
})

Setting DarkTheme has no effect Braintree iOS v4 SDK

The documentation clearly says it can be done, easy breezy:
https://developers.braintreepayments.com/guides/drop-in/customization/ios/v4#themes
I can indeed customize the primaryTextColor to red.
Here is a screenshot, that demonstrates that red works but not darkTheme:
And is here is my code in my UIViewController:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
showDropIn(clientTokenOrTokenizationKey: clientToken)
}
func showDropIn(clientTokenOrTokenizationKey: String) {
BTUIKAppearance.darkTheme()
BTUIKAppearance.sharedInstance().primaryTextColor = UIColor.red
let request = BTDropInRequest()
request.vaultManager = true
let dropIn = BTDropInController(authorization: clientTokenOrTokenizationKey, request: request)
{ (controller, result, error) in
if (error != nil) {
print("ERROR")
} else if (result?.isCancelled == true) {
print("CANCELLED")
} else if let result = result {
// Use the BTDropInResult properties to update your UI
// result.paymentOptionType
// result.paymentMethod
// result.paymentIcon
// result.paymentDescription
}
controller.dismiss(animated: true, completion: nil)
}
self.present(dropIn!, animated: true, completion: nil)
}
So Braintree's documentation on Theme's is a bit poorly choose wording IMO.
The instruction is what is misleading to me: "To use the Dark theme instead, call this method before initializing Drop-in". Yet you have to initialize or instantiate the drop-in before setting darkTheme.
The instruction might better read: "To use the Dark theme instead, call this method before presenting the Drop-in"
Here is my working code:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
showDropIn(clientTokenOrTokenizationKey: clientToken)
}
func showDropIn(clientTokenOrTokenizationKey: String) {
let request = BTDropInRequest()
request.vaultManager = true
dropIn = BTDropInController(authorization: clientTokenOrTokenizationKey, request: request)
{ (controller, result, error) in
if (error != nil) {
print("ERROR")
} else if (result?.isCancelled == true) {
print("CANCELLED")
} else if let result = result {
// Use the BTDropInResult properties to update your UI
// result.paymentOptionType
// result.paymentMethod
// result.paymentIcon
// result.paymentDescription
}
controller.dismiss(animated: true, completion: nil)
}
BTUIKAppearance.darkTheme()
BTUIKAppearance.sharedInstance()?.primaryTextColor = UIColor.lightGray
self.present(dropIn!, animated: true, completion: nil)
}

Dismiss alert after task on MainViewController swift

I need to download some content from API when users login for the first time at my app and show them I'm doing that. I do this at my MainViewController:
override func viewDidAppear(_ animated: Bool) {
let alert = UIAlertController(title: nil, message: "Wait please...", preferredStyle: .alert)
let loadingIndicator = UIActivityIndicatorView(frame: CGRect(x: 10, y: 5, width: 50, height: 50))
loadingIndicator.hidesWhenStopped = true
loadingIndicator.style = UIActivityIndicatorView.Style.gray
loadingIndicator.startAnimating();
alert.view.addSubview(loadingIndicator)
present(alert, animated: true, completion: nil)
let parceiroId = self.defaults.getParceiroId()
if !self.defaults.getDownloadInicialTipoEntrega() {
TipoEntregaAPI().loadTiposEntrega(parceiroId){ (dados) in
if dados != nil {
for tipoEntrega in dados!{
// Do some stuff, no errors
}
}
}
}
if !self.defaults.getDownloadInicialPedido() {
PedidoAPI().loadOrders(parceiroId){ (dados) in
if dados != nil {
for pedidos in dados!{
// Do some stuff, no errors
}
}
}
}
self.dismiss(animated: false, completion: { () in print("Done") })
}
The problem is that my alert with the loading never gets dismissed. It never prints "Done". Can anyone help me please?
I dont know if it is useful, but I always get this warning:
Warning: Attempt to dismiss from view controller <MyApp.MainViewController: 0x0000000> while a presentation or dismiss is in progress!
The problem is exactly what the error says. Your presentation call isn't completed at the moment you're calling self.dismiss(...). For further explanation, you're calling present(alert, animated: true, completion: nil) with animated: true parameter so it's not going to finish the presentation instantly. On the other hand, you are calling self.dismiss(animated: false, completion: { () in print("Done") }) on the same thread in the relatively short instruction block, so it gets executed before the iOS finishes the animated presentation of the dialog and that's why you get the error.
But furthermore, before actually fixing the issue you should ask yourself do you really want to dismiss the dialog as soon as it's presented. Judging from the code you posted, I'd assume you want it to be dismissed after either or both of the API calls are finished. If that's the case you need to move the dismiss method call within the completion block (closures) of your API calls.
Solution for me (Working fine and printing "Done"):
override func viewDidAppear(_ animated: Bool) {
if !self.defaults.getDownloadInicialTipoEntrega() || !self.defaults.getDownloadInicialPedido() || !self.defaults.getDownloadInicialVitrine() {
Functions.showAlertWaiting("Wait please...", self)
}
loadDeliveryTypesNOrders { (completed) in
if completed {
self.dismiss(animated: false, completion: { () in print("Done") })
}
}
}
func loadDeliveryTypesNOrders (completion: #escaping (Bool) -> ()) {
let parceiroId = self.defaults.getParceiroId()
if !self.defaults.getDownloadInicialTipoEntrega() {
TipoEntregaAPI().loadTiposEntrega(parceiroId){ (dados) in
if dados != nil {
for tipoEntrega in dados!{
// Do some stuff, no errors
}
}
}
}
if !self.defaults.getDownloadInicialPedido() {
PedidoAPI().loadOrders(parceiroId){ (dados) in
if dados != nil {
for pedidos in dados!{
// Do some stuff, no errors
}
}
}
}
completion(true)
}

How to wait that closure is done before presenting a viewController

When I press secondButton before article is not downloaded, the ArticleViewController is empty because showArticleView(section) is executed before the closure is done.
How can I wait that the completion is finish to execute showArticleView(section) method.
Controller should not be presented without datas.
#IBAction func secondButtonPressed() {
if let data = issue.data, let article = data.getTableOfContents.first {
downloadArticles(for: article)
}
}
private func downloadArticles(for section: IssueDataSection) {
if let mainPage = section.pages.first {
currentArticleDownload = issue.mediaManager.download(page: mainPage.intValue, loadingDelegate: self, completion: { medias in
if let medias = medias {
medias.articles(for: section) { _ in
self.showArticleView(section)
}
}
})
}
}
private func showArticleView(_ section: IssueDataSection?) {
if let controller = UIStoryboard(name: "IssueViewer", bundle: nil).instantiateViewController(withIdentifier: "ArticleViewController") as? ArticleViewController {
controller.issue = issue
if let section = section {
controller.section = section
}
controller.showMagHandler = {
self.firstButtonPressed()
}
controller.modalPresentationCapturesStatusBarAppearance = true
present(controller, animated: true, completion: nil)
}
}
Maybe controller is not presented from main thread.
Instead of self.showArticleView(section) try this
DispatchQueue.main.async(execute: {
self.showArticleView(section)
})
Instead of async you may try asyncAfter(.now()+0.5,... to allow more time for the page to download.

Why delegate event is not received swift?

I would like to pass data from EditPostViewController to NewsfeedTableViewController using delegates, but func remove(mediaItem:_) is never called in the adopting class NewsfeedTableViewController. What am I doing wrong?
NewsfeedTableViewController: UITableViewController, EditPostViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
//set ourselves as the delegate
let editPostVC = storyboard?.instantiateViewController(withIdentifier: "EditPostViewController") as! EditPostViewController
editPostVC.delegate = self
}
//remove the row so that we can load a new one with the updated data
func remove(mediaItem: Media) {
print("media is received heeeee")
// it does't print anything
}
}
extension NewsfeedTableViewController {
//when edit button is touched, send the corresponding Media to EditPostViewController
func editPost(cell: MediaTableViewCell) {
let editPostVC = storyboard?.instantiateViewController(withIdentifier: "EditPostViewController") as? EditPostViewController
guard let indexPath = tableView.indexPath(for: cell) else {
print("indexpath was not received")
return}
editPostVC?.currentUser = currentUser
editPostVC?.mediaReceived = cell.mediaObject
self.navigationController?.pushViewController(editPostVC!, animated: true)
}
protocol EditPostViewControllerDelegate: class {
func remove(mediaItem: Media)
}
class EditPostViewController: UITableViewController {
weak var delegate: EditPostViewControllerDelegate?
#IBAction func uploadDidTap(_ sender: Any) {
let mediaReceived = Media()
delegate?.remove(mediaItem: mediaReceived)
}
}
The objects instantiating in viewDidLoad(:) and on edit button click event are not the same objects. Make a variable
var editPostVC: EditPostViewController?
instantiate in in viewDidLoad(:) with delegate
editPostVC = storyboard?.instantiateViewController(withIdentifier: "EditPostViewController") as! EditPostViewController
editPostVC.delegate = self
and then present it on click event
navigationController?.pushViewController(editPostVC, animated: true)
or
present(editPostVC, animated: true, completion: nil)
you can pass data from presenter to presented VC before or after presenting the VC.
editPostVC.data = self.data
I suggest having a property in NewsfeedTableViewController
var editPostViewController: EditPostViewController?
and then assigning to that when you instantiate the EditPostViewController.
The idea is that it stops the class being autoreleased when NewsfeedTableViewController.viewDidLoad returns.