how to know when asynchronous search using MapKit is complete - ios

I am using mapkit to allow a user to search for an address. Pressing another button (the goButton) will ask them to confirm a formatted address populated using the results of their search, and also will send a command to search behind the scenes if the user entered text but did not actually search for the address before selecting goButton. My problem is that because the search is completing asynchronously, the confirmation function is being called too soon.
Is there a way I can tell when the search is completed? I tried putting a bool at the end of searchBarSearchButtonClicked and a while loop before calling confirmHostAddress that checked the value of the bool, but it did not detect a change. Thanks for your help.
#IBAction func goButton(sender: UIButton) {
//check and see if there is an address, if not, do search behind the scenes
if (myAddress == nil)
{
if (mapSearch.text != "")
{
self.searchBarSearchButtonClicked(self.mapSearch)
confirmHostAddress()
}
else
{
alertTextNeeded()
}
}
else
{
confirmHostAddress()
}
}

It looks like your async search is in confirmHostAddress()? That function needs to have a callback block, which will (hopefully) be running on a background thread. Within that callback, use dispatch_async() to enqueue, onto the main queue, the UI change that you want.
Search button click (on main queue) triggers search (on background queue), and search completion triggers (on main queue) UI update.

Related

UITableView not updating when tableView.reloadData() is called, what is causing this problem?

I have a table view that initially starts empty. I am trying to update it once an asynchronous method is called Networking().fetchRecipies(ingredients: searchText). However, after this method is called, the recipieTableView remains unpopulated. What is causeing this error? Below is the code I am running that should be run everytime the text in the text field changes. I know this is working because I am also printing the results of the API call
#IBOutlet weak var recipieTableView: UITableView!
var recipies = LocalData.recipies.recipieList
var filteredRecipies = [Recipie]()
override func viewDidLoad() {
filteredRecipies = recipies
super.viewDidLoad()
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
Networking().fetchRecipies(ingredients: searchText)
self.recipieTableView.reloadData()
DispatchQueue.main.async {
self.recipieTableView.reloadData()
self.recipieTableView.beginUpdates()
self.recipieTableView.endUpdates()
}
}
However, after this method is called, the recipieTableView remains unpopulated. What is causeing this error
What's causing the error is that when you say "after" you should actually be saying "before"! Your code does not run in the order it is written. It runs in this order:
Networking().fetchRecipies(ingredients: searchText) // 2
self.recipieTableView.reloadData() // 1...
// ...
In other words, all your calls to reloadData are happening before you fetch the recipes.
You need to write fetchRecipes such that it reloads the table view after the fetching has finished.
You say the method Networking().fetchRecipies(ingredients:) is called asynchronously, which basically means your code steps into another thread and simultaneously continues to execute to the end of the method your asynchronous function was called from.
So the line self.recipieTableView.reloadData() is called when your data is not yet populated.
After that you call DispatchQueue.main.async which is also unnecessary. Since you're already on the main thread (because that's the convention for searchBar(_:textDidChange:), as for the most of UIKit methods), the only thing your method does is runs the contents of the dispatched block of code some time later (but chances are still much sooner before your fetch request completes).
Read more on how concurrency works on iOS in the official guide. If you're a beginner, it would be just enough to understand the difference between serial and concurrent queues and sync and async dispatches.
Back to your problem. I suggest that you examine the API of your Networking class and check if there's any way to notify you when your data has been actually fetched. Most iOS APIs provide a completion block that is executed with your newly fetched data passed as an argument. For example, it could look like the following:
Networking().fetchRecipies(ingredients: searchText, completion: { fetchedIngredients in
// ... update your data source array
// reload table view
})
Call tableView.updateData() directly from inside such a block if it's called on the main queue, or wrap it inside DispatchQueue.main.async { } if your completion block is invoked from a background queue (refer to the API to find it out).
And, most important, don't forget to update your array (I believe it's either recipies or filteredRecipies) which serves as the source for your table's data BEFORE you call updateData().

How to implement a search queue

I am new in swift3.0 I am implementing a custom search box. I wish to know how can i make a search queue such that on text change in searchbox i need to perform search operation with new text and if there is an existing search operation going on cancel that. I also want to include threshold ontextchanged. So that search operation does not get fired very frequently
Your question is somehow general, but let me tell you how I accomplished this in Swift 3 and AFNetworking (this assumes you wish to search for the data on the server).
I hold a reference of the networking manager in the properties of the view controller:
//The network requests manager. Stored here because this view controller extensively uses AFNetworking to perform live search updates when the input box changes.
var manager = AFHTTPRequestOperationManager()
Afterwards, using UISearchController I check to see if there is any text entered in the search box at all and, if it is, I want to make sure there aren't any other ongoing AFNetworking tasks from now by closing any of them which are still running:
//Called when the something is typed in the search bar.
func updateSearchResults (for searchController: UISearchController) {
if !SCString.isStringValid(searchController.searchBar.text) {
searchController.searchResultsController?.view.isHidden = false
tableView.reloadData()
return
}
data.searchText = searchController.searchBar.text!
/**
Highly important racing issue solution. We cancel any current request going on because we don't want to have the list updated after some time, when we already started another request for a new text. Example:
- Request 1 started at 12:00:01
- We clear the containers because Request 2 has to start
- Request 2 started at 12:00:02
- Request 1 finished at 12:00:04. We update the containers because data arrived
- Request 2 finished at 12:00:05. We update the containers because data arrived
- Now we have data from both 1 and 2, something really not desired.
*/
manager.session.getTasksWithCompletionHandler { (dataTasks, uploadTasks, downloadTasks) in
dataTasks.forEach { $0.cancel() }
}
/**
Reloads the list view because we have to remove the last search results.
*/
reloadListView()
}
In the end, I also check in the failure closure if the code of the error is not NSURLErrorCancelled. Because, if that happened, I don't display any error message or toast.
//The operation might be cancelled by us on purpose. In this case, we don't want to interfere with the ongoing logic flow.
if (operation?.error as! NSError).code == NSURLErrorCancelled {
return
}
self.retrieveResultListFailureNetwork()
Hope it helps!

