How to add search functionality to table view which has yelp api data? - ios

I want to add restaurant category search functionality to RestaurantTableViewController which use yelp api business data. I followed basic search bar in table view tutorials but I do not do for my specific scenario. I do not differ filtered data and not filtered data in my RestaurantTableViewController when the search is active.
RestaurantTableViewController is below:
import UIKit
import CoreLocation
protocol ListActions: class {
func didTapCell(_ viewController: UIViewController, viewModel: RestaurantListViewModel)
}
class RestaurantTableViewController: UIViewController, UITableViewDelegate, FiltersViewControllerDelegate {
#IBOutlet weak var yourLocationLabel: UILabel!
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var searchBar: UISearchBar!
private let locationManager = CLLocationManager()
private let locationService = LocationService()
var filteredData: [RestaurantListViewModel]!
let appDelegate = UIApplication.shared.delegate as? AppDelegate
var viewModels = [RestaurantListViewModel]() {
didSet {
DispatchQueue.main.async {
// this no more loading, i notice it load late that is why when reload data in table view not working
}
}
}
weak var delegate: ListActions?
override func viewDidLoad() {
super.viewDidLoad()
filteredData = appDelegate!.data
userCurrentLocation()
DispatchQueue.main.async {
self.tabBarController?.tabBar.isHidden = false
}
if appDelegate?.data?.count == 0 {
tableView.setEmptyView(title: "There are no restaurants in your current location.", message: "Please change your location and try again!")
}
tableView.reloadData()
}
override func viewWillAppear(_ animated: Bool) {
if appDelegate?.data?.count == 0 {
tableView.setEmptyView(title: "There are no restaurants in your current location.", message: "Please change your location and try again!")
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("did appear")
self.removeActivityIndicator()
tableView.reloadData()
tableView.dataSource = self
tableView.delegate = self
DispatchQueue.main.async {
self.tabBarController?.tabBar.isHidden = false
}
}
}
extension RestaurantTableViewController: UITableViewDataSource {
// MARK: - Table view data source
func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier {
return "RestaurantCell"
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("number of rows \(String(describing: viewModels.count))")
print(viewModels)
if appDelegate?.data?.count == 0 {
self.tableView.setEmptyView(title: "There are no restaurants in your current location.", message: "Please change your location and try again!")
}
if (self.searchBar.isUserInteractionEnabled) {
return self.filteredData.count
}
else {
return appDelegate!.data?.count ?? 0
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "RestaurantCell", for: indexPath) as! RestaurantTableViewCell
if (self.searchBar.isUserInteractionEnabled) {
let vm = filteredData?[indexPath.row]
cell.configure(with: vm!)
return cell
} else {
let vm = appDelegate!.data?[indexPath.row]
cell.configure(with: vm!)
return cell
}
}
// MARK: - Delegate
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let detailsViewController = storyboard?.instantiateViewController(withIdentifier: "DetailsViewController")
else {return}
navigationController?.pushViewController(detailsViewController, animated: true)
let vm = appDelegate!.data?[indexPath.row]
appDelegate!.didTapCell(detailsViewController, viewModel: vm!)
if let selectedRowIndexPath = self.tableView.indexPathForSelectedRow {
self.tableView.deselectRow(at: selectedRowIndexPath, animated: true)
}
}
}
extension RestaurantTableViewController: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
filteredData = searchText.isEmpty ? appDelegate?.data : appDelegate?.data?.filter { (item: RestaurantListViewModel) -> Bool in
return item.categories[0].title.range(of: searchText, options: .caseInsensitive, range: nil, locale: nil) != nil
}
tableView.reloadData()
}
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
self.searchBar.showsCancelButton = true
}
}
My RestaurantTableViewCell data is below and I am getting restaurant table view cell data in configure function. But in RestaurantTableViewController's cellForRow method I do not differ my filtered and normal data when search is active.
class RestaurantTableViewCell: UITableViewCell {
#IBOutlet weak var cardContainerView: ShadowView!
#IBOutlet weak var restaurantImageView: UIImageView!
#IBOutlet weak var makerImageView: UIImageView!
#IBOutlet weak var restaurantNameLabel: UILabel!
#IBOutlet weak var locationLabel: UILabel!
#IBOutlet weak var restaurantType: UILabel!
let cornerRadius : CGFloat = 10.0
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
cardContainerView.layer.cornerRadius = cornerRadius
func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
cardContainerView.layer.shadowColor = UIColor.gray.cgColor
cardContainerView.layer.shadowOffset = CGSize(width: 5.0, height: 5.0)
cardContainerView.layer.shadowRadius = 15.0
cardContainerView.layer.shadowOpacity = 0.9
restaurantImageView.layer.cornerRadius = cornerRadius
restaurantImageView.clipsToBounds = true
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
func configure(with viewModel: RestaurantListViewModel) {
// For background thread
DispatchQueue.global(qos: .background).async {
DispatchQueue.main.async {
self.restaurantImageView.af_setImage(withURL: viewModel.imageUrl)
self.restaurantNameLabel.text = viewModel.name
self.locationLabel.text = "\(viewModel.formattedDistance) m"
}
}
if let restaurantType: String = String(viewModel.categories[0].title) {
self.restaurantType.text = restaurantType
}
}
}
RestaurantListViewModel is below also:
struct Business: Codable {
let id: String
let name: String
let imageUrl: URL
let distance: Double
let categories: [Category]
}
struct RestaurantListViewModel {
let name: String
let imageUrl: URL
let distance: Double
let id: String
let categories: [Category]
}
extension RestaurantListViewModel {
init(business: Business) {
self.name = business.name
self.id = business.id
self.imageUrl = business.imageUrl
self.distance = business.distance
self.categories = business.categories
}
}

