I am using this tutorial to auto-complete the google places.
I have installed the alamofire and swiftyJSON through cocoapads.
My issue is When I enter text in searchbar, nothing happens.
On my ViewControlller:
let gpaViewController = GooglePlacesAutocomplete(
apiKey: "MY-API-KEY",
placeType: .Address
)
gpaViewController.placeDelegate = self
presentViewController(gpaViewController, animated: true, completion: nil)
On my GooglePlacesAutoComplete View Controller:
import UIKit
import Alamofire
import SwiftyJSON
enum PlaceType: CustomStringConvertible {
case All
case Geocode
case Address
case Establishment
case Regions
case Cities
var description : String {
switch self {
case .All: return ""
case .Geocode: return "geocode"
case .Address: return "address"
case .Establishment: return "establishment"
case .Regions: return "regions"
case .Cities: return "cities"
}
}
}
struct Place {
let id: String
let description: String
}
protocol GooglePlacesAutocompleteDelegate {
func placeSelected(place: Place)
func placeViewClosed()
}
Only this set of codes is called:
class GooglePlacesAutocomplete: UINavigationController {
var gpaViewController: GooglePlacesAutocompleteContainer?
var placeDelegate: GooglePlacesAutocompleteDelegate? {
get { return gpaViewController?.delegate }
set { gpaViewController?.delegate = newValue }
}
convenience init(apiKey: String, placeType: PlaceType = .All) {
let gpaViewController = GooglePlacesAutocompleteContainer(
apiKey: apiKey,
placeType: placeType
)
self.init(rootViewController: gpaViewController)
self.gpaViewController = gpaViewController
let closeButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Stop, target: self, action: "close")
gpaViewController.navigationItem.leftBarButtonItem = closeButton
gpaViewController.navigationItem.title = "Enter Address"
}
func close() {
placeDelegate?.placeViewClosed()
}
}
No indication of this:
class GooglePlaceSearchDisplayController: UISearchDisplayController {
override func setActive(visible: Bool, animated: Bool) {
if active == visible { return }
searchContentsController.navigationController?.navigationBarHidden = true
super.setActive(visible, animated: animated)
searchContentsController.navigationController?.navigationBarHidden = false
if visible {
searchBar.becomeFirstResponder()
} else {
searchBar.resignFirstResponder()
}
}
}
// MARK: - GooglePlacesAutocompleteContainer
class GooglePlacesAutocompleteContainer: UIViewController {
var delegate: GooglePlacesAutocompleteDelegate?
var apiKey: String?
var places = [Place]()
var placeType: PlaceType = .All
convenience init(apiKey: String, placeType: PlaceType = .All) {
self.init(nibName: "GooglePlacesAutocomplete", bundle: nil)
self.apiKey = apiKey
self.placeType = placeType
}
override func viewDidLoad() {
super.viewDidLoad()
let tv: UITableView? = searchDisplayController?.searchResultsTableView
tv?.registerClass(UITableViewCell.self, forCellReuseIdentifier: "Cell")
}
}
Or this:
// MARK: - GooglePlacesAutocompleteContainer (UITableViewDataSource / UITableViewDelegate)
extension GooglePlacesAutocompleteContainer: UITableViewDataSource, UITableViewDelegate {
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return places.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.searchDisplayController?.searchResultsTableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell?
// Get the corresponding candy from our candies array
let place = self.places[indexPath.row]
// Configure the cell
cell!.textLabel!.text = place.description
cell!.accessoryType = UITableViewCellAccessoryType.DisclosureIndicator
return cell!
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
delegate?.placeSelected(self.places[indexPath.row])
}
}
// MARK: - GooglePlacesAutocompleteContainer (UISearchDisplayDelegate)
extension GooglePlacesAutocompleteContainer: UISearchDisplayDelegate {
func searchDisplayController(controller: UISearchDisplayController, shouldReloadTableForSearchString searchString: String!) -> Bool {
getPlaces(searchString)
return false
}
private func getPlaces(searchString: String) {
let url = "https://maps.googleapis.com/maps/api/place/autocomplete/json"
Alamofire.request(.GET, url).responseJSON { response in
switch response.result {
case .Success(let data):
let json = JSON(data)
let name = json["name"].stringValue
print(name)
case .Failure(let error):
print("Request failed with error: \(error)")
}
}
}
}
Apologies for long code. Am not sure, where I made a mistake.
I also set the delegate for searchBar in xib
I double checked the installation of Cocoapads, Alamofire and SwiftyJson. Can someone please help me? I'm new to these things..
You Have To Make Object Of UISearchBar Like This...
lazy var searchBar : UISearchBar = UISearchBar()
In Did Load
searchBar.delegate = self
Related
I'm trying to save data to the core data and then display it on another view controller. I have a table view with custom cell, which have a button. I've created a selector, so when we tap on the button in each of the cell, it should save all the data from the cell. Here is my parent view controller:
import UIKit
import SafariServices
import CoreData
class ViewController: UIViewController, UISearchBarDelegate {
#IBOutlet weak var pecodeTableView: UITableView!
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var savedNews = [SavedNews]()
var newsTitle: String?
var newsAuthor: String?
var urlString: String?
var newsDate: String?
var isSaved: Bool = false
private var articles = [Article]()
private var viewModels = [NewsTableViewCellViewModel]()
private let searchVC = UISearchController(searchResultsController: nil)
override func viewDidLoad() {
super.viewDidLoad()
pecodeTableView.delegate = self
pecodeTableView.dataSource = self
pecodeTableView.register(UINib(nibName: S.CustomCell.customNewsCell, bundle: nil), forCellReuseIdentifier: S.CustomCell.customCellIdentifier)
fetchAllNews()
createSearchBar()
loadNews()
saveNews()
countNewsToCategory()
}
#IBAction func goToFavouritesNews(_ sender: UIButton) {
performSegue(withIdentifier: S.Segues.goToFav, sender: self)
}
private func fetchAllNews() {
APICaller.shared.getAllStories { [weak self] result in
switch result {
case .success(let articles):
self?.articles = articles
self?.viewModels = articles.compactMap({
NewsTableViewCellViewModel(author: $0.author ?? "Unknown", title: $0.title, subtitle: $0.description ?? "No description", imageURL: URL(string: $0.urlToImage ?? "")
)
})
DispatchQueue.main.async {
self?.pecodeTableView.reloadData()
}
case .failure(let error):
print(error)
}
}
}
private func createSearchBar() {
navigationItem.searchController = searchVC
searchVC.searchBar.delegate = self
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 120
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModels.count
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: S.CustomCell.customCellIdentifier, for: indexPath) as! CustomNewsCell
cell.configure(with: viewModels[indexPath.row])
cell.saveNewsBtn.tag = indexPath.row
cell.saveNewsBtn.addTarget(self, action: #selector(didTapCellButton(sender:)), for: .touchUpInside)
return cell
}
#objc func didTapCellButton(sender: UIButton) {
guard viewModels.indices.contains(sender.tag) else { return }
print("Done")// check element exist in tableview datasource
if !isSaved {
saveNews()
print("success")
}
//Configure selected button or update model
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let article = articles[indexPath.row]
guard let url = URL(string: article.url ?? "") else {
return
}
let vc = SFSafariViewController(url: url)
present(vc, animated: true)
}
//Search
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
guard let text = searchBar.text, !text.isEmpty else {
return
}
APICaller.shared.Search(with: text) { [weak self] result in
switch result {
case .success(let articles):
self?.articles = articles
self?.viewModels = articles.compactMap({
NewsTableViewCellViewModel(author: $0.author ?? "Unknown", title: $0.title, subtitle: $0.description ?? "No description", imageURL: URL(string: $0.urlToImage ?? "")
)
})
DispatchQueue.main.async {
self?.pecodeTableView.reloadData()
self?.searchVC.dismiss(animated: true, completion: nil)
}
case .failure(let error):
print(error)
}
}
}
}
extension ViewController {
func loadNews() {
let request: NSFetchRequest<SavedNews> = SavedNews.fetchRequest()
do {
let savedNews = try context.fetch(request)
//Handle saved news
if savedNews.count > 0 {
isSaved = true
}
} catch {
print("Error fetching data from context \(error)")
}
}
func saveNews() {
//Initialize the context
let news = SavedNews(context: self.context)
//Putting data
news.title = newsTitle
news.author = newsAuthor
news.publishedAt = newsDate
news.url = urlString
do {
try context.save()
} catch {
print("Error when saving data \(error)")
}
}
func countNewsToCategory() {
//Initialize the context
let request: NSFetchRequest<SavedNews> = SavedNews.fetchRequest()
let predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
])
request.predicate = predicate
do {
savedNews = try context.fetch(request)
} catch {
print("Error fetching data from category \(error)")
}
}
}
I don't know where is the problem, I've created a correct data model, but data could not be saved. Here is my model:
import Foundation
struct APIResponse: Codable {
let articles: [Article]
}
struct Article: Codable {
let author: String?
let source: Source
let title: String
let description: String?
let url: String?
let urlToImage: String?
let publishedAt: String
}
struct Source: Codable {
let name: String
}
And also my model in Core Data:
My second view controller, to which I want display the data:
import UIKit
import CoreData
class FavouriteNewsViewController: UIViewController {
#IBOutlet weak var favTableView: UITableView!
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var savedNews = [SavedNews]()
override func viewDidLoad() {
super.viewDidLoad()
favTableView.delegate = self
favTableView.delegate = self
loadSavedNews()
favTableView.register(UINib(nibName: S.FavouriteCell.favouriteCell, bundle: nil), forCellReuseIdentifier: S.FavouriteCell.favouriteCellIdentifier)
// Do any additional setup after loading the view.
}
}
extension FavouriteNewsViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return savedNews.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = favTableView.dequeueReusableCell(withIdentifier: S.FavouriteCell.favouriteCellIdentifier, for: indexPath) as! FavouritesCell
print(savedNews)
let article = savedNews[indexPath.row]
if let articleTitle = article.title {
cell.favTitle.text = articleTitle
}
if let articleAuthor = article.author {
cell.favAuthor.text = articleAuthor
}
if let articleDesc = article.desc {
cell.favDesc.text = article.desc
}
return cell
}
}
extension FavouriteNewsViewController {
func loadSavedNews() {
let request: NSFetchRequest<SavedNews> = SavedNews.fetchRequest()
do {
savedNews = try context.fetch(request)
} catch {
print("Error fetching data from context \(error)")
}
}
func deleteNews(at indexPath: IndexPath) {
// Delete From NSObject
context.delete(savedNews[indexPath.row])
// Delete From current News list
savedNews.remove(at: indexPath.row)
// Save deletion
do {
try context.save()
} catch {
print("Error when saving data \(error)")
}
}
}
you did not assign your properties that you are trying to save
newsTitle,newsAuthor,newsDate,urlString
seems these properties have nil value . make sure these properties have valid value before save .
I need to search in the tableview.I have tableview and textfield.While clicking on the textfield it must search the tableview.
my model:-
class SearchModel: NSObject {
var restaurantname :String!
init?(dictionary :JSONDictionary) {
guard let name = dictionary["name"] as? String else {
return
}
self.restaurantname = name
}
}
my viewmodel:-
class SearchViewModel: NSObject {
var datasourceModel:SearchDataSourceModel
init(withdatasource newDatasourceModel: SearchDataSourceModel) {
datasourceModel = newDatasourceModel
}
func datafordisplay(atindex indexPath: IndexPath) -> SearchModel{
return datasourceModel.dataListArray![indexPath.row]
}
func numberOfRowsInSection(section:Int) -> Int {
return (datasourceModel.dataListArray?.count)!
}
func loadData(completion :#escaping (_ isSucess:Bool) -> ()){
loadFromWebserviceData { (newDataSourceModel) in
if(newDataSourceModel != nil)
{
self.datasourceModel = newDataSourceModel!
completion(true)
}
else{
completion(false)
}
}
}
//}
func loadFromWebserviceData(completion :#escaping (SearchDataSourceModel?) -> ()){
//with using Alamofire ..............
Alamofire.request("http://www.example.com").validate(statusCode: 200..<300).validate(contentType: ["application/json"]).responseJSON{ response in
switch response.result{
case .success(let data):
print("success",data)
let result = response.result
if let wholedata = result.value as? [String:Any]{
if let data = wholedata["data"] as? Array<[String:Any]>{
// print(data["name"] as! String)
print(data)
print(response)
let newDataSource:SearchDataSourceModel = SearchDataSourceModel(array: data)
completion(newDataSource)
// }
}
}
// case .failure(let data):
// print("fail",data)
case .failure(let encodingError ):
print(encodingError)
// if response.response?.statusCode == 404{
print(encodingError.localizedDescription)
completion(nil)
// }
}
}}
}
my datasource model:-
class SearchDataSourceModel: NSObject {
var dataListArray:Array<SearchModel>? = []
init(array :Array<[String:Any]>?) {
super.init()
var newArray:Array<[String:Any]> = []
if array == nil{
// newArray = self.getJsonDataStored44()
}
else{
newArray = array!
}
var datalist:Array<SearchModel> = []
for dict in newArray{
let model = SearchModel(dictionary: dict)
datalist.append(model!)
}
self.dataListArray = datalist
}
}
my viewcontroller:-
class SearchViewController: UIViewController,UITableViewDelegate,UITableViewDataSource, UISearchBarDelegate,UITextFieldDelegate {
#IBOutlet private weak var tableView: UITableView!
!
#IBOutlet weak var txt: UITextField!
var filteredSearchArray = NSMutableArray()
private var searchViewModel :SearchViewModel!
init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?, withViewModel viewModel:SearchViewModel) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
searchViewModel = viewModel
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var filteredData: [String]!
override func viewDidLoad() {
super.viewDidLoad()
txt.addTarget(self, action: #selector(textFieldDidChange(textField:)), for: UIControlEvents.editingChanged)
searchViewModel.loadData { (isSuccess) in
if(isSuccess == true)
{
self.tableView .reloadData()
}
else{
}
}
}
#objc private func textFieldDidChange(textField: UITextField) {
if textField.text == "" {
self .viewDidLoad()
}else{
filterContentForSearchText(searchText: textField.text!)
}
}
func filterContentForSearchText(searchText: String) {
filteredSearchArray = NSMutableArray(array:(searchViewModel.datasourceModel.dataListArray?.filter({(ele:AnyObject) -> Bool in
return (ele as! SearchModel).restaurantname.lowercased().contains(searchText.lowercased())
}))!)
tableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return searchViewModel.numberOfRowsInSection(section: section)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let identifier = "searchcell"
var cell: SearchCell! = tableView.dequeueReusableCell(withIdentifier: identifier) as? SearchCell
if cell == nil {
tableView.register(UINib(nibName: "SearchCell", bundle: nil), forCellReuseIdentifier: identifier)
cell = tableView.dequeueReusableCell(withIdentifier: identifier) as?SearchCell
}
cell.setsearchData(search: searchViewModel.datafordisplay(atindex: indexPath))
return cell
}
}
my tableviewcell:-
class SearchCell: UITableViewCell {
#IBOutlet weak var name: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func setsearchData(search:SearchModel)
{
self.name.text = search.restaurantname
}
}
This is my code but in the filteredSearchArray always shows as 0 element.
Here the list of names is displaying but clicking on the textfield it doesn't searching and not reloading. How to solve the problem.
The data is not coming in this code:-
func filterContentForSearchText(searchText: String) {
filteredSearchArray = NSMutableArray(array:(searchViewModel.datasourceModel.dataListArray?.filter({(ele:AnyObject) -> Bool in
return (ele as! SearchModel).restaurantname.lowercased().contains(searchText.lowercased())
}))!)
tableView.reloadData()
}
First of all one important thing don't use NSArray, NSDictionary in swift use Array, Dictionary instead.
I have made a change in your model class:
class SearchModel: NSObject {
var restaurantname :String!
init(dictionary :JSONDictionary) {
self.restaurantname = dictionary["name"] as? String ?? ""
}
}
Now filter your array like this:
filteredSearchArray = searchViewModel.datasourceModel.dataListArray?.filter {$0.restaurantname.lowercased().contains(searchText.lowercased())}
And now pass filteredSearchArray in tableView datasource then reload the table. You can take a bool variable isSearching to check which array you have to pass in datasource the filtered one the original. Toggle this isSearching in textFieldDidBeginEiditng and textFieldDidEndEiditng method.
I have tested it by below example in my playground:
var models: Array<SearchModel> = [SearchModel.init(dictionary: ["name": "Khalsa Dhaba"]), SearchModel.init(dictionary: ["name": "Yo! China"]), SearchModel.init(dictionary: ["name": "Sher-e-punjab"]), SearchModel.init(dictionary: ["name": "Haveli"])]
let result = models.filter {$0.restaurantname.lowercased().contains("o!".lowercased())}
print(result)
It works fine!
Secondly I noticed you're reloading your view by calling self.viewDidLoad() method which you should not call ever this calls itself and it is a function of viewContoller lifecycle. Call tableView.reloadData() instead.
searching in the textField of the tableView.
I need to search the names from the tableView. So for that i have tableView and textField in the UIViewController.
And i need to search from the Api. For that i have used the Alamofire method to fetch the data.
I need to search. Now i implement to display the names from the api to tableView And i got the output. But i need to implement searching .how to do in the mvvm.
my model:-
class SearchModel: NSObject {
var restaurantname :String!
init?(dictionary :JSONDictionary) {
guard let name = dictionary["name"] as? String else {
return
}
self.restaurantname = name
}
}
My viewmodel:-
class SearchViewModel: NSObject {
var datasourceModel:SearchDataSourceModel
init(withdatasource newDatasourceModel: SearchDataSourceModel) {
datasourceModel = newDatasourceModel
}
func datafordisplay(atindex indexPath: IndexPath) -> SearchModel{
return datasourceModel.dataListArray![indexPath.row]
}
func numberOfRowsInSection(section:Int) -> Int {
return (datasourceModel.dataListArray?.count)!
}
func loadData(completion :#escaping (_ isSucess:Bool) -> ()){
loadFromWebserviceData { (newDataSourceModel) in
if(newDataSourceModel != nil) {
self.datasourceModel = newDataSourceModel!
completion(true)
}
else {
completion(false)
}
}
}
//}
func loadFromWebserviceData(completion :#escaping (SearchDataSourceModel?) -> ()) {
//with using Alamofire ..............
Alamofire.request("http://www.example.com").validate(statusCode: 200..<300).validate(contentType: ["application/json"]).responseJSON{ response in
switch response.result {
case .success(let data):
print("success",data)
let result = response.result
if let wholedata = result.value as? [String:Any]{
if let data = wholedata["data"] as? Array<[String:Any]>{
// print(data["name"] as! String)
print(data)
print(response)
let newDataSource:SearchDataSourceModel = SearchDataSourceModel(array: data)
completion(newDataSource)
// }
}
}
// case .failure(let data):
// print("fail",data)
case .failure(let encodingError ):
print(encodingError)
// if response.response?.statusCode == 404 {
print(encodingError.localizedDescription)
completion(nil)
// }
}
}}
}
my DataSourcemodel:-
class SearchDataSourceModel: NSObject {
var dataListArray:Array<SearchModel>? = []
init(array :Array<[String:Any]>?) {
super.init()
var newArray:Array<[String:Any]> = []
if array == nil {
// newArray = self.getJsonDataStored44()
}
else {
newArray = array!
}
var datalist:Array<SearchModel> = []
for dict in newArray {
let model = SearchModel(dictionary: dict)
datalist.append(model!)
}
self.dataListArray = datalist
}
}
my viewController class:-
class SearchViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
#IBOutlet private weak var tableView: UITableView!
private var searchViewModel :SearchViewModel!
init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?, withViewModel viewModel:SearchViewModel) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
searchViewModel = viewModel
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
// tableView.dataSource = self
// filteredData = data
searchViewModel.loadData { (isSuccess) in
if(isSuccess == true) {
self.tableView .reloadData()
}
else {
}
}
// self.tableView .reloadData()
// Do any additional setup after loading the view.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return searchViewModel.numberOfRowsInSection(section: section)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let identifier = "searchcell"
var cell: SearchCell! = tableView.dequeueReusableCell(withIdentifier: identifier) as? SearchCell
if cell == nil {
tableView.register(UINib(nibName: "SearchCell", bundle: nil), forCellReuseIdentifier: identifier)
cell = tableView.dequeueReusableCell(withIdentifier: identifier) as? SearchCell
}
cell.setsearchData(search: searchViewModel.datafordisplay(atindex: indexPath))
// cell.name.text = searchViewModel.datafordisplay(atindex: indexPath)
// cell.name.text = filteredData[indexPath.row]
// cell.setsearchData(search: searchViewModel.datafordisplay(atindex: indexPath))
return cell
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
tableViewCell:-
class SearchCell: UITableViewCell {
#IBOutlet weak var name: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
func setsearchData(search:SearchModel) {
self.name.text = search.restaurantname
}
}
This is my code .Now how to implement the searching here.
There are t way of searching.
when you enter text and press the search button.
When You type then search without using the search button.
A solution for 1:
You need to call a method on the search button
Search button.
func Search() {
predicate = NSPredicate(format: "Self.YourSearchListName beginsWith[c]%#", SearchName)
GetsearchList = YourArrayForSearch.filtered(using: predicate) as NSArray
YourTable.reloadData()
}
2nd Solution
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if textField == self.YourTextFieldName{
Search()
}
}
for 2nd Solution don't forget to set the delegate.
filteredContactListArray is the array holding the filtered data to reload the tableview when user start searching for particular text.
#IBOutlet var searchCityTextField: UITextField!
var filteredSearchArray :SearchViewModel!
within in Viewdidload() function
searchCityTextField.addTarget(self, action: #selector(textFieldDidChange(textField:)), for: UIControlEvents.editingChanged)
UItextfieldDelegate Method
func textFieldDidChange(textField: UITextField) {
if textField.text == "" {
filteredSearchArray = searchViewModel// contactListArray is the actual array with all the list of data.
citiesTableView.reloadData()
}else{
filterContentForSearchText(textField.text!)
}
}
func filterContentForSearchText(searchText: String) {
filteredSearchArray = NSMutableArray(array:searchViewModel. dataListArray.filter({(ele:AnyObject) -> Bool in
return (ele as! searchViewModel).cityName.lowercased().contains(searchText.lowercased())
}))
citiesTableView.reloadData()
}
UItableviewDelegate Method
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredContactListArray.count
}
I created a UIViewController class with a tableView to show different places in the cells, i'm working with Alamofire and Google API (maps, places), the only problem is that when i run the project the cells are empty, this is my controller class:
import UIKit
class SelectClass: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var list : [QCategoryy] = [QCategoryy]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
self.title = "Categories"
list = NearbyPlaces.getCategories()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
list.sort() { $0.views > $1.views}
tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func backTapp(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
#IBAction func doneTapp(_ sender: Any) {
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return list.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let identifier = "CATEGORY_CELL"
let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath)
cell.textLabel?.text = list[indexPath.row].name
return cell
}
let nearbySearchSegueIdentifier = "goToMcourse"
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.performSegue(withIdentifier: nearbySearchSegueIdentifier, sender: list[indexPath.row])
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == nearbySearchSegueIdentifier {
guard let category = sender as? QCategoryy else {
return
}
if let vc = segue.destination as? CourseClass2 {
vc.category = category
}
}
}
}
extension QCategoryy {
private static let ketPrefix = "category-"
var views:Int {
get {
return UserDefaults.standard.integer(forKey: QCategoryy.ketPrefix + name)
}
}
func markView() {
UserDefaults.standard.set(views + 1, forKey: QCategoryy.ketPrefix + name)
}
}
and these are the two classes that work with it:
import Foundation
import UIKit
import CoreLocation
import Alamofire
class NearbyPlaces {
static func getCategories() -> [QCategoryy] {
let list:[QCategoryy] = ["Bakery", "Doctor", "School", "Taxi_stand", "Hair_care", "Restaurant", "Pharmacy", "Atm", "Gym", "Store", "Spa"]
return list
}
static let searchApiHost = "https://maps.googleapis.com/maps/api/place/nearbysearch/json"
static let googlePhotosHost = "https://maps.googleapis.com/maps/api/place/photo"
static func getNearbyPlaces(by category:String, coordinates:CLLocationCoordinate2D, radius:Int, token: String?, completion: #escaping (QNearbyPlacesResponse?, Error?) -> Void) {
var params : [String : Any]
if let t = token {
params = [
"key" : AppDelegate.googlePlacesAPIKey,
"pagetoken" : t,
]
} else {
params = [
"key" : AppDelegate.googlePlacesAPIKey,
"radius" : radius,
"location" : "\(coordinates.latitude),\(coordinates.longitude)",
"type" : category.lowercased()
]
}
Alamofire.request(searchApiHost, parameters: params, encoding: URLEncoding(destination: .queryString)).responseJSON { response in
if let error = response.error {
completion(nil, error)
}
if let response = QNearbyPlacesResponse(dic : response.result.value as? [String : Any]) {
completion(response, nil)
}
else {
completion(nil, QNearbyPlacesResponseError.noParsingDone)
}
}
}
static func googlePhotoURL(photoReference:String, maxWidth:Int) -> URL? {
return URL.init(string: "\(googlePhotosHost)?maxwidth=\(maxWidth)&key=\(AppDelegate.googlePlacesAPIKey)&photoreference=\(photoReference)")
}
}
enum QNearbyPlacesResponseError : Error {
case noParsingDone
}
struct QNearbyPlacesResponse {
var nextPageToken: String?
var status: String = "NOK"
var places: [QPlace]?
init?(dic:[String : Any]?) {
nextPageToken = dic?["next_page_token"] as? String
if let status = dic?["status"] as? String {
self.status = status
}
if let results = dic?["results"] as? [[String : Any]]{
var places = [QPlace]()
for place in results {
places.append(QPlace.init(placeInfo: place))
}
self.places = places
}
}
func canLoadMore() -> Bool {
if status == "OK" && nextPageToken != nil && nextPageToken?.characters.count ?? 0 > 0 {
return true
}
return false
}
}
and
struct QCategoryy {
var name:String
init(name:String) {
self.name = name
}
}
extension QCategoryy: ExpressibleByStringLiteral {
init(stringLiteral value: String) {
self.name = value
}
init(unicodeScalarLiteral value: String) {
self.init(name: value)
}
init(extendedGraphemeClusterLiteral value: String) {
self.init(name: value)
}
}
it seems to me that it's all right, i can not understand why when i go into my uiviewcontroller the tableView has all the empty cells, here there is also a screen of what i see when running the project
hope someone can find the issue
Try with:
tableView.dataSource = self
Try adding:
tableView.dataSource = self
tableView.delegate = self`
to your viewDidLoad()
I am using library SJSegmentedViewController for my project, github link to pod
Problem:
I have main view controller (FilterVC) on which I have a button "APPLY", on its action I want to access an array stored in another viewcontroller (FilterSkillVC), I am doing this using delegation, but still what I get is an empty instance
UPDATED
MY FilterVC code
import UIKit
import SJSegmentedScrollView
protocol FilterVCDelegate {
func btnApply()
}
class FilterVC: UIViewController {
var selectedSegment: SJSegmentTab?
var segmentedVC : SJSegmentedViewController?
var vcDelegate : FilterVCDelegate?
#IBOutlet weak var containerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
segmentViewInitialization()
}
#IBAction func btnApply(_ sender: Any) {
vcDelegate?.btnApply()
}
}
extension FilterVC {
var titles: [String] {
return [SegmentTitles.skillSet.rawValue,
SegmentTitles.cuisines.rawValue,
SegmentTitles.others.rawValue ]
}
var tabs: [String] {
return [StoryboardId.skillSet.rawValue,
StoryboardId.skillSet.rawValue,
StoryboardId.others.rawValue ]
}
func segmentViewInitialization() {
segmentedVC = CSSegment.setupTabs(storyboard: self.storyboard, tabs: tabs, titles: titles) as? SJSegmentedViewController
segmentedVC?.delegate = self
segmentedVC?.selectedSegmentViewHeight = 2.0
segmentedVC?.segmentTitleColor = .white
segmentedVC?.selectedSegmentViewColor = AppColor.secondary.value
segmentedVC?.segmentBackgroundColor = AppColor.primary.value
segmentedVC?.segmentViewHeight = 64.0
segmentedVC?.segmentShadow = SJShadow.light()
segmentedVC?.segmentTitleFont = AppFont.avenirMedium.size(14.0)
containerView.addSubview((segmentedVC?.view)!)
segmentedVC?.view.frame = containerView.bounds
}
}
extension FilterVC: SJSegmentedViewControllerDelegate {
func didMoveToPage(_ controller: UIViewController, segment: SJSegmentTab?, index: Int) {
if index != tabs.count-1 {
let filterVC = controller as! FilterSkillVC
filterVC.updateCurrentHeader(currentTab:SegmentTitles(rawValue: titles[index])!)
}
if selectedSegment != nil {
selectedSegment?.titleColor(.white)
}
if (segmentedVC?.segments.count)! > 0 {
selectedSegment = segmentedVC?.segments[index]
selectedSegment?.titleColor(AppColor.secondary.value)
}
}
}
My Skill VC code
import UIKit
class FilterSkillVC: UIViewController {
#IBOutlet var tblView: UITableView!
var instance = FilterVC()
lazy var arraySkills = [JSTags]()
lazy var arrayCuisines = [JSTags]()
var arrayID = [String]()
var currentHeader: SegmentTitles = .skillSet
override func viewDidLoad() {
super.viewDidLoad()
apiSkillCall()
apiCuisineCall()
registerCell(cellId: .filterListCell, forTableView: tblView)
tblView.tableFooterView = UIView()
// let instance = FilterVC()
instance.vcDelegate = self
}
func updateCurrentHeader(currentTab : SegmentTitles){
currentHeader = currentTab
tblView.reloadData()
}
//MARK: ----- Custom Methods
func countForHeader() -> NSInteger {
switch currentHeader {
case .skillSet:
return arraySkills.count
case .cuisines:
return arrayCuisines.count
default:
return 0
}
}
func titleForHeader(_ index: NSInteger) -> (name: String?, obj: AnyObject?) {
switch currentHeader {
case .skillSet:
return (name: arraySkills[index].name, obj: arraySkills[index])
case .cuisines:
return (name: arrayCuisines[index].name, obj: arrayCuisines[index])
default:
return (name: nil, obj: nil)
}
}
//MARK: ----- Handle Response Methods
func handleSkillsResponse(response: Response) {
switch response{
case .success(let response):
if let skills = response as? [JSTags] {
self.arraySkills = skills
}
case .failure(let str):
Alerts.shared.show(alert: .oops, message: /str, type: .error)
case .dataNotExist(let str):
Alerts.shared.show(alert: .oops, message: str, type: .info)
}
tblView.reloadData()
}
func handleCuisineResponse(response: Response) {
switch response{
case .success(let response):
if let cuisines = response as? [JSTags] {
self.arrayCuisines = cuisines
tblView.reloadData()
}
case .failure(let str):
Alerts.shared.show(alert: .oops, message: /str, type: .error)
case .dataNotExist(let str):
Alerts.shared.show(alert: .oops, message: str, type: .info)
}
}
//MARK: API Methods
func apiSkillCall() {
APIManager.shared.request(with: ProfileEndPoint.fetchSkills()) { (response) in
self.handleSkillsResponse(response: response)
}
}
func apiCuisineCall() {
APIManager.shared.request(with: ProfileEndPoint.fetchCuisines()) { (response) in
self.handleCuisineResponse(response: response)
}
}
}
extension FilterSkillVC : UITableViewDelegate, UITableViewDataSource, FilterListCellDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return countForHeader()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifiers.filterListCell.rawValue) as! FilterListCell
let filter = titleForHeader(indexPath.row)
cell.lblFilterLabel.text = filter.name
//Mark: Cell delegate
cell.delegate = self
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 70
}
//Mark: FilterCellDelegate Method
func buttonTapped(cell: FilterListCell) {
if let indexPath = self.tblView.indexPath(for: cell) {
print("Button tapped on row \(indexPath.row)")
if currentHeader == .skillSet {
arraySkills[indexPath.row].isSelected = !arraySkills[indexPath.row].isSelected
}
else {
arrayCuisines[indexPath.row].isSelected = !arrayCuisines[indexPath.row].isSelected
}
}
}
}
extension FilterSkillVC : FilterVCDelegate {
func btnApply() {
for object in arraySkills {
if object.isSelected {
arrayID.append((object.id) ?? "")
}
}
for object in arrayCuisines {
if object.isSelected {
arrayID.append((object.id) ?? "")
}
}
}
}
You are losing the reference to the instance as soon as the viewDidLoad method is completed.
Make instance a global Variable.
Like so :
import UIKit
class FilterSkillVC: UIViewController {
#IBOutlet var tblView: UITableView!
var instance = FilterVC() //New line added here.
lazy var arraySkills = [JSTags]()
lazy var arrayCuisines = [JSTags]()
var arrayID = [String]()
var currentHeader: SegmentTitles = .skillSet
override func viewDidLoad() {
super.viewDidLoad()
apiSkillCall()
apiCuisineCall()
registerCell(cellId: .filterListCell, forTableView: tblView)
tblView.tableFooterView = UIView()
//let instance = FilterVC() //Commented this.
instance.vcDelegate = self
}
More updates :
In the didMoveToPage method, you are getting a reference to a FilterVC (from a storyboard ??), now this instance of FilterVC is different from the instance of filterVC we created.
Please add this change and try :
func didMoveToPage(_ controller: UIViewController, segment: SJSegmentTab?, index: Int) {
if index != tabs.count-1 {
let filterVC = controller as! FilterSkillVC
filterVC.updateCurrentHeader(currentTab:SegmentTitles(rawValue: titles[index])!)
self.vcDelegate = filterVC // <== Updated this line.
}