Proper way to end an Async process - ios

So I have this code:
#IBAction func onStart(_ sender: Any) {
DispatchQueue(label: "SyncQueue").async{
for i in 0..<numberOfItemToDownload {
// download data from the internet this may takes longer depends on the connection speed and data being download
if isCancelled {
DispatchQueue.main.async { // dismiss the progress ViewController (displayed modally) }
break
}
}
}
}
#IBAction func onEnd(_ sender: Any) {
isCancelled = true
}
This works ok, but if the current item being download takes longer then the user taps the "End" button the "progress dialog" is not dismissed until the current item is done. This makes the "End" button not working in user's perspective.
In Android we can interrupt the AsyncTask process and end it. Is there another way to do it in Swift? like when the user taps the "End" the process should immediately stop and dismiss the "progress dialog".

GDC is not easily cancellable.
Use an NSOperation subclass then you can cancel easily and quickly.

Related

How to make a button in an extension redirect the user to a certain page

Overview
I'm building a custom keyboard extension in swift and I want to make a button redirect the user to a certain page in its parent application.
What I'm doing
I created a button in my keyboard that is supposed to take the user to the application and it works.
#objc func openURL(_ url: URL) {
return
}
func openApp(_ urlstring:String) {
var responder: UIResponder? = self as UIResponder
let selector = #selector(openURL(_:))
while responder != nil {
if responder!.responds(to: selector) && responder != self {
responder!.perform(selector, with: URL(string: urlstring)!)
return
}
responder = responder?.next
}
}
#IBAction func openShop(_ sender: Any) {
openApp ("myapppurl")
parent?.performSegue(withIdentifier: "Shop", sender: self)//Shop is the identifier of a segue in my app.
}
And of course I added this to my the info.plist file in my app.
Problem
When I press the button, it takes me to the app but it shows the main vc, not the page i need.
Question
How do I make the button take me directly to the desired vc?
You can't performSegue to container app from extension. In order to performSegue to work view needs to be in foreground. When you are performing Segue, container App is in background and that's why it is not doing anything.
You will need to handle deep-linking in container App.
Add destination inside your openApp URL "myapppurl//shop"
This should trigger openURL[enter link description here][1] in container app.
and URL param should give you requested destination. There you can handle logic to deep-linking.
You can set USERDEFAULTS value , when opening app check it, and clear.

Why isn't my rx subscription triggered on my onNext?

Playing around with Rx Swift I have run into a situation where my subscription doesn't trigger.
I have two viewControllers. The first has a label that the subscriber should update, like this:
func listen() {
print("In func")
let sec = storyboard?.instantiateViewController(withIdentifier: "secondvc") as! SecondViewController
sec.myRx.subscribe(onNext: {
print("SUBSCRIBED", $0)
self.rxLabel.text = $0
})
}
If you go to the sencond viewController there is a button that sets off an onNext event. Like this:
var myRx = PublishSubject<String>()
#IBAction func myButton(_ sender: Any) {
myRx.asObserver().onNext("Hello")
}
So, in my head, when myButton is pressed in the second viewController the label in the first viewController should update when going back to that viewController. But from what I can tell, the function is triggered, but the subscription isn't triggered at all.
Please make sure you are subscribing to the same PublishSubject you're posting events to. Easiest way to confirm this is to by setting breakpoints and checking the address.

Segue from AlertController without action

I have a loading indicator, implemented with UIAlertController. When i send auth request to server - i fire up loading indicator. When request is successful i have to go to another ViewController and hide loading indicator. Before i perform segue i need to wait until AlertController dismiss indication is completed. So i have such a logic:
private var loadingIndicator: UIAlertController?
func navigateToMonitoring() {
DispatchQueue.global(qos: .background).async {
if let indicator = self.loadingIndicator {
while !indicator.isBeingDismissed { continue }
DispatchQueue.main.async {
self.performSegue(withIdentifier: "Monitoring", sender: self)
}
}
}
}
But when this method executed i've got a message - Warning: Attempt to present on whose view is not in the window hierarchy! - and segue does not performed.
How can i fix this?
(Swift 3, Xcode 8)
You can maybe create the alert and store its reference, then show the alert and call the API, and in that API's completion block, you can dismiss it and perform the segue, hope this makes sense to you.