Related

Passing data from Table view cell using button delegate

I want to pass the data from one view controller to another view controller when the user clicked the button . I am using button with delegate to pass the table view cell values into different view controller view . In second view controller I have two labels and one image to display the fields but the problem is when I clicked the button it is empty.
Here is the cell code .
import UIKit
protocol CellSubclassDelegate: AnyObject {
func buttonTapped(cell: MovieViewCell)
}
class MovieViewCell: UITableViewCell {
weak var delegate:CellSubclassDelegate?
static let identifier = "MovieViewCell"
#IBOutlet weak var movieImage: UIImageView!
#IBOutlet weak var movieTitle: UILabel!
#IBOutlet weak var movieOverview: UILabel!
#IBOutlet weak var someButton: UIButton!
#IBAction func someButtonTapped(_ sender: UIButton) {
self.delegate?.buttonTapped(cell: self)
}
override func prepareForReuse() {
super.prepareForReuse()
self.delegate = nil
}
func configureCell(title: String?, overview: String?, data: Data?) {
movieTitle.text = title
movieOverview.text = overview
if let imageData = data{
movieImage.image = UIImage(data: imageData)
// movieImage.image = nil
}
}
}
Here is the first view controller code .
import UIKit
class MovieViewController: UIViewController, UISearchBarDelegate {
#IBOutlet weak var userName: UILabel!
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var searchBar: UISearchBar!
private var presenter: MoviePresenter!
var finalname = ""
var movieTitle = ""
var movieOverview = ""
var movieImage : UIImage?
override func viewDidLoad() {
super.viewDidLoad()
userName.text = "Hello: " + finalname
setUpUI()
presenter = MoviePresenter(view: self)
searchBarText()
}
private func setUpUI() {
tableView.dataSource = self
tableView.delegate = self
}
private func searchBarText() {
searchBar.delegate = self
}
#IBAction func selectSegment(_ sender: UISegmentedControl) {
if sender.selectedSegmentIndex == 0{
setUpUI()
presenter = MoviePresenter(view: self)
presenter.getMovies()
}
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchText == ""{
presenter.getMovies()
}
else {
presenter.movies = presenter.movies.filter({ movies in
let originalTitle = movies.originalTitle.lowercased().range(of: searchText.lowercased())
let overview = movies.overview.lowercased().range(of: searchText.lowercased())
let posterPath = movies.posterPath.lowercased().range(of: searchText.lowercased())
return (originalTitle != nil) == true || (overview != nil) == true || (posterPath != nil) == true}
)
}
tableView.reloadData()
}
}
extension MovieViewController: MovieViewProtocol {
func resfreshTableView() {
tableView.reloadData()
}
func displayError(_ message: String) {
let alert = UIAlertController(title: "Error", message: message, preferredStyle: .alert)
let doneButton = UIAlertAction(title: "Done", style: .default, handler: nil)
alert.addAction(doneButton)
present(alert, animated: true, completion: nil)
}
}
extension MovieViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
presenter.rows
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: MovieViewCell.identifier, for: indexPath) as! MovieViewCell
let row = indexPath.row
let title = presenter.getTitle(by: row)
let overview = presenter.getOverview(by: row)
let data = presenter.getImageData(by: row)
cell.delegate = self
cell.configureCell(title: title, overview: overview, data: data)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let dc = storyboard?.instantiateViewController(withIdentifier: "MovieDeatilsViewController") as! MovieDeatilsViewController
let row = indexPath.row
dc.titlemovie = presenter.getTitle(by: row) ?? ""
dc.overview = presenter.getOverview(by: row) ?? ""
dc.imagemovie = UIImage(data: presenter.getImageData(by: row)!)
self.navigationController?.pushViewController(dc, animated: true)
}
}
extension MovieViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
}
extension MovieViewController : CellSubclassDelegate{
func buttonTapped(cell: MovieViewCell) {
guard (self.tableView.indexPath(for: cell) != nil) else {return}
let customViewController = storyboard?.instantiateViewController(withIdentifier: "MovieDeatilsViewController") as? MovieDeatilsViewController
customViewController?.titlemovie = movieTitle
customViewController?.imagemovie = movieImage
customViewController?.overview = movieOverview
self.navigationController?.pushViewController(customViewController!, animated: true)
}
}
Here is the details view controller code .
class MovieDeatilsViewController: UIViewController {
#IBOutlet weak var movieImage: UIImageView!
#IBOutlet weak var movieTitle: UILabel!
#IBOutlet weak var movieOverview: UILabel!
var titlemovie = ""
var overview = ""
var imagemovie :UIImage?
override func viewDidLoad() {
super.viewDidLoad()
movieTitle.text = titlemovie
movieOverview.text = overview
movieImage.image = imagemovie
}
}
Here is the result when I clicked the button .
The problem is you don't update you're global properties when selecting each of you're row,
If you pass data over cell delegate and pass you're cell through delegate, you can pass data from cell like:
customViewController?.titlemovie = cell.movieTitle.text ?? ""
customViewController?.imagemovie = cell.movieImage.image
customViewController?.overview = cell.movieOverview.text ?? ""
of course it would be better to pass you're data model to you're cell. and then share that through you're delegate not share you're cell, like:
protocol CellSubclassDelegate: AnyObject {
func buttonTapped(cell: MovieModel)
}

