Duplicate item in tableview when using searchbar - ios

This is the searchbar delegate that i use
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
if searchText.characters.count >= 2 {
self.setupinbox(searchText)
}
}
This is the function to get data from server
func setupinbox(q : String) {
arrayOfRels.removeAll(keepCapacity: false)
self.tableView.reloadData()
let session = NSURLSession.sharedSession()
let request = NSMutableURLRequest(URL: NSURL(string: "URL")!)
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.HTTPMethod = "POST"
let data = "devicetoken=\(devicetoken!)&q=\(q)&user_id=\(userid)"
request.HTTPBody = data.dataUsingEncoding(NSUTF8StringEncoding)
let task = session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
if let error = error {
print(error)
}
if let response = response {
let res = response as! NSHTTPURLResponse
dispatch_async(dispatch_get_main_queue(), {
if (res.statusCode >= 200 && res.statusCode < 300)
{
do {
let jsonData = try NSJSONSerialization.JSONObjectWithData(data!, options:NSJSONReadingOptions.MutableContainers ) as! NSArray
let json = JSON(jsonData)
for (_, subJSON): (String, JSON) in json[0]["interests"] {
let title = subJSON["title"].string
let eID = subJSON["ID"].string
let count = subJSON["count"].string
let rel1 = InboxInterests(title: title!,eventID : NSInteger(eID!)!, count: count!)
self.arrayOfRels.append(rel1)
}
} catch let error as NSError {
print(error)
}
self.tableView.reloadData()
} else {
let alert = UIAlertController(title: "Error", message: "Connection Failed", preferredStyle: .Alert)
alert.addAction(UIAlertAction(title: "Ok", style: .Default, handler: { (action: UIAlertAction) in
}))
self.presentViewController(alert, animated: true, completion: nil)
}
}
)}
})
task.resume()
}
When i type searchbar slowly, everything is ok, i see results correctly.
But when i type 2 letters very quickly, i get duplicate item.
I also get duplicate items when i remove letters quickly
Tried something like delaying the textDidChange but it didnt work.
Any idea what causes this and how can i fix?