segue called twice when using DispatchQueue.main.async

I'm trying to perform a segue to a new view controller, but the segue is being called twice and the new view controller appears twice.I'm using a method that performs a GET request to an API to retrieve data.That method uses a completion handler.
func getSearchResultsForQuery(_ query: String, completionHandlerForSearchResultsForQuery: #escaping (_ success: Bool, _ error: NSError?) -> Void)
When the method completes successfully my segue is called, from within the main queue as is required.
I've set breakpoints so I could see what was going on and the execution jumps from the performSegue back up to the conditional that checks if the method was successful and then continues until the segue is called a second time. I've tried a purely programatic segue, but the result was the same.I also added a print statement, and if I comment out the segue the print statement is only called once.
I've used this same pattern a number of times before and never had a problem with it and I just can't figure out why this is happening.The only thing I'm doing different this time is using Swift 3 and using DispatchQueue.main.async instead of dispatch_async(dispatch_get_main_queue(). Here is the function which is giving me this problem:
#IBAction func search(_ sender: UIButton) {
let searchQuery = searchField.text
TIClient.sharedInstance().getSearchResultsForQuery(searchQuery!) { (success, error) in
if success {
print("Food items fetch successful")
DispatchQueue.main.async {
print("Perorming segue for food item: \(searchQuery)")
self.performSegue(withIdentifier: "showFoodItems", sender: self)
}
} else {
print("error: \(error)")
}
}
}
Edit: I never found out what the problem was, but completely deleting the story board and recreating it solved it.
I know this isn't a great way to fix this issue, Also I can't leave a comment due to low reputation but what happens if you wrap the whole if statement in DispatchQueue.main?
#IBAction func search(_ sender: UIButton) {
let searchQuery = searchField.text
TIClient.sharedInstance().getSearchResultsForQuery(searchQuery!) { (success, error) in
DispatchQueue.main.async {
if success {
print("Food items fetch successful")
self.performSegue(withIdentifier: "showFoodItems", sender: self)
} else {
print("error")
}
}
}
Would that yield a different result or still the same result? checking for Bool doesn't require too much processing power so I don't think putting it in a main queue is a bad thing but I'd do this to trouble shoot. Sorry I can't just comment on this.
Check in storyboard, maybe you set segue from your button action instead of controller.

How to make consecutive calls to pull down paginated data in Swift

Upon a button press, I need to pull down paginated data (pulling down pins from Pinterest IOS SDK, the pins are limited to 25 per call), i.e. I need my code to iterate until responseObject no longer .hasnext. What's the best way to approach this? I can press the button over and over to pull it all down, but I want to automate it so there's only 1 button press required.
#IBAction func didPressGetPinsButton(sender: UIButton) {
if self.fetchingMore == false && self.currentResponseObject.hasNext() {
self.fetchingMore = true
self.currentResponseObject.loadNextWithSuccess({ (nextResponseObject :PDKResponseObject!) -> Void in
self.fetchingMore = false
self.currentResponseObject = nextResponseObject
guard let unwrappedArrayOfPins = nextResponseObject.pins() as? [PDKPin] else {return}
for pin in unwrappedArrayOfPins {
pins.append(pin)
}
put the action code into a separate function, so that you call it for the first time from the button press, and then you keep re-calling it while .hasNext
#IBAction func didPressGetPinsButton(sender: UIButton) {
actionGetPins()
}
func actionGetPins() {
// all your code
if nextResponseObject.hasNext() {
actionGetPins()
}
}
Depending on how long it takes to get all of the data, you might also want to run the whole function in a background task, but you should at least add a busy indicator to show that there's still work being done.

Resources