My label.text is nil with Reusable library in swift

I'm trying to show the label with content "aaaaaaaaaaaaaaaaA". I'm using Reusable library. Although I connected IBOutlet right way, the label and imageView of cell did not show content.
This is my cell class
import UIKit
import Reusable
import SDWebImage
protocol ChooseMemberTableViewCellDelegate: AnyObject {
func addUserToGroup(forUser user: User)
}
class ChooseMemberTableViewCell: UITableViewCell, Reusable {
#IBOutlet weak var userImageView: UIImageView!
#IBOutlet weak var userNameLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
func setupCell(data: User) {
userNameLabel?.text = "aaaaaaaaaaaaaaaaA"
// userNameLabel?.text = "\(data.userName)"
// let url = URL(string: data.image)
// userImageView.sd_setImage(with: url ?? "", completed: nil)
}
}
This is my ViewController, which i register cell
//
// MembersViewController.swift
// Message
//
// Created by Minh Tâm on 1/9/20.
// Copyright © 2020 Minh Tâm. All rights reserved.
//
import UIKit
import Firebase
import Reusable
import Then
private enum Constants {
static let numberOfSection = 1
static let heightForRows: CGFloat = 60
}
final class ChooseMembersViewController: UIViewController {
#IBOutlet private weak var searchMembers: UISearchBar!
#IBOutlet private weak var listContacts: UITableView!
var searchUser = [User]()
private let database = Firestore.firestore()
var users = [User]()
private var currentUser = Auth.auth().currentUser
private var roomRepository = RoomRepository()
private var userRepository = UserRepository()
var groupName: String?
private var selectUserArray = [String]()
override func viewDidLoad() {
super.viewDidLoad()
// userRepository.fetchUser()
configListTableView()
fetchUser()
}
func configListTableView() {
listContacts.do {
$0.register(cellType: ChooseMemberTableViewCell.self)
$0.delegate = self
$0.dataSource = self
}
}
public func fetchUser() {
database.collection("users").getDocuments(){ (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
if let snapshot = querySnapshot {
for document in snapshot.documents {
let data = document.data()
let uid = data["uid"] as? String ?? ""
if uid != self.currentUser?.uid {
let newUser = User.map(uid: uid, dictionary: data)
self.users.append(newUser)
}
}
}
self.searchUser = self.users
self.listContacts.reloadData()
}
}
}
#IBAction func handleBack(_ sender: UIButton) {
self.dismiss(animated: false)
}
#IBAction func handleDone(_ sender: UIButton) {
guard let currentUser = currentUser, let groupName = groupName else { return }
selectUserArray.append(currentUser.uid)
let time = NSNumber(value: Int(NSDate().timeIntervalSince1970))
roomRepository.updateFirebase(groupName: groupName, time: time, selectUserArray: selectUserArray)
}
}
extension ChooseMembersViewController: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchText.isEmpty {
searchUser = users
} else {
searchUser = users.filter { $0.userName.lowercased().contains(searchText.lowercased()) }
}
listContacts.reloadData()
}
}
extension ChooseMembersViewController: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return Constants.numberOfSection
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return searchUser.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = listContacts.dequeueReusableCell(for: indexPath, cellType: ChooseMemberTableViewCell.self).then {
let user = searchUser[indexPath.row]
$0.setupCell(data: user)
}
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return Constants.heightForRows
}
}
extension ChooseMembersViewController: ChooseMemberTableViewCellDelegate {
func addUserToGroup(forUser user: User) {
let selectedUserUid = user.uid
selectUserArray.append(selectedUserUid)
}
}
Cell does not show information of label and imageView
ChooseMemberTableViewCell is correct, but I'd recommend use userNameLabel.text = "aaaaaaaaaaaaaaaaA" (not optional for userNameLabel)
If you use cell from Storyboard into your tableView - no need to register this one, the UIStoryboard already auto-register its cells. I mean you should remove this line: listContacts.register(cellType: ChooseMemberTableViewCell.self)
You should add identifier ChooseMemberTableViewCell for cell in Storyboard
This should work for cellForRowAt:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: ChooseMemberTableViewCell = tableView.dequeueReusableCell(for: indexPath)
cell.setupCell(data: user)
.then {
let user = searchUser[indexPath.row]
$0.setupCell(data: user)
}
return cell
}
I guess that's all. I've done it and I see label with text in tableView

