I'm having SearchContoller and whenever user starts searching I have to display the result. But continuous API calls over here sometimes old result remains even if there is no data received from API.
How to queue them so that there won't be any misplaced data viewed.
fileprivate func getData(searchString: String){
getFriendList = false
guard let currentUser = AppController.shared.currentUser else { return }
// self.friendsList.removeAll()
APIHandler.shared.doAPIGetCallForUrl(Constants.kcFindFriends + "?UserId=" + String(currentUser.userId) + "&seachString=" + searchString + "&PageNumber=" + String(pageNumber) , callback: { [weak self](success, jsonData, error) in
guard let weakSelf = self else { return }
guard success == true else {
// weakSelf.findFriendsTableView.reloadData()
return
}
guard let json = jsonData else { return }
guard let findFriendList: [FriendList] = json.value() else { return }
weakSelf.friendsList.append(contentsOf: findFriendList)
weakSelf.findFriendsTableView.reloadData()
})
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
searchString = searchText
pageNumber = 1
getData(searchString: searchString)
}
Define a var for request in your viewController and in its setter cancel any old request
var searchRequest: Request?{
didSet{
oldValue?.cancel()
}
}
In your function pass the request to that var, So whenever you'll make a new search it'll cancel the old one.
searchRequest = Alamofire.request("URL", method: .post, parameters: nil)
.responseJSON(completionHandler: { (response) in
//Handle response
})
#BhavinRamani Reply
func searchBar(searchBar: UISearchBar!, textDidChange searchText: String!) {
NSObject.cancelPreviousPerformRequestsWithTarget(withTarget: self, selector: #selector(YourView.getData(searchString:)), object: searchBar)
if !searchBar.text.isEmpty {
dispatch_after_delay(0.25) {
searchString = searchText
pageNumber = 1
getData(searchString: searchString)
}
} else {
}
}
Related
I have a big array of alimentobject named baseOuChercheAliments.
I have to search in it with the searchbar.
when i do that, with the code below, the table view display the result of the search of the first letter tapped, and not all the words tapped.
In fact, the search of the list of the first letter takes time and this is what is displayed in the end and not the search of what is tapped totaly in the search bar.
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
DispatchQueue.global(qos: .default).async { [self] in
let searchTextBeingQuireid = searchText
var searchedRecords = [AlimentObject]()
let words = Set(searchText.split(separator: " ").map(String.init))
if searchText.isEmpty {
searchedRecords = [] //baseOuChercheAliments
} else {
var baseDeDonneesAlimentsFiltreeClassique = baseOuChercheAliments.filter{$0.nomAliment.range(of: searchText, options: .anchored) != nil}
baseDeDonneesAlimentsFiltreeClassique.sort(by: { $0.nomAliment < $1.nomAliment})
var baseDeDonneesAlimentsFiltreeComplexe = baseOuChercheAliments.filter { object in words.allSatisfy { word in object.nomAliment.localizedCaseInsensitiveContains(word) } }
baseDeDonneesAlimentsFiltreeComplexe.sort(by: { $0.nomAliment < $1.nomAliment })
var soustraction = Array(Set(baseDeDonneesAlimentsFiltreeComplexe).subtracting(baseDeDonneesAlimentsFiltreeClassique))
soustraction.sort(by: { $0.nomAliment > $1.nomAliment })
searchedRecords = baseDeDonneesAlimentsFiltreeClassique + soustraction
}
if searchText == searchTextBeingQuireid {
DispatchQueue.main.async {
baseDeDonneesAlimentsFiltree = searchedRecords
self.tableView.reloadData()
}
}
}
}
Could someone tell me how to fix that in order to display the result of what is totally tapped in the search bar and not only the result of the search of the first letter tapped in the searchbar.
Blocks capturing function arguments by value, that's why when you check at the end of the filtration searchText == searchTextBeingQuireid it'll always be true.
Instead, you need to store current searchText in your class, and compare with it during filtration.
As you need to stop outdated filtration as fast as you can, you need to check if searchText was changed after each long operation. I've added checks between each operation, but it may be redundant, you can only leave checks after operations which take time(can check it with prints)
private var searchText = ""
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
self.searchText = searchText
DispatchQueue.global(qos: .default).async { [self] in
var searchedRecords = [AlimentObject]()
let words = Set(searchText.split(separator: " ").map(String.init))
if searchText.isEmpty {
searchedRecords = [] //baseOuChercheAliments
} else {
var baseDeDonneesAlimentsFiltreeClassique = baseOuChercheAliments.filter{$0.nomAliment.range(of: searchText, options: .anchored) != nil}
if self.searchText != searchText {
return
}
baseDeDonneesAlimentsFiltreeClassique.sort(by: { $0.nomAliment < $1.nomAliment})
if self.searchText != searchText {
return
}
var baseDeDonneesAlimentsFiltreeComplexe = baseOuChercheAliments.filter { object in words.allSatisfy { word in object.nomAliment.localizedCaseInsensitiveContains(word) } }
if self.searchText != searchText {
return
}
baseDeDonneesAlimentsFiltreeComplexe.sort(by: { $0.nomAliment < $1.nomAliment })
if self.searchText != searchText {
return
}
var soustraction = Array(Set(baseDeDonneesAlimentsFiltreeComplexe).subtracting(baseDeDonneesAlimentsFiltreeClassique))
if self.searchText != searchText {
return
}
soustraction.sort(by: { $0.nomAliment > $1.nomAliment })
if self.searchText != searchText {
return
}
searchedRecords = baseDeDonneesAlimentsFiltreeClassique + soustraction
}
if self.searchText == searchText {
DispatchQueue.main.async {
baseDeDonneesAlimentsFiltree = searchedRecords
self.tableView.reloadData()
}
}
}
}
I'm trying to apply search in my app but it shows a random product not the same of what I'm typing at searchBar
method :
static func getSearch ( name : String ,completion : #escaping (_ product : ProductObject) -> ()) {
let path = Firestore.firestore().collection("Products").whereField("name" , isLessThanOrEqualTo: name)
path.addSnapshotListener { (query, error) in
if error != nil {return}
guard let doucments = query?.documents else {return}
for doc in doucments {
if let data = doc.data() as [String: AnyObject]? {
let newData = ProductObject(dictionary: data)
completion (newData)
}
}
}
}
at searchBar text did change :
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
self.products = []
ProductApi.getSearch(name: searchText) { (pro) in
self.products.append(pro)
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
if searchTxt.text?.count == 0 {
DispatchQueue.main.async { searchBar.resignFirstResponder() }
}
collectionView.reloadData()
}
this was the best way I found :
let path = Firestore.firestore().collection("Products").order(by: "name").start(at: [name]).end(at: ["(name) \u{f8ff}"])
Is there a way to show in the GMSAutocompleteResultsViewController custom predefined results like home/work address, favorites or recent places?
I have the places saved in UserDefaults just don't know how to show them when clicking on the search bar, before any text input is introduced.
I manage to do this using MapKit and custom table view:
func updateSearchResults(for searchController: UISearchController) {
print("----UPDATE SEARCH RESULTS")
guard let mapView = mapView,
let searchBarText = searchController.searchBar.text
else {
return
}
matchingItems = []
if(searchBarText == " "){
print("Text: SPACE")
if let location = currentLocation{
matchingItems = [location]
}
if(home != nil){
addressToPlacemark(searchParam: home!, region: nil, reload: false)
}
if(work != nil){
addressToPlacemark(searchParam: work!, region: nil, reload: false)
}
} else if(searchBarText.characters.count >= 3){
addressToPlacemark(searchParam: searchBarText, region: mapView.region, reload: true)
} else{
self.tableView.reloadData()
}
}
func addressToPlacemark(searchParam: String, region: MKCoordinateRegion?, reload: Bool) {
let request = MKLocalSearchRequest()
request.naturalLanguageQuery = searchParam
if let region = region{
request.region = region
}
let search = MKLocalSearch(request: request)
search.start { (response, error) in
print("ERROR: \(String(describing: error))")
guard let response = response else {
print("NO VALID RESPONSE")
self.tableView.reloadData()
return
}
self.matchingItems.append(contentsOf: response.mapItems.map({ (item:MKMapItem) -> MKPlacemark in
item.placemark
}))
var uniqueItems:[MKPlacemark] = []
for item in self.matchingItems{
let first = uniqueItems.first(where: { (uniqueItem:MKPlacemark) -> Bool in
uniqueItem.title == item.title && uniqueItem.coordinate.latitude == item.coordinate.latitude && uniqueItem.coordinate.longitude == item.coordinate.longitude
})
if(first == nil){
uniqueItems.append(item)
}
}
self.matchingItems = uniqueItems
if(reload || self.matchingItems.count == self.savedAddressesCount){
print("Reloaded data")
self.tableView.reloadData()
}
}
}
I've "tricked" the UISearchController to show the table view by programmatically adding a space when clicked:
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
print("searchBarTextDidBeginEditing - CLICK")
searchBar.text = " "
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if(searchText.characters.count > 1 && searchText.hasPrefix(" ")){
searchBar.text = searchText.substring(from: searchText.index(searchText.startIndex, offsetBy: 1))
print("searchBarTextDidChange - TRIM")
}else if(searchText.isEmpty){
searchBar.text = " "
print("searchBarTextDidChange - EMPTY")
}
}
Can I do something similar using Google Maps? Is this even possible just by using GMSAutocompleteResultsViewController or should I use a custom ResultsViewController?
Suppose I have a SearchText object as such:
class SearchText: Object {
dynamic var text: String = ""
}
I would like to create an accessor such that I can get that object if it exists or create one if it doesn't. This is what I have.
extension Realm {
var searchText: SearchText {
if let searchText = objects(SearchText.self).first {
return searchText
} else {
let searchText = SearchText()
try! write {
add(searchText)
}
return searchText
}
}
This kinda works but here is my problem. I would like to use searchText and also update its value. Something like:
func updateSearchText(text: String) {
try! write {
searchText?.text = text
}
}
When I try to updateSearchText, I get a realm exception of Realm is already in a write transaction. I think it's because I'm nesting to writes: creating the text and then updating it.
How would I do this elegantly?
Realm doesn't provide nested transaction currently. There is a way to avoid the exception is check you're in write transaction before open a transaction. Like the following:
func updateSearchText(text: String) {
func update() {
searchText?.text = text
}
if inWriteTransaction {
update()
} else {
try! write {
update()
}
}
}
Maybe searchText setter also should be:
var searchText: SearchText {
if let searchText = objects(SearchText.self).first {
return searchText
} else {
let searchText = SearchText()
if inWriteTransaction {
add(searchText)
} else {
try! write {
add(searchText)
}
}
return searchText
}
}
Within my Swift iOS app, I am using this library to show Google Place auto complete: https://github.com/watsonbox/ios_google_places_autocomplete
This is what I have in my main view controller:
let gpaViewController = GooglePlacesAutocomplete(
apiKey: "myapikey",
placeType: .Address
)
gpaViewController.placeDelegate = self // Conforms to GooglePlacesAutocompleteDelegate
presentViewController(gpaViewController, animated: true, completion: nil)
This works well, but it takes me to a new view. How do I apply the autocomplete on a search field in my main view controller without having to switch over to another view?
Hey use this swift code from GitHub
https://github.com/vijayvir/LeoGoogle/blob/master/AutoCompeteWithSearchBar/LeoGoogleAutoCompleteSearch.swift
#IBOutlet var leoGoogleAutoCompleteSearch: LeoGoogleAutoCompleteSearch!
override func viewDidLoad() {
super.viewDidLoad()
leoGoogleAutoCompleteSearch.closureDidUpdateSearchBarWithPredictions = { predictions in
predictions.map({ (prediction ) -> Void in
print(prediction.placeId ?? "NG" , " πΈπΈ " , prediction.description ?? "NG" )
})
}
Here I have made the object of autocomplete api which will return the place_id and description of the search through closure . further Developer can modify the code . For request I have use Almofire for get reguest , which is common these days .
more about code
mport Foundation
import GooglePlaces
import Alamofire
import UIKit
typealias LeoGoogleMapACompletionBlock = (AnyObject, AnyObject) -> Void
typealias LeoGoogleMapAFailureBlock = (AnyObject, AnyObject) -> Void
struct LeoGoogleMapsApiPlaceAutocomplete {
static func get(url: URL,
completionHandler: LeoGoogleMapACompletionBlock? = nil,
failureHandler: LeoGoogleMapAFailureBlock? = nil) {
print("π«π«π«π«π«π«π« get :", url)
Alamofire.request(url,
method: .get
)
.responseJSON { response in
print(" get π¬π¬π¬π¬π¬π¬π¬ " )
if let json = response.result.value {
// print("WebServices : - ", json)
completionHandler!(json as AnyObject, response.result as AnyObject)
} else {
failureHandler?("" as AnyObject, "" as AnyObject)
}
}
.responseString { _ in
failureHandler?("" as AnyObject, "" as AnyObject)
// print("responseString Success: \(responseString)")
}
.responseData { _ in
}
}
struct Prediction {
var description : String?
var id : String?
var placeId : String?
init(dictionary : NSDictionary) {
description = dictionary["description"] as? String
id = dictionary["id"] as? String
placeId = dictionary["place_id"] as? String
}
}
var predictions: [Prediction] = []
init(response: AnyObject) {
if let searchList = response["predictions"] as? [Any] {
for object in searchList {
let tempPrediction = Prediction(dictionary: (object as? NSDictionary)!)
predictions.append(tempPrediction)
}
}
}
}
class LeoGoogleAutoCompleteSearch: NSObject {
#IBOutlet weak var searchBar: UISearchBar!
var closureDidUpdateSearchBar : ((LeoGoogleMapsApiPlaceAutocomplete)-> Void)?
var closureDidUpdateSearchBarWithPredictions : (([LeoGoogleMapsApiPlaceAutocomplete.Prediction])-> Void)?
}
extension LeoGoogleAutoCompleteSearch : UISearchBarDelegate {
func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool {
return true
}
func searchBarShouldEndEditing(_ searchBar: UISearchBar) -> Bool {
return true }
func searchBar(_ searchBar: UISearchBar, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
return true
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { // called when text changes (including clear)
webserviceSearchBy(text: searchBar.text!)
}
func webserviceSearchBy(text : String) {
let newString = text.replacingOccurrences(of: " ", with: "+")
let url : URL = URL(string: "https://maps.googleapis.com/maps/api/place/autocomplete/json?input=\(newString)&key=AIzaSyAVrXmSPxYourApiPK_ceurWlZJgrpWY")!
LeoGoogleMapsApiPlaceAutocomplete.get(url: url, completionHandler: { (response, _) in
let some : LeoGoogleMapsApiPlaceAutocomplete = LeoGoogleMapsApiPlaceAutocomplete(response: response)
self.closureDidUpdateSearchBar?(some)
self.closureDidUpdateSearchBarWithPredictions?(some.predictions)
}) { (response, _) in
}
}
}
Add these two variables ...
let autoPlaceURLString : String = "https://maps.googleapis.com/maps/api/place/autocomplete/json"
let apiKey = "your api key"
Now Set your UITextfield delegate to self. And on textfield change method call this below method...
fetchAutocompletePlaces(keyword: textField.text!)
You will receive an array of addresses...
func fetchAutocompletePlaces(keyword: String) {
let urlString = "\(autoPlaceURLString)?key=\(apiKey)&input=\(keyword)"
let s = (CharacterSet.urlQueryAllowed as NSCharacterSet).mutableCopy() as! NSMutableCharacterSet
s.addCharacters(in: "+&")
let encodedURL = urlString.addingPercentEncoding(withAllowedCharacters: s as CharacterSet)
Alamofire.request(encodedURL!).responseJSON { (response) in
if response.result.isSuccess {
if let json = response.result.value as? [String: Any] {
let predictions = json["predictions"]
var locations = [String]()
for dict in predictions as! [NSDictionary]{
locations.append(dict["description"] as! String)
}
DispatchQueue.main.async(execute: { () -> Void in
self.strAddressByGoogle = locations
if (self.strAddressByGoogle.count == 0){
self.tblAddress.isHidden = true
}else {
self.tblAddress.isHidden = false
}
self.tblAddress.reloadData()
})
}
}
}
}
Now show this list in a UITableView on the same view.
Why do not use Google Places API for iOS ? and follow the steps to do it in programmatically using fetcher. Link : https://developers.google.com/places/ios-api/autocomplete#get_place_predictions_programmatically