search in firestore swift - ios

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}"])

Related

How to filter data in searchController using Share Class in swift 4

I am using UISearchController to filter data. Here is my code:
var filteredData: [ShareObj] = [ShareObj]()
var arrHotelData : [ShareObj] = [ShareObj]()
extension AddAddressVC : UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
if let searchText = searchController.searchBar.text {
filteredData.removeAll()
let type2Array = arrHotelData.filter { $0.strHotelName == searchText.lowercased()}
print(type2Array)
tableView.reloadData()
}
}
}
Here is my code to save data
for i in 0..<arrData.count
{
let ShareObj : ShareObj = AddHotelShareObj()
let dict = arrData[i]
if (dict[""]) != nil {
ShareObj.strHotelName = dict[""] as! String
}
if (dict[""]) != nil {
ShareObj.strAddress = dict[""] as! String
}
if (dict[""]) != nil {
ShareObj.strZipCode = dict[""] as! String
}
self.arrHotelData.append(ShareObj)
But array is returning []. Can you help me why it's not working
Try this:
func updateSearchResults(for searchController: UISearchController) {
if let searchText = searchController.searchBar.text {
filteredData.removeAll()
let filterPredicate = NSPredicate(format: "self contains[c] %#", argumentArray: [searchText])
let type2Array = self.arrHotelData.filter { filterPredicate.evaluate(with: $0.strHotelName) }
print(type2Array)
tableView.reloadData()
}
}
you can try this:
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
filteredArray.removeAll()
filteredArray = arrHotelData.filter({$0.lowercased().contains(searchText.lowercased())})
self.tableView.reloadData()
}
Here you should use filteredData to display and replace the code
let ShareObj : ShareObj = AddHotelShareObj() to
let shareObj : ShareObj = AddHotelShareObj() because same reference and Type may cause problem.

Firebase Swift - Realtime update does not work when result is filtered by search controller

I am making an iOS app using Swift and Firebase. The view controller retrieves/observes items and stores them in items array. Item cell has + and - buttons that perform an update on the quantity of the item using its indexPath.
If I use search controller to filter items, I get to store search results in filteredItems array. I am trying to update quantity of these filtered items but they only get to update once when I tap + or - button and does not show the update in search result view (no separate view, I display the filteredItems using data source in the same view). Even if I hit it multiple times, it always updates once.
Once I go back to the regular view by canceling search bar, I see 1 up or down depends on which button I tapped. Does anyone know what might be causing the problem here?
class ItemsViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, SortTypeTableViewControllerDelegate, ItemListCellDelegate {
private var items = [Item]()
private var filteredItems = [Item]()
private func retrieveFirebaseData(sortType: ItemSort.SortType, sortOrder: ItemSort.SortOrder) {
guard let currentUser = Auth.auth().currentUser else {
return print("user not logged in")
}
let itemsRef = DatabaseReferenceHelper.usersRef.child(currentUser.uid).child("items")
itemsRef.queryOrdered(byChild: sortType.rawValue).observe(.value) { (snapshot) in
var newItems: [Item] = []
for item in snapshot.children {
let item = Item(snapshot: item as! DataSnapshot)
if self.displayFavoritesOnly == true {
if item.favorite == true {
newItems.append(item)
}
} else {
newItems.append(item)
}
}
self.items = sortOrder == .ascending ? newItems : newItems.reversed()
self.collectionView.reloadData()
}
}
// this is from item cell delegate
func increaseDecreaseQuantity(_ sender: ItemListCell, increment: Bool) {
guard let tappedIndexPath = collectionView.indexPath(for: sender) else {
return
}
let item: Item
item = isFiltering() ? filteredItems[tappedIndexPath.item] : items[tappedIndexPath.item]
let updatedQuantity = increment == true ? item.quantity + 1 : item.quantity - 1
guard let currentUser = Auth.auth().currentUser else {
return print("user not logged in")
}
let itemsRef = DatabaseReferenceHelper.usersRef.child(currentUser.uid).child("items")
itemsRef.child(item.key).updateChildValues(["quantity": updatedQuantity])
}
// Here's the search logic I learned from Ray Wenderlich
private func searchBarIsEmpty() -> Bool {
return searchController.searchBar.text?.isEmpty ?? true
}
private func filterContentForSearchText(_ searchText: String, scope: String = "All") {
filteredItems = items.filter({$0.title.lowercased().contains(searchText.lowercased())})
collectionView.reloadData()
}
private func isFiltering() -> Bool {
return searchController.isActive && !searchBarIsEmpty()
}
extension ItemsViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
filterContentForSearchText(searchController.searchBar.text!)
}
}
Reload your collectionView after each + & -. Once you reload your collection view, new updates will be visible.
I fixed the issue. All I did was adding another parameter called "searchText" in retrieveFirebaseData function and use the function in filterContentForSearchText.
Basically, I needed to filter items UNDER observe method like in the code below.
private func retrieveFirebaseData(sortType: ItemSort.SortType, sortOrder: ItemSort.SortOrder, searchText: String?) {
guard let currentUser = Auth.auth().currentUser else {
return print("user not logged in")
}
let itemsRef = DatabaseReferenceHelper.usersRef.child(currentUser.uid).child("items")
itemsRef.queryOrdered(byChild: sortType.rawValue).observe(.value) { (snapshot) in
if let searchText = searchText {
// FILTER HERE
self.filteredItems = self.items.filter({$0.title.lowercased().contains(searchText.lowercased())})
} else {
var newItems: [Item] = []
for item in snapshot.children {
let item = Item(snapshot: item as! DataSnapshot)
if self.displayFavoritesOnly == true {
if item.favorite == true {
newItems.append(item)
}
} else {
newItems.append(item)
}
}
self.items = sortOrder == .ascending ? newItems : newItems.reversed()
}
self.collectionView.reloadData()
}
}
private func filterContentForSearchText(_ searchText: String, scope: String = "All") {
retrieveFirebaseData(sortType: itemSortType, sortOrder: itemSortOrder, searchText: searchText)
}
"filterContentForSearchText" function used to filter items and reload table like this:
private func filterContentForSearchText(_ searchText: String, scope: String = "All") {
filteredItems = items.filter({$0.title.lowercased().contains(searchText.lowercased())})
collectionView.reloadData()
}