How I could save data using realm in tableview?

I have table where I can add some empty cells. Using protocol I can send some data to each cell. When I am looking into the table I can see all the information I sent. My problem happens when try to save date using realm, only save last sent data to cell.
// model:
import UIKit
import RealmSwift
class HITActionModel: Object {
#objc dynamic var seconds: Int = 0
#objc dynamic var color: String = ""
#objc dynamic var name: String = ""
#objc dynamic var id: Int = 0
var parentWorkout = LinkingObjects(fromType: WorkoutModel.self, property: "actionsArray")
}
class WorkoutModel: Object {
#objc dynamic var title: String?
#objc dynamic var rounds: Int = 0
var actionsArray = List<HITActionModel>()
}
//protocol
protocol HITActionCellDelegate {
func action(sec: Int, name: String, id: Int)
}
//custom cell
class HITActionCell: UITableViewCell {
//MARK: - Properties
#IBOutlet weak var changeColorBtn: UIButton!
#IBOutlet weak var actionLabel: UILabel!
#IBOutlet weak var actionNameTextField: UITextField!
var delegate: HITActionCellDelegate?
var numberOfSeconds = 0
var id = 0
//MARK: - Lifecycle
override func awakeFromNib() {
super.awakeFromNib()
selectionStyle = .none
}
//MARK: - Elements setup
func setupElements() {
actionNameTextField.placeholder = "Add name for Action!"
changeColorBtn.backgroundColor = UIColor.neonYellow
actionLabel.text = "Entry seconds for action"
}
#IBAction func addActionSeconds(_ sender: Any) {
numberOfSeconds += 1
actionLabel.text = "\(numberOfSeconds) SECONDS"
delegate?.action(sec: numberOfSeconds, name: actionNameTextField.text ?? "", id: self.id)
}
#IBAction func reduceActionSeconds(_ sender: Any) {
if numberOfSeconds > 0 {
numberOfSeconds -= 1
actionLabel.text = "\(numberOfSeconds) SECONDS"
delegate?.action(sec: numberOfSeconds, name: actionNameTextField.text ?? "", id: self.id)
}
}
}
// From here I send data using Protocol to viewController :
import UIKit
import RealmSwift
class AddActionViewController: UIViewController {
//MARK: - Properties
let workout = WorkoutModel()
let action = HITActionModel()
let realm = try! Realm()
var numberOfRounds = 0
#IBOutlet weak var workoutNameTextField: UITextField!
#IBOutlet weak var workoutNameLabel: UILabel!
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var roundsLabel: UILabel!
#IBOutlet weak var addRounds: UIButton!
#IBOutlet weak var reduceRounds: UIButton!
#IBOutlet weak var saveButton: UIButton!
#IBOutlet weak var cancelButton: UIButton!
#IBOutlet weak var addActionButton: UIButton!
//MARK:- Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
print(Realm.Configuration.defaultConfiguration.fileURL)
tableView.delegate = self
tableView.dataSource = self
tableView.separatorStyle = .none
tableView.register(UINib(nibName: Const.UI.NibName.hiActionCell, bundle: nil), forCellReuseIdentifier: Const.UI.CellIdentifier.hiActionCell)
}
//MARK:- Elements setup
func setupUI() {
view.backgroundColor = UIColor.black
title = Const.NameString.workouts
addActionButton.setTitle(Const.NameString.addAction, for: .normal)
addActionButton.tintColor = UIColor.black
addActionButton.titleLabel?.font = UIFont.textStyle9
workoutNameLabel.text = Const.NameString.workoutName
workoutNameLabel.font = UIFont.textStyle7
workoutNameLabel.textColor = UIColor.white
workoutNameTextField.textColor = UIColor.black
workoutNameTextField.font = UIFont.textStyle8
roundsLabel.text = Const.NameString.startingRounds
roundsLabel.font = UIFont.textStyle9
roundsLabel.textColor = UIColor.black
roundsLabel.textAlignment = .center
}
#IBAction func saveButtonAction(_ sender: Any) {
if workoutNameTextField.text != "" {
workout.title = workoutNameTextField.text!
workout.rounds = numberOfRounds
save(workout)
self.dismiss(animated: true, completion: nil)
} else {
let myalert = UIAlertController(title: "Message", message: "You forgot something to add ;) (name, rounds, seconds)", preferredStyle: UIAlertController.Style.alert)
myalert.addAction(UIAlertAction.init(title: "I'm guilty", style: .default, handler: nil))
self.present(myalert, animated: true)
}
}
func save(_ workout: WorkoutModel) {
do {
try realm.write {
realm.add(workout)
}
} catch {
print(error.localizedDescription)
}
}
#IBAction func cancelButtonAction(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
}
#IBAction func addActionButtonPressed(_ sender: Any) {
workout.actionsArray.append(action)
tableView.beginUpdates()
tableView.insertRows(at: [IndexPath(row: workout.actionsArray.count - 1, section: 0)], with: .fade)
tableView.endUpdates()
}
#IBAction func addRoundsBtn(_ sender: Any) {
numberOfRounds += 1
roundsLabel.text = "\(numberOfRounds) ROUNDS"
}
#IBAction func reduceRoundsBtn(_ sender: Any) {
if numberOfRounds > 0 {
numberOfRounds -= 1
roundsLabel.text = "\(numberOfRounds) ROUNDS"
}
}
}
//MARK:- Setup TextFieldDelegate Method
extension AddActionViewController: UITextFieldDelegate {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
view.endEditing(true)
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
workoutNameTextField.resignFirstResponder()
return true
}
}
//Quick guide implementation for showing and deleting cells
extension AddActionViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return workout.actionsArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: Const.UI.CellIdentifier.hiActionCell) as? HITActionCell else {
return UITableViewCell()
}
cell.delegate = self
cell.setupElements()
cell.id = indexPath.row
return cell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if (editingStyle == .delete) {
workout.actionsArray.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
}
if (editingStyle == .insert) {
print("test")
}
}
extension AddActionViewController: HITActionCellDelegate {
func action(sec: Int, name: String, id: Int) {
action.name = name
action.seconds = sec
action.id = id
}
}
If could someone help me, It would be greet for me :)