You should put an extra line of code:
do {
arrayOfRels.removeAll(keepCapacity: false)
........

Related

Wait for task return on session.dataTask

I have a project where I send and get data back from an API. Sending and getting it works fine.
My goal is to wait a response (ie: yes|no) from the server and depending on it, proceed with the segue or not and show an alert if no.
For this part I have:
a tableview view with a list of clients and a button to add new client.
a detail view where I add/edit a client
on detail view there is a save button with a segue to the tableview view
The function saveClient() gets the data and makes a request to the server
func saveClient() -> Bool {
let name = txtName.text ?? ""
let address = txtAddress.text ?? ""
let city = txtCity.text ?? ""
let province = txtProvince.text ?? ""
let postal_code = txtPostalCode.text ?? ""
meal = Client(name:name, client_id: "", postal_code: postal_code, province: province, city: city, address: address)
var jsonData = Data()
let jsonEncoder = JSONEncoder()
do {
jsonData = try jsonEncoder.encode(meal)
}
catch {
}
print("a1")
var success: Bool
success = false
makeRequestPost(endpoint: "http://blog.local:4711/api/clients/add",
requestType: "POST",
requestBody: jsonData,
completionHandler: { (response : ApiContainer<Client>?, error : Error?) in
if let error = error {
print("error calling POST on /todos")
print(error)
return
}
let b = (response?.meta)!
print(b.sucess)
if(b.sucess == "yes") {
success = true
}
else
{
DispatchQueue.main.async(execute: {
let myAlert = UIAlertController(title: "Error", message: "Error creating Client", preferredStyle: .alert)
let okAction = UIAlertAction(title: "Ok", style: .default, handler: nil)
myAlert.addAction(okAction)
self.present(myAlert, animated: true, completion: nil)
})
return
}
} )
return success
}
on this same controller:
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
guard let button = sender as? UIBarButtonItem, button === btnSave else {
return false
}
if !saveClient() {
print("no sir")
return false
}
print("yes sir")
return true
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
guard let button = sender as? UIBarButtonItem, button === btnSave else {
return
}
}
request function:
func makeRequestPost<T>(endpoint: String,
requestType: String = "GET",
requestBody: Data, completionHandler: #escaping (ApiContainer<T>?, Error?) -> ()) {
guard let url = URL(string: endpoint) else {
print("Error: cannot create URL")
let error = BackendError.urlError(reason: "Could not create URL")
completionHandler(nil, error)
return
}
var urlRequest = URLRequest(url: url)
let session = URLSession.shared
urlRequest.httpMethod = "POST"
urlRequest.httpBody = requestBody
urlRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
urlRequest.addValue("application/json", forHTTPHeaderField: "Accept")
let task = session.dataTask(with: urlRequest, completionHandler: {
(data, response, error) in
guard let responseData = data else {
print("Error: did not receive data")
completionHandler(nil, error)
return
}
guard error == nil else {
completionHandler(nil, error!)
return
}
do {
let response = try JSONDecoder().decode(ApiContainer<T>.self, from: responseData)
completionHandler(response, nil)
}
catch {
print("error trying to convert data to JSON")
print(error)
completionHandler(nil, error)
}
})
task.resume()
}
On the console i get:
a1
no sir
yes
The correct would be:
a1
yes
yes sir
Final notes:
I tried some examples w semaphores... but it didn't work.
I'm using a var named meal on other places and haven't changed it to clients. However, it doesn't interfere on that part of the code
You said:
My goal is to wait a response (ie: yes|no) from the server and depending on it, proceed with the segue or not
While I understand the natural appeal of that approach, the reality is that you never should "wait" (especially in shouldPerformSegue). Your UI will freeze (especially notable if the user has poor Internet connectivity), your app is susceptible to being killed by the watchdog process that looks for frozen apps, etc.
So, rather than performing the segue and having shouldPerformSegue try to wait for the network response to see if it's OK to proceed, do it the other way around: You can write code that performs the query and if everything is good, only then initiate segue programmatically.

Unable to Json response is not working