Handle multiple request Alamofire while Search

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 {
}
}

Why the first completionHandler return the data before all methods are called?

Here is my API code
import UIKit
import Alamofire
import SwiftyJSON
class ItunesApi: NSObject
{
var artistArray:Array <Artist> = []
func downloadData(name:String, _ completionHandler: #escaping (_ result: Array<Artist>) -> Void)
{
Alamofire.request("https://itunes.apple.com/search?term=U2&entity=musicArtist").response
{ response in
if let data = response.data
{
let JsonResult = JSON(data)
self.findDiscography(data: JsonResult)
}
completionHandler(self.artistArray)
}
}
func findDiscography (data: JSON)
{
for (subJson):(String, JSON) in data["results"]
{
if let artistName = subJson.1["artistName"].string
{
print(artistName);
self.downloadDiscography(name: artistName)
}
}
}
func downloadDiscography (name: String)
{
Alamofire.request("https://itunes.apple.com/search?term=U2&entity=album").response
{ response in
if let data = response.data
{
let JsonResult = JSON(data)
self.createDataModel(name: name, data: JsonResult)
}
}
}
func createDataModel (name: String, data: JSON)
{
var albums:Array <Album> = []
for (subJson):(String, JSON) in data["results"]
{
var thumbnail:String = ""
var title:String = ""
var year:String = ""
if let thumbImage = subJson.1["artworkUrl60"].string
{
thumbnail = thumbImage;
}
if let titleString = subJson.1["collectionName"].string
{
title = titleString;
}
if let releaseDate = subJson.1["releaseDate"].string
{
year = releaseDate;
}
let album = Album(_thumbnail: thumbnail, _title: title, _year: year)
albums.append(album)
}
let artist = Artist(_name: name, _musicStyle: "Rock", _albums: albums as NSArray);
self.artistArray.append(artist);
}
}
And I call here in MyClassTableView.m
func searchBarSearchButtonClicked(_ searchBar: UISearchBar)
{
if let artist = searchBar.text
{
self.itunesApi.downloadData(name: artist, { (array) in
print(array);
})
}
}
Why the copmletionHandler return before all method are called? I want to return in first completionHandeler the result of all method but it return before. The self.itunesApi.downloadData return [] instead of an array filled

Google Maps Autocomplete

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

Resources