search the tableview using mvvm - ios

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.

Related

Table View Not ReloadData after Unfollow User swift

I am using firebase when I follow user his post show up nicely in the UITableView . but when unfollow him , his post still in the UITableView. UITableView not reloading data after unfollow.
View Controller :
import UIKit
import SVProgressHUD
import SDWebImage
class HomeVC: UIViewController {
#IBOutlet weak var tableViewOutLet: UITableView!
#IBOutlet weak var activityIndicatorView: UIActivityIndicatorView!
var postContentArray = [Post]() // contine all Posts .
var userContentArray = [UserModel]()
override func viewDidLoad() {
super.viewDidLoad()
tableViewOutLet.dataSource = self
tableViewOutLet.estimatedRowHeight = 521
tableViewOutLet.rowHeight = UITableView.automaticDimension
loadPosts()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.tabBarController?.tabBar.isHidden = false
}
#objc func loadPosts () {
// activityIndicatorView.startAnimating()
API.Feed.ObserveFeedWithMyPostAndFollowerPost(withid: API.User.Curren_User!.uid) { (postr) in
self.fetchUserInfo(PostUserID: postr.userPostId!, Completed: {
self.postContentArray.append(postr)
// self.activityIndicatorView.stopAnimating()
self.tableViewOutLet.reloadData()
})
}
API.Feed.ObserveFeedReomved(withid: API.User.Curren_User!.uid) { (key) in
// print(key)
self.postContentArray = self.postContentArray.filter {$0.userPostId != key }
self.tableViewOutLet.reloadData()
}
}
#objc func fetchUserInfo (PostUserID : String , Completed : #escaping ()-> Void) {
API.User.observeUserInformation(CommentUserID: PostUserID) { (User) in
self.userContentArray.append(User)
Completed()
}
}
#IBAction func logOutButton(_ sender: Any) {
AuthServices.logout(OnSuccess: {
SVProgressHUD.showSuccess(withStatus: "تم تسجيل الخروج")
let storyBoard = UIStoryboard(name: "Start", bundle: nil)
let signInvc = storyBoard.instantiateViewController(withIdentifier: "SignInVC")
self.present(signInvc, animated: true, completion: nil)
}) { (error) in
SVProgressHUD.showError(withStatus: error)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "commentIconSequeToCommentPage" {
let commentvc = segue.destination as! CommentsVC
commentvc.postID = sender as? String
}
}
}
extension HomeVC: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return postContentArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "PostCell", for: indexPath) as! HomeTableViewCell
let postContent = postContentArray[indexPath.row]
let userContent = userContentArray[indexPath.row]
cell.postContent = postContent
cell.userContentInfo = userContent
cell.homeView = self
return cell
}
}
Observe Functions :
import Foundation
import FirebaseDatabase
class FeedAPI {
var REF_FEED = Database.database().reference().child("Feed")
func ObserveFeedWithMyPostAndFollowerPost (withid id : String , complation : #escaping (Post)->Void) {
REF_FEED.child(id).observe(DataEventType.childAdded) { (snapshto : DataSnapshot) in
let key = snapshto.key
API.Post.observePostFuntion(withID: key, Complation: { (post) in
complation(post)
})
}
}
func ObserveFeedReomved (withid id : String , complation : #escaping (String)->Void) {
REF_FEED.child(id).observe(DataEventType.childRemoved) { (snapshot : DataSnapshot) in
let key = snapshot.key
complation(key)
}
}
}
where exactly is the unfollow function?
Also you should set the tableview delegate
override func viewDidLoad() {
tableViewOutLet.delegate = self
}
Please show where exactly are you preforming the unfollow function.
my follow And unfollow func
class FollowAPI {
var REF_FOLLOWERS = Database.database().reference().child("Follower")
var REF_FOLLOWING = Database.database().reference().child("Following")
func followAction (withId id : String) {
API.Follow.REF_FOLLOWERS.child(id).child(API.User.Curren_User!.uid).setValue(true)
API.Follow.REF_FOLLOWING.child(API.User.Curren_User!.uid).child(id).setValue(true)
API.myPOSTS.REF_MYPOSTS.child(id).observeSingleEvent(of: DataEventType.value) { (snapshot : DataSnapshot) in
print(snapshot.key)
if let dic = snapshot.value as? [String : Any]{
for key in dic.keys {
API.Feed.REF_FEED.child(API.User.Curren_User!.uid).child(key).setValue(true)
}
}
}
}
func UnFollowAction (withid id : String) {
API.Follow.REF_FOLLOWERS.child(id).child(API.User.Curren_User!.uid).setValue(NSNull())
API.Follow.REF_FOLLOWING.child(API.User.Curren_User!.uid).child(id).setValue(NSNull())
API.myPOSTS.REF_MYPOSTS.child(id).observeSingleEvent(of: DataEventType.value) { (snapshot : DataSnapshot) in
print(snapshot.key)
if let dic = snapshot.value as? [String : Any]{
for key in dic.keys {
API.Feed.REF_FEED.child(API.User.Curren_User!.uid).child(key).removeValue()
}
}
}
}
func isFollwoing (userid : String , Complated : #escaping (Bool)->Void) {
REF_FOLLOWERS.child(userid).child(API.User.Curren_User!.uid).observeSingleEvent(of: .value) { (snapshot ) in
if let _ = snapshot.value as? NSNull {
Complated(false)
} else {
Complated(true)
}
}
}
}

searching in the textfield of the tableview

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
}

variable not available in URLSESSION

