I want to show filtered cells in UITableView using UISearchBar, but it's not working. My search function is at the end of my code.
import UIKit
struct User2: Codable {
let firstName: String
let lastName: String
let email: String
let userid: String
enum CodingKeys: String, CodingKey {
case firstName = "first_name"
case lastName = "last_name"
case email = "email"
case userid = "user_id"
}
}
class SearchViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableview: UITableView!
#IBOutlet var searchBtn: UISearchBar!
private var dataSource = [User]() {
didSet {
self.tableview.reloadData()
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.tableview.register(UITableViewCell.self, forCellReuseIdentifier: "groupCell")
self.tableview.dataSource = self
self.tableview.delegate = self
let url = URL(string: "https://ex.com/ex.php")
URLSession.shared.dataTask(with: url!, completionHandler: { [weak self] (data, response, error) in
guard let data = data, error == nil else {
print(error?.localizedDescription ?? "An error occurred")
return
}
DispatchQueue.main.async {
self?.dataSource = try! JSONDecoder().decode([User].self, from: data)
}
}).resume()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
tableview.reloadData()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataSource.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "groupCell", for: indexPath)
let user = self.dataSource[indexPath.row]
cell.textLabel?.text = user.firstName + " " + user.lastName
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let user = self.dataSource[indexPath.row]
let userVC = DataViewController(user: user)
self.navigationController?.pushViewController(userVC, animated: true)
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
User2 = User2.filter({ (item) -> Bool in
let heading: NSString = ((item["firstName"]! as? String)! + (item["lastName"]! as! String)) as NSString
return (heading.range(of: searchText, options: NSString.CompareOptions.caseInsensitive).location) != NSNotFound
})
self.tableview.reloadData()
}
}
A better approach would be to have 2 dataSources, one for normal state, another when there is active search, like so:
class SearchViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
private var dataSource = [User]() {
didSet {
self.tableview.reloadData()
}
}
private var searchDataSource: [User]? {
didSet {
self.tableView.reloadData()
}
}
override func viewDidLoad() {
super.viewDidLoad()
...
self.searchBtn.delegate = self
}
// some changes to UITableView DataSource/Delegate
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.searchDataSource?.count ?? self.dataSource.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
...
let user = self.searchDataSource?[indexPath.row] ?? self.dataSource[indexPath.row]
...
}
}
Finally use following extension extending UISearchBarDelegate
extension SearchViewController: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchText.isEmpty {
self.searchDataSource = nil
} else {
self.searchDataSource = self.dataSource.filter({
let fullName = $0.firstName + " " + $0.lastName
return fullName.range(of: searchText, options: [.anchored, .caseInsensitive]) != nil
})
}
}
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
if self.searchDataSource != nil && searchBar.text?.isEmpty ?? true {
self.searchDataSource = nil
}
}
}
that yields
I think filter should be applied on dataSource as, where dataSource is of kind [User2]:
let filteredArr: [User] = dataSource.filter({ $0.firstName.lowercased().contains(searchText.lowercased()) || $0.lastName.lowercased().contains(searchText.lowercased()) })
You can try to apply filtering in your dataSource like:
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
let filteredArray = dataSource.filter({ user in
let username = "\(user.firstName) \(user.lastName)"
return username.range(of: searchText, options: .caseInsensitive) != nil
})
self.tableview.reloadData()
}
Related
I faced a problem as I cannot return number of cells from the fetchedData object.
I can print an array with data however I cannot fill out cells.
It'd be great if anybody could help me resolve this mystery :)
Here's my Model:
import Foundation
struct ExchangeRateModel: Codable {
let table: String
let no: String
let effectiveDate: String
let rates: [Rate]
}
struct Rate: Codable {
let currency: String
let code: String
let mid: Double
}
and rootVC
import UIKit
class RootViewController: UIViewController {
private let cellID = "cell"
private let tabelType = ["a","b","c"]
private let exchangeTabels = ["Tabela A", "Tabela B", "Tabela C"]
private var currentTable = "a"
private let urlString = "https://api.nbp.pl/api/exchangerates/tables/"
var fetchedData = [ExchangeRateModel]()
private let tableView: UITableView = {
let tabel = UITableView()
return tabel
}()
override func viewDidLoad() {
super.viewDidLoad()
configureNavBar()
configureView()
configureTable()
performeRequest()
tableView.reloadData()
}
private func configureNavBar(){
title = "KURSY WALUT NBP"
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Tabele", style: .plain, target: self, action: #selector(tabeleTapped))
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .refresh, target: self, action: #selector(refreshTapped))
}
#objc private func tabeleTapped(){
let ac = UIAlertController(title: "Zmień tabele kursów", message: nil, preferredStyle: .actionSheet)
for table in exchangeTabels {
ac.addAction(UIAlertAction(title: table, style: .default, handler: changeTable))
}
present(ac, animated: true)
}
func changeTable(action: UIAlertAction){
if action.title == exchangeTabels[0]{
currentTable = tabelType[0]
tableView.reloadData()
} else if action.title == exchangeTabels[1] {
currentTable = tabelType[1]
tableView.reloadData()
} else {
currentTable = tabelType[2]
tableView.reloadData()
}
}
#objc private func refreshTapped(){
tableView.reloadData()
}
private func configureView(){
view.backgroundColor = .white
view.addSubview(tableView)
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
private func configureTable(){
tableView.delegate = self
tableView.dataSource = self
tableView.rowHeight = 50
tableView.register(TabelTableViewCell.self, forCellReuseIdentifier: cellID)
}
private func performeRequest(){
let urlString = "\(self.urlString)\(currentTable)"
let url = URL(string: urlString)
print("URL: \(url!)")
guard url != nil else { return }
let session = URLSession(configuration: .default)
let dataTask = session.dataTask(with: url!) { (data, response, error) in
if error == nil && data != nil {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode([ExchangeRateModel].self, from: data!)
self.fetchedData = decodedData
print(self.fetchedData)
} catch {
print(error)
}
}
}
DispatchQueue.main.async {
dataTask.resume()
self.tableView.reloadData()
}
}
}
extension RootViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print(fetchedData.count)
return fetchedData.count
}
Here I'm not sure how to return those data
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellID, for: indexPath) as! TabelTableViewCell
cell.currencyNameLabel.text = fetchedData[indexPath.row].rates[indexPath.row].currency
cell.currencyCodeLabel.text = fetchedData[indexPath.row].rates[indexPath.row].code
cell.effectiveDateLabel.text = fetchedData[indexPath.row].effectiveDate
cell.midRateLabel.text = String(fetchedData[indexPath.row].rates[indexPath.row].mid)
return cell
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
let nextVC = CurrencyViewController()
navigationController?.pushViewController(nextVC, animated: true)
}
}
Thank you all in advance!
Need to reload tableview after getting the value from server.
Try this code
private func performeRequest(){
let urlString = "\(self.urlString)\(currentTable)"
let url = URL(string: urlString)
print("URL: \(url!)")
guard url != nil else { return }
let session = URLSession(configuration: .default)
let dataTask = session.dataTask(with: url!) { (data, response, error) in
if error == nil && data != nil {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode([ExchangeRateModel].self, from: data!)
self.fetchedData = decodedData
print(self.fetchedData)
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch {
print(error)
}
}
}
dataTask.resume()
}
The json contains only one ExchangeRateModel in the array.
Seems like you want to show the rates. You need to return fetchedData[0].rates.count instead of return fetchedData.count
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if fetchedData.count > 0 {
return fetchedData[0].rates.count
}
return 0
}
Also you need to update cellForRowAtIndexPath method like this
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellID, for: indexPath) as! TabelTableViewCell
cell.currencyNameLabel.text = fetchedData[0].rates[indexPath.row].currency
cell.currencyCodeLabel.text = fetchedData[0].rates[indexPath.row].code
cell.effectiveDateLabel.text = fetchedData[0].effectiveDate
cell.midRateLabel.text = String(fetchedData[0].rates[indexPath.row].mid)
return cell
}
You did't reload table after coming response from the api call. inside response callback where you print the array of response you have to reload tableview.
do {
let decodedData = try decoder.decode([ExchangeRateModel].self, from: data!)
self.fetchedData = decodedData
print(self.fetchedData)
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch {
print(error)
}
I also added DispatchQueue and updated all other places.
See below:
private func performeRequest(){
let urlString = "\(self.urlString)\(currentTable)"
let url = URL(string: urlString)
print("URL: \(url!)")
guard url != nil else { return }
let session = URLSession(configuration: .default)
let dataTask = session.dataTask(with: url!) { (data, response, error) in
if error == nil && data != nil {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode([ExchangeRateModel].self, from: data!)
self.fetchedData = decodedData
print(self.fetchedData)
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch {
print(error)
}
}
}
dataTask.resume()
}
}
and also table:
extension RootViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print(fetchedData.count)
return fetchedData[0].rates.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellID, for: indexPath) as! TabelTableViewCell
cell.currencyNameLabel.text = fetchedData[0].rates[indexPath.row].currency
cell.currencyCodeLabel.text = fetchedData[0].rates[indexPath.row].code
cell.effectiveDateLabel.text = fetchedData[0].effectiveDate
cell.midRateLabel.text = String(fetchedData[0].rates[indexPath.row].mid)
return cell
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
let nextVC = CurrencyViewController()
navigationController?.pushViewController(nextVC, animated: true)
}
}
and I'm still getting this error.
....................................................................................................................................................................
I get the data and print it. Maybe the code is not correct I'm just new into swift, but I don't have any errors in Xcode. There's something missing but I just don't know what.
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
struct Class: Codable {
let first_name: String
let last_name: String
let email: String
enum CodingKeys: String, CodingKey {
case first_name = "first_name"
case last_name = "last_name"
case email = "email"
}
}
#IBOutlet weak var tableview: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "https://x.de/x.php")
URLSession.shared.dataTask(with: url!, completionHandler: {(data, response, error) in
guard let data = data, error == nil else { print(error!); return }
let decoder = JSONDecoder()
let classes = try! decoder.decode([Class].self, from: data)
for myClass in classes {
print(myClass.first_name)
print(myClass.last_name)
print(myClass.email)
}
}
).resume()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
tableview.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//return myarray.count
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "groupCell", for: indexPath) as UITableViewCell
return cell
}
}
Several issues
Your view controller is missing dataSource, an Array that holds list of users
Update the array (dataSource) once remote data is received
Reload UITableView once the array (dataSource) changes, using tableView.reloadData()
Within numberOfRowsInSection return the number of array elements dataSource.count
UITableView.dequeueReusableCell(withIdentifier:for:) requires that you register a cell class or xib using tableview.register method
Lastly, instead of underscores, as first_name, use camelCase variable naming convention, as firstName
struct User: Codable {
let firstName: String
let lastName: String
let email: String
enum CodingKeys: String, CodingKey {
case firstName = "first_name"
case lastName = "last_name"
case email = "email"
}
}
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableview: UITableView!
private var dataSource = [User]() {
didSet {
self.tableview.reloadData()
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.tableview.register(UITableViewCell.self, forCellReuseIdentifier: "groupCell")
self.tableview.dataSource = self
self.tableview.delegate = self
let url = URL(string: "https://example.com/users.php")
URLSession.shared.dataTask(with: url!, completionHandler: { [weak self] (data, response, error) in
guard let data = data, error == nil else {
print(error?.localizedDescription ?? "An error occurred")
return
}
// Within `dataTask` we are on background thread, we must update our UITableView on main thread
DispatchQueue.main.async {
self?.dataSource = try! JSONDecoder().decode([User].self, from: data)
}
}).resume()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
tableview.reloadData()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataSource.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "groupCell", for: indexPath)
let user = self.dataSource[indexPath.row]
cell.textLabel?.text = user.firstName
return cell
}
}
Above code yields
In the cellForRowAt func you have to do something like that :
cell.textLabel?.text = myarray[indexPath.row]
because the text of myarray should be displayed at a label or whatever
I would like some help with the search bar functionality. I am stuck and not sure where to take it from here. I am trying to update the tableview when search text word is contained in a recipe title. I am not sure how to do this because of the depreciated searchDisplay controller. Help would be appreciated.
import UIKit
import SwiftyJSON
class Downloader {
class func downloadImageWithURL(_ url:String) -> UIImage! {
let data = try? Data(contentsOf: URL(string: url)!)
return UIImage(data: data!)
}
}
class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource,UISearchBarDelegate,UISearchDisplayDelegate{
#IBOutlet weak var recipeTable: UITableView!
// search functionality Need help with my search functionality
var filteredRecipes = [Recipe]()
func filterContentForSearch(searchText:String) {
// need help here
self.filteredRecipes = self.recipes.filter({(title:Recipe) -> Bool in
return (title.title!.lowercased().range(of: searchText.lowercased()) != nil)
})
}
private func searchDisplayController(controller: UISearchController!, shouldReloadTableForSearchString searchString: String!) -> Bool {
self.filterContentForSearch(searchText: searchString)
return true
}
//end search parameters
// tableview functionionalitys
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableView == searchDisplayController!.searchResultsTableView {
return filteredRecipes.count
}else{
return recipes.count
}
// recipeTable.reloadData()
}
// tableview functionalities
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! RecipeTableViewCell
if tableView == self.searchDisplayController!.searchResultsTableView{
//get images from download
DispatchQueue.main.async { () ->Void in
cell.imageLabel.image = Downloader.downloadImageWithURL(self.filteredRecipes[indexPath.row].imageUrl)
}
cell.recipeLabel.text = self.filteredRecipes[indexPath.row].title
recipeTable.reloadData()
}else{
//get image from download
DispatchQueue.main.async { () ->Void in
cell.imageLabel.image = Downloader.downloadImageWithURL(self.recipes[indexPath.row].imageUrl)
}
cell.recipeLabel.text = recipes[indexPath.row].title
}
//recipeTable.reloadData()
return cell
}
// structs for json
struct Root : Decodable {
let count : Int
let recipes : [Recipe]
}
struct Recipe : Decodable { // It's highly recommended to declare Recipe in singular form
let recipeId : String
let imageUrl, sourceUrl, f2fUrl : String
let title : String?
let publisher : String
let socialRank : Double
let page : Int?
let ingredients : [String]?
}
//recipes is array of Recipes
var recipes = [Recipe]() // array of recipes
//unfiltered recipes to put into search
fileprivate func getRecipes() {
let jsonURL = "https://www.food2fork.com/api/search?key=264045e3ff7b84ee346eb20e1642d9d9"
//.data(using: .utf8)!
guard let url = URL(string: jsonURL) else{return}
URLSession.shared.dataTask(with: url) {(data, response , err) in
if let response = response as? HTTPURLResponse, response.statusCode != 200 {
print(response.statusCode)
return
}
DispatchQueue.main.async {
if let err = err{
print("failed to get data from URL",err)
return
}
guard let data = data else{return}
//print(String(data: data, encoding: .utf8))
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let result = try decoder.decode(Root.self, from: data)
self.recipes = result.recipes
//print(result.recipes)
self.recipeTable.reloadData()
}catch let jsonERR {
print("Failed to decode",jsonERR)
}
}
}.resume()
}
override func viewDidLoad() {
super.viewDidLoad()
//recipeTable.reloadData()
//search bar
//filteredRecipes = recipes
//call json object
getRecipes()
}
}
You could take this approach:
Add a Boolean variable to indicate whether searching or not
var searching: Bool = false
Use this for numberOfRowsInSection for the tableview
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searching {
return filteredRecipes.count
} else {
return recipes.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! RecipeTableViewCell
var recipe: Recipe
if searching {
recipe = filteredRecipes[indexPath.row]
} else {
recipe = recipes[indexPath.row]
}
DispatchQueue.main.async { () ->Void in
cell.imageLabel.image = Downloader.downloadImageWithURL(recipe.imageUrl)
}
cell.recipeLabel.text = recipe.title
return cell
}
And add this for the searchBar (set searching for other funcs like searchBarCancelButtonClicked)
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchBar.text == nil || searchBar.text == "" {
searching = false
filteredRecipes.removeAll()
view.endEditing(true)
} else {
searching = true
filteredRecipes = recipes.filter{$0.title.contains(searchBar.text!)}
}
tableView.reloadData()
}
I'm trying to save the data from func getCoinData to an array sympolsCoin and array sympolsCoin to use it in my TableView
I create this class in the same ViewController.swift file :
struct Coin: Decodable {
let symbol : String
let price_usd : String }
And this in my View controller class :
var coins = [Coin]()
var sympolsCoin = [String]()
var priceUSDcoin = [String]()
func getCoinData(completion: #escaping () -> ()) {
let jsonURL = "https://api.coinmarketcap.com/v1/ticker/"
let url = URL(string: jsonURL)
URLSession.shared.dataTask(with: url!) { (data, response, error) in
do {
self.coins = try JSONDecoder().decode([Coin].self, from: data!)
for info in self.coins {
self.sympolsCoin.append(info.symbol)
self.priceUSDcoin.append(info.price_usd)
print("\(self.sympolsCoin) : \(self.priceUSDcoin)")
completion()
}
}
catch {
print("Error is : \n\(error)")
}
}.resume()
}
And when i use the array in my TableView i got blank table !
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "BitcoinTableViewCell", for: indexPath) as! BitcoinTableViewCell
cell.coinNameLable.text = sympolsCoin[indexPath.row]
cell.priceLable.text = priceUSDcoin[indexPath.row]
return cell
}
Since you are using JSONDecoder the entire logic to create and populate sympolsCoin and priceUSDcoin is pointless and redundant.
struct Coin: Decodable {
private enum CodingKeys: String, CodingKey {
case symbol, priceUSD = "price_usd"
}
let symbol : String
let priceUSD : String
}
var coins = [Coin]()
The completion handler is redundant, too. Just reload the table view on the main thread after receiving the data:
func getCoinData() {
let jsonURL = "https://api.coinmarketcap.com/v1/ticker/"
let url = URL(string: jsonURL)
URLSession.shared.dataTask(with: url!) { [unowned self] (data, response, error) in
guard let data = data else { return }
do {
self.coins = try JSONDecoder().decode([Coin].self, from: data)
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch {
print("Error is : \n\(error)")
}
}.resume()
}
In viewDidLoad load the data
override func viewDidLoad() {
super.viewDidLoad()
getCoinData()
}
In cellForRow update the UI
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "BitcoinTableViewCell", for: indexPath) as! BitcoinTableViewCell
let coin = coins[indexPath.row]
cell.coinNameLable.text = coin.symbol
cell.priceLable.text = coin.priceUSD
return cell
}
Create an Outlet of tableView in ViewController Class and give it name "tableView" then
Try this code: Swift 4
func getCoinData() {
let jsonURL = "https://api.coinmarketcap.com/v1/ticker/"
let url = URL(string: jsonURL)
URLSession.shared.dataTask(with: url!) { (data, response, error) in
do {
self.coins = try JSONDecoder().decode([Coin].self, from: data!)
for info in self.coins {
self.sympolsCoin.append(info.symbol)
self.priceUSDcoin.append(info.price_usd)
print("\(self.sympolsCoin) : \(self.priceUSDcoin)")
self.tableView.reloadData()
}
}
catch {
print("Error is : \n\(error)")
}
}.resume()
}
Call this function in ViewDidLoad like this
override func viewDidLoad() {
super.viewDidLoad()
getCoinData()
}
You need to update the tableView from the main thread. As a good lesson to learn: Always update the UI from the Main Thread. Always.
do {
self.coins = try JSONDecoder().decode([Coin].self, from: data!)
for info in self.coins {
self.sympolsCoin.append(info.symbol)
self.priceUSDcoin.append(info.price_usd)
DispatchQueue.main.async {
self.tableView.reloadData()
}
print("\(self.sympolsCoin) : \(self.priceUSDcoin)")
completion()
}
}
There is, however another problem with your code the way you have your labels setup won't work. TableViewCells get reused so I'm guessing you have #IBOutlets for them somewhere else. What you should do is declare a label constant in cellForRowAt:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath)
let coinNameLabel = cell.viewWithTag(100) as! UILabel
coinNameLabel.text = sympolsCoin[indexPath.row]
let priceNameLabel = cell.viewWithTag(101) as! UILabel
priceNameLabel.text = priceUSDcoin[indexPath.row]
}
The above code assumes you've setup two labels with the tags 100 and 101 in your storyboard(assuming your using one)
**
// First View Controller
//
//
//
import UIKit
struct Countory : Decodable {
let name: String
let capital: String
let region: String
let alpha2Code: String
}
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var listArr = [Countory]()
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.delegate = self
self.tableView.dataSource = self
let url = "https://restcountries.eu/rest/v2/all"
let urlObj = URL(string: url)!
URLSession.shared.dataTask(with: urlObj) {(data, responds, Error) in
do {
self.listArr = try JSONDecoder().decode([Countory].self, from: data!)
for country in self.listArr {
print("Country",country.name)
print("###################")
print("Capital",country.capital)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
} catch {
print(" not ")
}
}.resume()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.listArr.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell") as! TableViewCell
cell.label1.text = "Name: \(listArr[indexPath.row].name)"
cell.lable2.text = listArr[indexPath.row].capital
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let homeView = self.storyboard?.instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController
homeView.res = listArr[indexPath.row].region
homeView.alpha = listArr[indexPath.row].alpha2Code
self.navigationController?.pushViewController(homeView, animated: true)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
}
// SecondViewController
class SecondViewController: UIViewController {
#IBOutlet weak var label4: UILabel!
#IBOutlet weak var label3: UILabel!
var res = ""
var alpha = ""
override func viewDidLoad() {
super.viewDidLoad()
self.label3.text = res
self.label4.text = alpha
}
}
**
I have a infinite-scrolling in tableView like facebook, it works very well but the problem is activityIndicator does not hide when complete data loading.
NetworkRequestAPI Request
import Foundation
class NetworkRequestAPI {
static func getPropductListByCategory(productId : Int, pageNo : Int , completion: #escaping ([Product]? , Error?) -> ()){
let url = URL(string: Configuration.BASE_URL+"/product-by/type?product_type_id="+String(productId)+"&page="+String(pageNo))
var categoryObject = [Product]()
URLSession.shared.dataTask(with:url!) { (urlContent, response, error) in
if error != nil {
}
else {
do {
let json = try JSONSerialization.jsonObject(with: urlContent!) as! [String:Any]
let products = json["products"] as? [String: Any]
// productCount = (json["product_count"] as? Int)!
let items = products?["data"] as? [[String:Any]]
items?.forEach { item in
let oProduct = Product()
oProduct.product_id = item["product_id"] as? Int
oProduct.product_name = item["product_name"] as? String
oProduct.product_image = item["product_image"] as? String
let ratingItem = item["rating_info"] as? [String: AnyObject]
let rating = RatingInfo()
rating.final_rating = ratingItem?["final_rating"] as? String
oProduct.rating_info = rating
categoryObject.append(oProduct)
}
completion(categoryObject, nil)
} catch let error as NSError {
print(error)
completion(nil, error)
}
}
}.resume()
}
}
ListTableView Class
class ListTableView: UITableViewController {
var isInitUILoad = true
var arrProduct = [[Product]]()
var product_id:Int = 0
var pageNo = 1
override func viewDidLoad() {
super.viewDidLoad()
self.initUILoad()
}
func initUILoad(){
ActivityIndicator.customActivityIndicatory(self.view, startAnimate: true)
NetworkRequestAPI.getPropductListByCategory(productId: product_id, pageNo: pageNo) { (products, error) in
DispatchQueue.main.async(execute: {
if products != nil {
self.pageNo += 1
self.arrProduct.append(products!)
print(self.arrProduct.count)
}else{
print(error.debugDescription)
}
ActivityIndicator.customActivityIndicatory(self.view, startAnimate: false)
self.tableView?.reloadData()
self.isInitUILoad = false
})
}
}
func loadMore(complition:#escaping (Bool) -> ()) {
NetworkRequestAPI.getPropductListByCategory(productId: product_id, pageNo: pageNo) { (products, error) in
DispatchQueue.main.async(execute: {
if error != nil{
print(error.debugDescription)
}
if products != nil && products?.count ?? 0 > 0{
self.pageNo += 1
self.arrProduct.append(products!) self.tableView?.insertSections([self.arrProduct.count - 1], with: .fade)
}else{
print("no product left")
}
complition(true)
})
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: categoryCellid, for: indexPath) as! ListTableCell
var categoryObject = arrProduct[indexPath.section]
cell.product = categoryObject[indexPath.item]
return cell
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 115.0
}
override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let footerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: footerCellid) as! HeadFooterCell
if !isInitUILoad {
tableView.tableFooterView?.isHidden = true
loadMore(complition: { (isDone) in
footerView.activityIndicatorView.stopAnimating()
})
}
return footerView
}
override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 0.01
}
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let lastSectionIndex = tableView.numberOfSections - 1
let lastRowIndex = tableView.numberOfRows(inSection: lastSectionIndex) - 1
if indexPath.section == lastSectionIndex && indexPath.row == lastRowIndex {
// print("this is the last cell")
let spinner = UIActivityIndicatorView(activityIndicatorStyle: .gray)
spinner.startAnimating()
spinner.backgroundColor = .green
spinner.frame = CGRect(x: CGFloat(0), y: CGFloat(0), width: tableView.bounds.width, height: CGFloat(44))
self.tableView.tableFooterView = spinner
self.tableView.tableFooterView?.isHidden = false
}
}
HeadFooterCell
class HeadFooterCell: UITableViewHeaderFooterView {
override init(reuseIdentifier: String?) {
super.init(reuseIdentifier: reuseIdentifier)
setupViews()
}
var activityIndicatorView : UIActivityIndicatorView = {
var activityIndicatorView = UIActivityIndicatorView(activityIndicatorStyle: .gray)
activityIndicatorView.backgroundColor = UIColor.cyan
return activityIndicatorView
}()
func setupViews() {
addSubview(activityIndicatorView)
addConstraintsWithFormat("H:|[v0]|", views: activityIndicatorView)
addConstraintsWithFormat("V:|[v0(50)]|", views: activityIndicatorView)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
You need to stop it in the main thread:
DispatchQueue.main.async {
footerView.activityIndicatorView.stopAnimating()
}
Also, set hidesWhenStopped to true:
footerView.activityIndicatorView.hidesWhenStopped = true