Swift 3.0 (Xcode 8.3)
I'm trying to make a small program, that send a username to a web data base. I have found out how to send first_name, last_name,dob,owner_mobile,owner_email,
owner_password,choice_for_verification, by using in POST method. I obtain a JSON string earlier, then when I try to parse it, I get the above error on the try NSJSONSerialization line, on the as keyword. What did I do wrong? Thanks for your answers.
#IBAction func Login_Action(_ sender: Any)
{
var responseString : String!
var request = URLRequest(url: URL(string: "http://dev.justpick2go.com/cpanel/api/owner/ownerregistration.php")!)
request.setValue("Application/x-www.ownerregistration.php.com", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
let postString = "first_name=\(txtFirstName.text!)&last_name=\(txtLastName.text!)&dob=\(txtDOB.text!)&owner_mobile=\(txtMobileNo.text!)&owner_email=\(txtEmailID.text!)&owner_password=\(txtPassword.text!)" //&choice_for_verification=\(email)" // sending a parameters
print("\(postString)")
request.httpBody = postString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data, error != nil else { //checking for fundamental error
print("Error is =\(String(describing: error))")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200
{ // checking for http errors
print("statusCode should be 200 , but is\(httpStatus.statusCode)")
print("response is =\(String(describing: response))")
}
responseString = String(data: data, encoding: .utf8)
print("ResponseString=\(responseString!)")
do {
let json : NSDictionary! = try! JSONSerialization.jsonObject(with: data, options: .mutableContainers) as! NSDictionary
self.parseTheJSonData(JsonData: json)
}
catch
{
print(error)
}
}
task.resume()
}
func parseTheJSonData(JsonData : NSDictionary)
{
var successMessage : String = String()
var sampleCode : Int = Int()
let verificationAlert = UIAlertController()
if ((JsonData.value(forKey: "success") as! Int) == 1)
{
successMessage = "Login is Successful"
sampleCode = JsonData.value(forKey: "success") as! Int
verificationAlert.addAction(UIAlertAction(title: "No", style: .cancel, handler: nil))
verificationAlert.addAction(UIAlertAction(title: "Yes", style: .default, handler: { (Relogin) in
let Log = self.storyboard?.instantiateViewController(withIdentifier: "") as! LoginViewController
self.navigationController?.pushViewController(Log, animated: true)
self.present(Log, animated: true, completion: nil)
}))
}
else if ((JsonData.value(forKey: "success") as! Int) == 0)
{
sampleCode = JsonData.value(forKey: "success") as! Int
successMessage = "Please try again"
verificationAlert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
}
verificationAlert.title = successMessage
OperationQueue.main.addOperation
{
self.present(verificationAlert, animated: true, completion: nil)
}
}

IOS Swift - go to another view after getting stats from NURLSESSION

Once the user click the login button, i will call the func LoginClicked and get the status from api:
func LoginClicked(sender: AnyObject)
{
data_request{
(response) -> () in
let arrResponse = response.componentsSeparatedByString("|")
if (arrResponse[2] == "1"){
self.performSegueWithIdentifier("Login", sender: self)
}
else {
let alert = UIAlertController(title: "Login Failed", message: "Invalid Login!", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Click", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
}
}
}
func data_request(completion : (response:NSString) -> ()){
let txtUI : String = txtUsername!.text!
let txtPWD : String = txtPassword!.text!
let url = NSURL(string: "http://myweb.net/?UI=\(txtUI)&PW=\(txtPWD)")!
let request = NSURLRequest(URL: url)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task = session.dataTaskWithRequest(request, completionHandler: {
(
let data, let response, let error) in
guard let _:NSData = data, let _:NSURLResponse = response where error == nil else {
print("error")
return
}
let dataString = NSString(data: data!, encoding: NSUTF8StringEncoding)
completion(response : dataString!)
})
task.resume()
}
If success, it will move to another view. Otherwise, show failed alert. it will hit the error BAD_EXECUTION_INSTRUCTION when calling self. in both condition.
After get the hint from #Tj3n, it can do the proper action now.
func data_request(completion : (response:NSString) -> ()){
let txtUI : String = txtUsername!.text!
let txtPWD : String = txtPassword!.text!
let url = NSURL(string: "http://myweb.net/?UI=\(txtUI)&PW=\(txtPWD)")!
let request = NSURLRequest(URL: url)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task = session.dataTaskWithRequest(request, completionHandler: {
(
let data, let response, let error) in
guard let _:NSData = data, let _:NSURLResponse = response where error == nil else {
print("error")
return
}
dispatch_async(dispatch_get_main_queue(), {
let dataString = NSString(data: data!, encoding: NSUTF8StringEncoding)
//let response = dataString?.componentsSeparatedByString("|")
//print(dataString)
//print(response![2])
let arrResponse = dataString!.componentsSeparatedByString("|")
if (arrResponse[2] == "1"){
self.performSegueWithIdentifier("Login", sender: self)
}
else {
let alert = UIAlertController(title: "Login Failed", message: "Invalid Login!", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Click", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
}
})
let dataString = NSString(data: data!, encoding: NSUTF8StringEncoding)
completion(response : dataString!)
})
task.resume()
}

Calling a func in other swift file which works with web-service returns nothing

In swift 2 When I'm communicating with a web-service and when I write these codes in button action it works fine.
let alert = UIAlertController(title: "Alert", message: "Message", preferredStyle: .Alert)
let ok = UIAlertAction(title: "OK", style: .Default, handler: { (action) -> Void in })
alert.addAction(ok);
let request = NSMutableURLRequest(URL: NSURL(string: "http://www.myaddress.com/web-service/iostest.aspx")!)
request.HTTPMethod = "POST"
var postString = String();
postString = "uid=1";
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
guard error == nil && data != nil else {
alert.title="Error"
alert.message = "Connection error"
dispatch_async(dispatch_get_main_queue()){
self.presentViewController(alert, animated: true, completion: nil)
}
return
}
if let httpStatus = response as? NSHTTPURLResponse where httpStatus.statusCode != 200 {
alert.title="Error"
alert.message = "Server error"
dispatch_async(dispatch_get_main_queue()){
self.presentViewController(alert, animated: true, completion: nil)
}
}
let responseString = NSString(data: data!, encoding: NSUTF8StringEncoding)
alert.title="Info"
alert.message = responseString as? String
dispatch_async(dispatch_get_main_queue()){
self.presentViewController(alert, animated: true, completion: nil)
}
}
task.resume()
As I said this works fine but as I want to do this from different ViewControls as well I have created a swift file which contains a struct and a static func in that struct that returns the the "responseString" so I could alert it in the view control. Something like this:
struct globalClass {
static func sendInfo(url: String, data: String) -> (answer: String, errorCode: Int32) {
var res = String();
var err = Int32();
err = 0;
let request = NSMutableURLRequest(URL: NSURL(string: url)!);
request.HTTPMethod = "POST";
let postString: String = data;
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding);
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
guard error == nil && data != nil else {
err = 1;
return;
}
if let httpStatus = response as? NSHTTPURLResponse where httpStatus.statusCode != 200 {
err = 2;
return;
}
let responseString = NSString(data: data!, encoding: NSUTF8StringEncoding);
res = (responseString as? String)!;
}
task.resume();
return (res, err);
}
But now when I call this func from my button it shows me an empty alert very fast that it seems like it didn't get anything from web-service and didn't even try too.
I put these in the button action:
#IBAction func btnData(sender: AnyObject) {
let y: String = "uid=1";
let res = globalClass.sendInfo("http://www.myaddress.com/web-service/iostest.aspx", data: y);
let alert = UIAlertController(title: "", message: "", preferredStyle: .Alert);
let OK = UIAlertAction(title: "OK", style: .Default, handler: nil);
alert.addAction(OK);
if (res.errorCode==0) {
alert.title = "Info";
alert.message = res.answer;
} else if (res.errorCode==1) {
alert.title = "Error";
alert.message = "Error connecting to server";
} else {
alert.title = "Error";
alert.message = "Server returned an error";
}
dispatch_async(dispatch_get_main_queue()){
self.presentViewController(alert, animated: true, completion: nil);
};
}
Thanks for the help,
Afshin Mobayen Khiabani
globalClass.sendInfo uses async call - dataTaskWithRequest. The result of the request will be delivered in completion of this method. But you don't wait for that result, instead you try to use sendInfo like a sync function.
To be able to deliver the result from dataTaskWithRequest's completion, put your own completion into sendInfo and invoke this completion (closure) when the result is delivered. An example
struct GlobalClass {
static func sendInfo(url: String, data: String, completion: (answer: String?, errorCode: Int32?) -> Void) {
// you code here which prepares request
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
// you parse the result here
// you deliver the result using closure
completion(string, error)
}
task.resume();
}
}
And an example of usage:
func usage() {
GlobalClass.sendInfo("url", data: "data") { (answer, errorCode) in
// your answer and errorCode here
// handle the result
}
}
static func sendInfo(url: String, data: String, completion: (answer: String, errorCode: Int32) -> ()){
//Your code..
let responseString = NSString(data: data!, encoding: NSUTF8StringEncoding);
res = (responseString as? String)!;
completion(answer: res, errorCode: err)
}
task.resume()
}
Then when you call the sendInfo, call like so:
sendInfo(url: "your url", data: "your data") { (result, error) in
//you use your result and error values as u want.
}