I am doing in mvvm method.my code as below.
foodModel :-
class foodModel: NSObject {
var name :String!
var city :String!
var imageurl :String!
init?(dictionary :JSONDictionary) {
guard let name = dictionary["name"] as? String,
let imageurl = dictionary["imageurl"] as? String,
let city = dictionary["city"] as? String else {
return
}
self.name = name
self.city = city
self.imageurl = imageurl
}
}
foodDataSourceModel:-
class foodDataSourceModel: NSObject {
var dataListArray:Array<foodModel>? = []
init(array :Array<[String:Any]>?) {
super.init()
var newArray:Array<[String:Any]> = []
if array == nil{
newArray = self.getJsonDataStored1()
}
else{
newArray = array!
}
var datalist:Array<foodModel> = []
for dict in newArray{
let model = foodModel(dictionary: dict)
datalist.append(model!)
}
self.dataListArray = datalist
}
}
typealias dummyDataSource1 = foodDataSourceModel
extension dummyDataSource1{
func getJsonDataStored1() ->Array<Dictionary<String,String>>{
let jsonArray = [["name":"Anjapar","imageurl":"","city":"india"],["name":"Anjapar","imageurl":"","city":"india"]] as Array<Dictionary<String,String>>
return jsonArray
}
}
foodViewModel:-
class foodViewModel: NSObject {
var datasourceModel:foodDataSourceModel
init(withdatasource newDatasourceModel:foodDataSourceModel) {
datasourceModel = newDatasourceModel
}
func datafordisplay(atindex index: Int) -> foodModel {
return datasourceModel.dataListArray![index]
}
func numberOfItemsInSection(section:Int) -> Int {
return (datasourceModel.dataListArray?.count)!
}
func loadData(){
loadFromWebserviceData { (newDataSourceModel) in
self.datasourceModel = newDataSourceModel!
}
}
func loadFromWebserviceData(completion :#escaping (foodDataSourceModel?) -> ()){
let url = URL(string :"http://example.com")!
URLSession.shared.dataTask(with: url) { data, _, _ in
if let data = data {
let json = try! JSONSerialization.jsonObject(with: data, options: [])
//let dictionaries = json as! [JSONDictionary]
let array = json as! Array<[String:Any]>
let newDataSource:foodDataSourceModel = foodDataSourceModel(array: array)
// DispatchQueue.main.async {
//
// }
completion(newDataSource)
}
}.resume()
}
}
foodViewController:-
#IBOutlet private weak var tableView: UITableView!
private var foodViewModel :foodViewModel!
init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?, withViewModel viewModel:foodViewModel) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
foodViewModel = viewModel
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
// tableView.register(UINib(nibName: "foodCell", bundle: nil), forCellReuseIdentifier: "cell")
foodViewModel.loadData()
self.tableView .reloadData()
// Do any additional setup after loading the view.
}
// func numberOfSections(in tableView: UITableView) -> Int {
// return restaurtantViewModel.numberOfItemsInSection(section: Int)
// }
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:UITableViewCell = self.tableView.dequeueReusableCell(withIdentifier: "cell")! as! foodCell
//cell.setRestaurtantData(restaurtant: foodModel)
cell.textLabel?.text = "rtyuiop"
//
return cell
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
}
*/
}
foodCell:-
class foodCell: UITableViewCell {
#IBOutlet weak var name: UILabel!
#IBOutlet weak var city: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
func setRestaurtantData(restaurtant:foodModel)
{
self.name.text = restaurtant.name
self.city.text = restaurtant.city
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Here output is not getting .I need to display the values in tableview.Showing an error as failed to obtain a cell from dataSource
{
"query": "sandwich",
"results_count": 12,
"page": 1,
"results": [
{
"name": "Caffè Macs",
"coordinates": {
"lat": 37.330576,
"lng": -122.029739
},
"meals": ["breakfast", "lunch", "dinner"]
},
...
]
}
This is the pattern ,so from this i need to get the values as "name","lng" and "lat".
There is some problem in load the data from Json .In the urlsession task -showing as variable not available. please check .How to solve this?
Please update your viewDidLoad(). You must set delegate and datasource of tableview.
First set in class:-
class yourClass: UIViewController,UITableViewDelegate , UITableViewDataSource
Now
override func viewDidLoad() {
super.viewDidLoad()
// tableView.register(UINib(nibName: "foodCell", bundle: nil), forCellReuseIdentifier: "cell")
foodViewModel.loadData()
self.tableView .delegate = self
self.tableview.dataSource = self
self.tableView .reloadData()
When you fetch data from a web service, this is usually done in a asynchronous manner. In your viewDidLoad method, you're updating your table view immediately after the call which starts fetching the data.
override func viewDidLoad() {
...
// Start fetching the data
foodViewModel.loadData()
// Reloading the table view even though the data might not be ready
self.tableView.reloadData()
...
}
You can fix this by passing a callback to your loadData function in your foodViewModel:
func loadData(completion: (() -> ()) {
loadFromWebserviceData { (newDataSourceModel) in
self.datasourceModel = newDataSourceModel!
completion()
}
}
And in your foodViewController, you would then wait to update your table view until the data source is ready to be displayed:
override func viewDidLoad() {
...
foodViewModel.loadData {
self.tableView.reloadData()
}
...
}
While the code above should work, this is just to highlight the problem you're currently facing and therefore, was kept as simple as possible.

Handling Data between ViewControllers in SJSegmentedViewController

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

In Alamofire only the UINavigationController is called! no other response

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

Resources