TableView with TableViewCell in UIViewController not showing the data from SearchBar

I am having trouble displaying the data I got from my REST API. I can retrieve the data with no problem but the tableview is not displaying the data. How do I get the data inside the tableview cell in the tableview?
class SearchBSViewController: UIViewController {
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var tableView: UITableView!
lazy var tapRecognizer: UITapGestureRecognizer = {
var recognizer = UITapGestureRecognizer(target:self, action: #selector(dismissKeyBoard))
return recognizer
}()
var searchResults: [Service] = []
let busStop = BusStop(odataMetadata: "", busStopCode: "", services: [])
let queryService = QueryService()
override func viewDidLoad() {
super.viewDidLoad()
alterLayout()
searchBar.delegate = self
}
func alterLayout() {
tableView.tableHeaderView = UIView()
tableView.estimatedSectionHeaderHeight = 50
navigationItem.titleView = searchBar
searchBar.showsScopeBar = false
searchBar.placeholder = "Search for bus stop by bus code"
}
}
extension SearchBSViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.searchResults.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell: BusCell = tableView.dequeueReusableCell(withIdentifier: "BusCell", for: indexPath) as? BusCell else {
return UITableViewCell()
}
let bus = searchResults[indexPath.row]
cell.busNoLbl.text = bus.serviceNo
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexpath: IndexPath) -> CGFloat {
return 100
}
}
extension SearchBSViewController: UISearchBarDelegate {
#objc func dismissKeyBoard() {
searchBar.resignFirstResponder()
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
dismissKeyBoard()
print("Something")
guard let searchText = searchBar.text, !searchText.isEmpty else { return }
UIApplication.shared.isNetworkActivityIndicatorVisible = true
guard let searchNumber: Int = Int(searchText) else { return }
queryService.GetBusStop(BusNo: searchNumber) {
results in
UIApplication.shared.isNetworkActivityIndicatorVisible = false
if let results = results {
print(results.services?[1].nextBus?.estimatedArrival ?? 0)
self.searchResults = results.services!
self.tableView.reloadData()
self.tableView.setContentOffset(CGPoint.zero, animated: false)
}
}
}
func position(for bar: UIBarPositioning) -> UIBarPosition {
return .topAttached
}
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
view.addGestureRecognizer(tapRecognizer)
}
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
view.removeGestureRecognizer(tapRecognizer)
}
}
class BusCell: UITableViewCell {
#IBOutlet weak var busNoLbl: UILabel!
#IBOutlet weak var firstBusLbl: UILabel!
#IBOutlet weak var secBusLbl: UILabel!
#IBOutlet weak var thirdBusLbl: UILabel!
#IBOutlet weak var firstPrg: UIProgressView!
#IBOutlet weak var secPrg: UIProgressView!
#IBOutlet weak var thirdPrg: UIProgressView!
#IBOutlet weak var firstType: UILabel!
#IBOutlet weak var secType: UILabel!
#IBOutlet weak var thirdType: UILabel!
func configure(services: Service) {
busNoLbl.text = services.serviceNo
firstBusLbl.text = services.nextBus?.estimatedArrival
secBusLbl.text = services.nextBus2?.estimatedArrival
thirdBusLbl.text = services.nextBus3?.estimatedArrival
}
}
class QueryService {
typealias QueryResult = (BusStop?) -> ()
var buses: BusStop = BusStop(odataMetadata: "", busStopCode: "", services: [])
let defaultSession = URLSession(configuration: .default)
var dataTask: URLSessionDataTask?
func GetBusStop(BusNo: Int, completionBlock: #escaping QueryResult){
dataTask?.cancel()
var urlComponents = URLComponents(string: "http://datamall2.mytransport.sg/ltaodataservice/BusArrivalv2")!
urlComponents.queryItems = [URLQueryItem(name: "BusStopCode", value: String(BusNo))]
guard let url = urlComponents.url else { return}
let urlRequest = Header(url: url) //input the header for authorization
dataTask = defaultSession.dataTask(with: urlRequest) { (data, response, error) in
if error != nil {
print(error!.localizedDescription)
}
guard let data = data else { return }
do {
let BusStopData = try
JSONDecoder().decode(BusStop.self, from: data)
DispatchQueue.main.async {
print("Queue")
completionBlock(BusStopData)
}
} catch let jsonError {
print(jsonError)
}
}
dataTask?.resume()
}
}
First thing is you need to set up a delegate and data source method for the table view. setup it in your view did load method
tableView.delegate = self
tableView.datasource = self
Second is you need to reload your table view after you hit your API call
tableView.reloadData()
Please set delegate and datasoruce of table view in alterLayout() function, like
tableView.delegate = self
tableView.datasource = self