How to execute two asynchronous functions sequentially

func getTopicIdFromMYSQL(){
let myUrl = NSURL(string: "xxxx")
let request = NSMutableURLRequest(URL: myUrl!)
request.HTTPMethod = "POST"
let email:String = "xxx#gmail.com"
let postString = "email=\(email)"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request){
data, response, error in
if(error != nil){
print("Get all topic")
print("error=\(error)")
return
}
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers) as? NSDictionary
if let parseJSON = json
{
let resultValue = parseJSON["status"] as? String
print("Get all topic")
favouriteTopic = parseJSON["getResult"]! as! [AnyObject]
print("return topic:\(favouriteTopic)")
dispatch_async(dispatch_get_main_queue(), {
if(resultValue == "Success"){
}
else{
let error = UIAlertController(title: "Error", message: "Please check your network configuration!:-(", preferredStyle: .Alert)
let cancel = UIAlertAction(title: "Cancel", style: .Cancel, handler: nil)
let ok = UIAlertAction(title: "OK", style: .Default, handler: nil)
error.addAction(cancel)
error.addAction(ok)
}
})
}
}catch
{
print(error)
}
}
task.resume()
}
I have already got this code, how ever, I want to run another function after it. What should I do. like:
getTopicIdFromMYSQL()
getCommentFromMYSQL()
print("Finish")
I find the problem is my code does not excute in order, the function getCommentFromMYSQL is almost the same as getTopicIdFromMYSQL, I want to run these three in order what should I do?
Add a completion handler to your asynchronous functions as a parameter:
func getTopicIdFromMYSQL(completion: (AnyObject?, ErrorType?)->())
func getCommentFromMYSQL(completion: (AnyObject?, ErrorType?)->())
Note:
The completion handler must be eventually called when the asynchronous function completes - either with an error or the computed value.
You then call these functions as shown below:
getTopicIdFromMYSQL() { (result1, error) in
if let result1 = result1 {
// process result1
// ...
getCommentFromMYSQL() { (result2, error) in
if let result2 = result2 {
// process result2
// ...
} else {
// handle error
}
}
} else {
// handle error
}
}
You may implement these functions as follows:
func getTopicIdFromMYSQL(completion: (AnyObject?, ErrorType?) {
let myUrl = NSURL(string: "xxxx")
let request = NSMutableURLRequest(URL: myUrl!)
request.HTTPMethod = "POST"
let email:String = "xxx#gmail.com"
let postString = "email=\(email)"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request){
data, response, error in
if error != nil {
completion(nil, error)
}
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers) as? NSDictionary
if let parseJSON = json {
let resultValue = parseJSON["status"] as? String
print("Get all topic")
favouriteTopic = parseJSON["getResult"]! as! [AnyObject]
completion(favouriteTopic, nil)
} else {
throw MyError.Error(message: "bogus JSON")
}
} catch let error {
completion(nil, error)
}
}
task.resume()
}
The code is always executed in order. I think your problem is that you are making an asynchronous call in this line:
NSURLSession.sharedSession().dataTaskWithRequest(request)
But, as the code is executed sequentially, the method getCommentFromMYSQL() is called before the asynchronous call finishes. You should call getCommentFromMYSQL() inside this conditional:
if (resultValue == "Success") {
}
Or whenever you want. That will ensure the method getCommentFromMYSQL() to be called after the first method finishes its execution.
This is because, you are making async calls, which run in background thread.
If you want them to be executed in order, call the next method after dispatching to main queue,
dispatch_async(dispatch_get_main_queue(), {
//call here
getCommentFromMYSQL()
if(resultValue == "Success"){
}
else{
let error = UIAlertController(title: "Error", message: "Please check your network configuration!:-(", preferredStyle: .Alert)
let cancel = UIAlertAction(title: "Cancel", style: .Cancel, handler: nil)
let ok = UIAlertAction(title: "OK", style: .Default, handler: nil)
error.addAction(cancel)
error.addAction(ok)
}
})

Resources