Avoid multiple web service call in ios

I am inserting data to server database from my app.
on submit button I am calling inserdata web services.
My data inserted in database. but I have one problem,
before I get first result back when I tap on submit button again then same record inserted multiple times in database.
Please help how can I avoid this. (also by mistake I have tapped multiple times on submit button then same record inserted multiple time).
when web service triggers first time.... set button.selected = yes and in function check if button isSelected then not perform action. Use this bool value to distinguish between both conditions.
When user pressed submit button, show progress hud or an activity Indicator View (loading overlay). This will let user know that some processing is going on and make them not trigger any other action.
Also set synchronous property to that object from which you are calling this WebService. (When you execute something synchronously, you wait for it to finish before moving on to another task. When you execute something asynchronously, you can move on to another task before it finishes.)
And when you got response, clear all data from your form or your app. Because If data is cleared, then user can't insert same record into Database.
So, No need to disable that submit button.
Try this.
- (IBAction)doneCollectionSaveAction:(id)sender {
if([activity isAnimating]==YES){
[ALToastView toastInView:[UIApplication sharedApplication].keyWindow withText:#"Please wait..."];
}else{
[self addToCollection];
}
}
or
- (IBAction)doneCollectionSaveAction:(UIButton *)sender {
if (sender.selected==YES) {
//do nothing
[ALToastView toastInView:[UIApplication sharedApplication].keyWindow withText:#"Please wait..."];
}else{
//send data to server
self.buttonDoneOutlet.selected=YES;
[self addToCollection];
}
}
//and self.buttonDoneOutlet.selected=NO; //failure network call

Using a UIActivityIndicatorView for Login process but as I'm using a thread I can't stop it

I'm using a UIActivityIndicatorView to show the user that something is going on while I validate the login details. the code below shows what I'm doing:
func confirmLogin(sender: UIButton) {
ViewController.gVariables.gUser = userNameText.text
ViewController.gVariables.gPwd = passwordText.text
ViewController.gVariables.gLoggedIn = ""
indicatorView.startAnimating()
passwordText.text = ""
GetRemoteData.getValidationFromServer( { (svrData) -> Void in
dispatch_async(dispatch_get_main_queue()) {
self.indicatorView.stopAnimating()
self.indicatorView.hidden = true
MenuViewController.processLogin()
}
})
}
The user name and password are passed to the getValidationFromServer method in my GetRemoteData class which handles a lot of other web service activity. I set my indicator to start animating before calling the method but the following code should stop the indicator but it makes no difference as it is on another thread
What can I do to turn my indicator off?
The completion block of getValidationFromServer is dispatching the stopping of the activity indicator view back to the main queue. That is the correct way to do it. Your indicator view should be stopping when the code dispatched back to the main queue runs.
If the indicator view isn't stopping, there are only a few logical reasons why that might be happening:
You might have some other code elsewhere that is starting the indicatorView again (e.g. in processLogin).
You might have something that is blocking the main thread, preventing this code that was dispatched to the main thread from running.
Perhaps your completion block isn't getting called at all.
I would suggest putting a breakpoint or log message where you call stopAnimating and see if you're getting to that point at all. From there, you can diagnose the source of the problem. But the code provided in the question is not the source of the problem.

Progress bar while synchronous save to parse.com

Anyone know how I can show a UIProgressView while I save synchronous to parse.com?
I try to show a progress view before I start the sync save and hide it after the save is done, but this doesn't work. It doesn't show the progress view and just start save right away.
I am starting to think that the sync save takes all the power from everything else and a async save is the best for this issue. But in my case I have to save synchronous since I show the saved data directly after it is saved.
Anyone know how this can be done?
self.startProgress()
self.saveSynchronousToParse()
self.stopProgress()
A 'synchronous' method is also known as a 'blocking' method - it effectively blocks the current thread until it completes.
By default your app is running in the main queue, and this is the queue that performs all of the UI tasks, so once you call "saveSynchronousToParse" (which presumably calls save or some similar synchronous Parse function) your UI will freeze until the task completes. You will probably receive a warning in the console that you are executing a synchronous task on the main thread.
A progress view doesn't really make sense in this case, because you don't get any feedback from the Parse API as to how much progress has been made in saving the information.
A UIActivityView makes more sense. You can use the following to achieve what you are after using an UIActivityView
self.activityView.startAnimating()
self.somePFObject.saveInBackgroundWithBlock {
(success: Bool!, error: NSError!) -> Void in
dispatch_async(dispatch_get_main_queue(),{
self.activityView.stopAnimating()
});
if success {
println("success")
} else {
println("\(error)")
}
}

Resources