Swift - tableviewcell returns empty using custom cell

I am new to swift programming and would need some help to check what is wrong in my tableviewcell. I have tried alot of great suggestions on stackoverflow.( make sure your outlets are connected, set delegate and datasource of your tableview to self)
This is my ViewController:
import UIKit
class CharacterViewController: UIViewController, UITableViewDataSource, UITableViewDelegate , APIControllerProtocol {
#IBOutlet weak var CharacterInfoView: UITableView!
var apiController:APIController!
var dataArray: [[String:Any]]?
var processcharacter= [CharacterListModel]()
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor=UIColor.white
self.CharacterInfoView.estimatedRowHeight = 44
self.CharacterInfoView.rowHeight = UITableViewAutomaticDimension
self.CharacterInfoView.dataSource = self
self.CharacterInfoView.delegate = self
apiController = APIController()
apiController.delegate=self
self.navigationItem.title = "Character"
self.view.showLoading()
apiController.getCharacterData{ (statusCode, data, response, error) -> () in
self.view.stopLoading()
if(statusCode == nil)
{
self.view.showServiceNotAvailableMessage(self)
}
if !(error == nil)
{
self.view.showServiceNotAvailableMessage(self)
}
if statusCode == 200
{
do
{
self.processcharacter= CharacterListData.processData(data: data)
self.CharacterInfoView.reloadData()
}
catch(_ as NSError)
{
}
}
else
{
return
}
}
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if self.dataArray == nil
{
return 0
}
else
{
return processcharacter.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier:"Cell", for:indexPath) as! CharacterTableViewCell
var processcharacters= processcharacter[indexPath.row]
cell.location.text = processcharacters.location
cell.name.text = processcharacters.name
cell.characterID.text = processcharacters.characterID
cell.time.text = processcharacters.lastlocatedtime
if wecares.sos == true {
processcharacters.imagebutton = UIImage(named: "sos_icon")!
}
else{
processcharacters.imagebutton = UIImage(named: "null_button")!
}
cell.button.image = processcharacters.imagebutton
return cell
}
func reachabilityChanged(_ status: Bool) {
}
}
This is my subclass for my ViewController:
import Foundation
class CharacterListDataHelper: NSObject {
static func processData(data: AnyObject?) -> [CharacterListModel]
{
var modelList:[CharacterListModel] = [CharacterListModel]()
let darr = try? JSONSerialization.jsonObject(with: data! as! Data, options: .mutableLeaves) as! [[String:Any]]
var dataModel:CharacterListModel
for obj in darr!
{
dataModel = CharacterListModel()
dataModel.location = obj["playerLocation"] as! String
dataModel.name = obj["playerName"] as! String
dataModel.characterID= obj["playerID"] as! String
dataModel.lastlocatedtime = obj["lastUpdatedTime"] as! String
}
modelList.append(dataModel)
}
return modelList
}
}
This is my Model:
import Foundation
struct CharacterListModel {
var name: String?
var characterID: String?
var location: String?
var lastlocatedtime: String?
var imagebutton: UIImage?
var sos: Bool?
}
This is my TableViewCell:
class CharacterTableViewCell: UITableViewCell {
#IBOutlet weak var name: UILabel!
#IBOutlet weak var characterID: UILabel!
#IBOutlet weak var location: UILabel!
#IBOutlet weak var lastlocatedtime: UILabel!
#IBOutlet weak var button: UIImageView!
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
}
Thanks in advance!
Don't fetch data in viewDidLoad() method. Fetch in viewWillAppear and reload after getting data.

Resources